PageSourceSearch

https://gramjam.app/_app/immutable/chunks/D8x9GurF.js

js gramjam.app collected 2026-09-25 20:21:07 UTC 9,190 bytes, 260 lines download raw bytes

1(function() {
2	try {
3		var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
4		e.SENTRY_RELEASE = { id: "63139fb4359a32dff6e65ef9c1c4be204da0c121" };
5		var n = new e.Error().stack;
6		n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "0c2c20ec-f21b-4a2b-ab47-ffc14348389f", e._sentryDebugIdIdentifier = "sentry-dbid-0c2c20ec-f21b-4a2b-ab47-ffc14348389f");
7	} catch (e) {}
8})();
9import { i as __toESM } from "./0xK-A0pq.js";
10import { Rn as expose, Un as coordStr2Int, Wn as coordToStr, er as wordToString, rr as scoreWord, tr as getTileId, zn as require_localforage } from "./CvEu9crO.js";
11//#region src/algorithms/trie.ts
12var import_localforage = /* @__PURE__ */ __toESM(require_localforage(), 1);
13var Trie = class {
14	root;
15	elements;
16	constructor() {
17		this.root = {
18			terminal: false,
19			children: {}
20		};
21		this.elements = 0;
22	}
23	get length() {
24		return this.elements;
25	}
26	get(key) {
27		const node = this.getNode(key);
28		if (node) return node.value;
29	}
30	contains(key) {
31		return !!this.getNode(key);
32	}
33	insert(key, value) {
34		let node = this.root;
35		for (const char of [...key]) {
36			if (!node.children[char]) node.children[char] = {
37				terminal: false,
38				children: {}
39			};
40			node = node.children[char];
41		}
42		node.terminal = true;
43		node.value = value;
44		if (!node.terminal) {
45			node.terminal = true;
46			this.elements += 1;
47		}
48		node.value = value;
49	}
50	remove(key) {
51		const node = this.getNode(key);
52		if (node) {
53			node.terminal = false;
54			this.elements -= 1;
55		}
56	}
57	getNode(key) {
58		let node = this.root;
59		for (const char of [...key]) {
60			node = node.children[char];
61			if (!node) return;
62		}
63		return node;
64	}
65};
66//#endregion
67//#region src/algorithms/words.md5?raw
68var words_default = "3da1b1098126b0b32ec79967e05c3a2a\n";
69//#endregion
70//#region src/algorithms/dictionary-worker.ts
71var sortLetters = (w) => [...w].sort((a, b) => a.localeCompare(b)).join("");
72var wordList = [];
73var dictionary = new Trie();
74var letters = new Trie();
75var shouldUpdateDictionary = async () => {
76	return words_default !== await import_localforage.default.getItem("dictionary-md5");
77};
78var getIntersections = (w1, w2) => {
79	const c1 = w1.coords.map(coordToStr);
80	const c2 = w2.coords.map(coordToStr);
81	const [long, short] = c1.length > c2.length ? [c1, c2] : [c2, c1];
82	return long.filter((c) => short.includes(c)).map((i) => i.split(",").map((x) => parseInt(x)));
83};
84var removeOverlappingWords = (axis) => {
85	const filteredWords = [];
86	for (const [idx, words] of Object.entries(axis)) {
87		for (const [i1, w1] of Object.entries(words)) for (const [i2, w2] of Object.entries(words).slice(+i1 + 1)) {
88			const intersections = getIntersections(w1, w2);
89			if (intersections.length) {
90				if (w1.word.length === intersections.length) filteredWords.push(+i1);
91				else if (w2.word.length === intersections.length) filteredWords.push(+i2);
92			}
93		}
94		axis[idx] = words.filter((w, i) => !filteredWords.includes(i));
95	}
96};
97var findWord = (board, i, j, axis, gameType) => {
98	const currWord = [];
99	let longestWord = [];
100	let node = dictionary.getNode("");
101	const [start, end] = axis === "row" ? [i, board.length] : [j, board[0].length];
102	for (let k = start; k < end && node; k++) {
103		const tile = board[axis === "row" ? k : i][axis === "col" ? k : j];
104		if (tile == void 0) break;
105		const nextLetter = tile.letter.toLowerCase();
106		currWord.push(tile);
107		node = node.children[nextLetter.charAt(0)];
108		if (node && nextLetter.length > 1) node = node.children[nextLetter.charAt(1)];
109		if (node?.terminal) longestWord = [...currWord];
110	}
111	if (gameType === "daily" || longestWord.map((t) => t.letter).join("").length >= 4) return longestWord;
112	else return [];
113};
114var isFullLength = (w, board) => {
115	return w.axis === "row" && w.word.length === board.length || w.axis === "col" && w.word.length === board[0].length;
116};
117var Dictionary = {
118	load: async () => {
119		if (!import_localforage.default.supports(import_localforage.default.INDEXEDDB)) throw Error("load locally");
120		import_localforage.default.config({
121			driver: [
122				import_localforage.default.INDEXEDDB,
123				import_localforage.default.WEBSQL,
124				import_localforage.default.LOCALSTORAGE
125			],
126			name: "dictionary",
127			version: 4,
128			storeName: "dictionary"
129		});
130		let words;
131		if (await shouldUpdateDictionary()) {
132			words = await (await fetch("/words.json")).json();
133			await import_localforage.default.setItem("dictionary", words);
134			await import_localforage.default.setItem("dictionary-md5", words_default);
135		} else words = await import_localforage.default.getItem("dictionary") ?? [];
136		wordList = words;
137		const start = Date.now();
138		dictionary = new Trie();
139		letters = new Trie();
140		words.forEach((w) => {
141			dictionary.insert(w, w);
142			letters.insert(sortLetters(w), w);
143		});
144		return (Date.now() - start) / 1e3;
145	},
146	findWords: (board, gameType, coords) => {
147		const rows = {};
148		const cols = {};
149		if (!coords) coords = board.flatMap((col, i) => col.map((row, j) => [i, j]));
150		for (const [i, j] of coords.filter((c) => c != null)) for (const axis of ["row", "col"]) {
151			const word = findWord(board, i, j, axis, gameType);
152			if (word.length) {
153				const coords = word.map((_, offset) => [i + (axis === "col" ? 0 : offset), j + (axis === "row" ? 0 : offset)]);
154				const s = wordToString({ word });
155				const match = {
156					word,
157					coords,
158					axis,
159					...scoreWord(word, gameType),
160					length: s.length,
161					alpha: s,
162					intersectingIds: []
163				};
164				if (axis === "row") rows[j] = (rows[j] ?? []).concat([match]);
165				else cols[i] = (cols[i] ?? []).concat([match]);
166			}
167		}
168		if (gameType !== "daily") {
169			removeOverlappingWords(rows);
170			removeOverlappingWords(cols);
171		}
172		let rowWords = Object.values(rows).flat();
173		let colWords = Object.values(cols).flat();
174		if (gameType === "daily") {
175			rowWords = rowWords.filter((w) => isFullLength(w, board));
176			colWords = colWords.filter((w) => isFullLength(w, board));
177			if (board.length === 1 && colWords.length === 1) rowWords = [];
178			else if (board[0]?.length === 1 && rowWords.length === 1) colWords = [];
179		}
180		const wordsByCoord = /* @__PURE__ */ new Map();
181		for (const word of [...rowWords, ...colWords]) for (const c of word.coords) {
182			const key = coordToStr(c);
183			wordsByCoord.set(key, (wordsByCoord.get(key) ?? []).concat([word]));
184		}
185		const intersecting = new Set([...wordsByCoord.values()].filter((ws) => ws.length > 1).flat());
186		const words = [...rowWords, ...colWords].sort((a, b) => {
187			if (intersecting.has(a) && intersecting.has(b)) return a.axis === "row" ? -1 : 1;
188			return a.coords[0][1] - b.coords[0][1];
189		});
190		const intersections = {};
191		for (const [key, sharing] of wordsByCoord) {
192			if (sharing.length < 2) continue;
193			const coord = coordStr2Int(key);
194			const tile = board[coord[0]][coord[1]];
195			const ordered = [...sharing].sort((a, b) => words.indexOf(a) - words.indexOf(b));
196			const last = ordered.pop();
197			for (const word of ordered) {
198				const intersectingId = getTileId();
199				intersections[intersectingId] = {
200					tile,
201					coord
202				};
203				const tileIdx = word.coords.findIndex((c) => coordToStr(c) === key);
204				word.word[tileIdx] = {
205					...word.word[tileIdx],
206					id: intersectingId
207				};
208				word.intersectingIds.push(intersectingId);
209			}
210			last.intersectingIds.push(tile.id);
211		}
212		return {
213			words,
214			intersections
215		};
216	},
217	boardHasWord: (board) => {
218		const key = sortLetters(board.flatMap((col) => col.flatMap((t) => [...t.letter.toLowerCase()])).join(""));
219		const q = [[letters.getNode(""), key]];
220		while (q.length) {
221			const [node, key] = q.pop();
222			if (node.terminal && [board.length, board[0].length].includes(node.value.length)) return node.value;
223			for (const [letter, child] of Object.entries(node.children)) {
224				const idx = key.indexOf(letter);
225				if (idx !== -1) q.push([child, key.slice(idx + 1)]);
226			}
227		}
228	},
229	searchRegex: (search, lenRange) => {
230		const suggestions = [];
231		if (search.includes("(")) search = search.replaceAll("(", "");
232		else if (search.includes("]")) search = search.replaceAll("]", "") + "$";
233		else search = "^" + search;
234		let wildSearch = search.replaceAll("*", "[A-Za-z]");
235		if (!/[A-Za-z]/.test(wildSearch)) return [];
236		try {
237			const re = new RegExp(wildSearch, "i");
238			return wordList.filter((w) => re.test(w)).filter((w) => w.length >= lenRange[0] && w.length <= lenRange[1]).sort((a, b) => a.length - b.length).slice(0, 5);
239		} catch (err) {
240			return suggestions;
241		}
242	},
243	searchTrie: (search, lenRange) => {
244		const w = dictionary.getNode(search.toLowerCase());
245		let q = [];
246		let head = w;
247		let suggestions = [];
248		while (head && suggestions.length < 5) {
249			if (head.children) q = q.concat(Object.values(head.children) ?? []);
250			if (head.terminal && head.value && head.value.length >= lenRange[0] && head.value.length <= lenRange[1]) suggestions.push(head.value);
251			head = q.shift();
252		}
253		return suggestions;
254	}
255};
256if (typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope) expose(Dictionary);
257//#endregion
258export { Dictionary };
259
260//# sourceMappingURL=D8x9GurF.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.