From 71fbf9c2643fdd75e9feef8e60b7f4122234f482 Mon Sep 17 00:00:00 2001 From: Josh Parker Date: Sun, 10 Mar 2024 12:19:15 -0600 Subject: [PATCH] add the UI for the basic game mode (no fake letters, no hidden letters) --- gmw/cheat-wos.user.js | 408 +++++++++++++++++++++++------------------- 1 file changed, 227 insertions(+), 181 deletions(-) diff --git a/gmw/cheat-wos.user.js b/gmw/cheat-wos.user.js index 1fbaf80..9be5e2a 100644 --- a/gmw/cheat-wos.user.js +++ b/gmw/cheat-wos.user.js @@ -1,210 +1,256 @@ -const pointValues = { - a: 1, - b: 3, - c: 3, - d: 2, - e: 1, - f: 4, - g: 2, - h: 4, - i: 1, - j: 8, - k: 5, - l: 1, - m: 3, - n: 1, - o: 1, - p: 3, - q: 10, - r: 1, - s: 1, - t: 1, - u: 1, - v: 4, - w: 4, - x: 8, - y: 4, - z: 10, -}; - -const score = (w) => [...w].reduce((acc, c) => acc + pointValues[c], 0); - -const getDict = (prefixTreeRoot) => { - const dict = []; - const getDictHelper = (prefixTreeNode, prefix) => { - if (prefixTreeNode === '') return; - - if ('' in prefixTreeNode) { - dict.push(prefix); - } - - Object.keys(prefixTreeNode).forEach((letter) => { - getDictHelper(prefixTreeNode[letter], prefix + letter); - }); +// ==UserScript== +// @name Cheat on Words on Stream +// @namespace http://tampermonkey.net/ +// @version 2024-03-10 +// @description Pop out instant anagram solutions +// @author Josh Parker +// @match https://hryanjones.com/guess-my-word/ +// @icon https://www.google.com/s2/favicons?sz=64&domain=hryanjones.com +// @grant none +// ==/UserScript== + +(function cheatWos() { + console.log('about to cheat'); + const fakeWords = new Set(JSON.parse(localStorage.getItem('wos-fake-words'))); + + const abc = 'abcdefghijklmnopqrstuvwxyz'; + const pointValues = { + a: 1, + b: 3, + c: 3, + d: 2, + e: 1, + f: 4, + g: 2, + h: 4, + i: 1, + j: 8, + k: 5, + l: 1, + m: 3, + n: 1, + o: 1, + p: 3, + q: 10, + r: 1, + s: 1, + t: 1, + u: 1, + v: 4, + w: 4, + x: 8, + y: 4, + z: 10, }; - getDictHelper(prefixTreeRoot, ''); - return dict; -}; + const score = (w) => [...w].reduce((acc, c) => acc + pointValues[c], 0); -const abc = 'abcdefghijklmnopqrstuvwxyz'; + /* const makePrefixTree = (dictArray) => { + const tree = {}; -// eslint-disable-next-line no-undef -const dict = getDict(validWordTrie); + const addChild = (node, c) => { + if (!(c in node)) { + // eslint-disable-next-line no-param-reassign + node[c] = {}; + } -// prefilter the dict by word length so that this filtered list won't have to be computed again -const dicts = new Array(16).fill().map((_, i) => dict.filter((w) => w.length === i)); + return node[c]; + }; -const makePrefixTree = (dictArray) => { - const tree = {}; + dictArray.forEach((w) => { + let node = tree; + [...w].forEach((c) => { + node = addChild(node, c); + }); - const addChild = (node, c) => { - if (!(c in node)) { - // eslint-disable-next-line no-param-reassign - node[c] = {}; - } + // an empty string child signals the end of a valid word + addChild(node, ''); + }); - return node[c]; + return tree; }; +*/ + const abcObj = {}; - dictArray.forEach((w) => { - let node = tree; - [...w].forEach((c) => { - node = addChild(node, c); - }); - - // an empty string child signals the end of a valid word - addChild(node, ''); + [...abc].forEach((c) => { + abcObj[c] = 0; }); - return tree; -}; + const abcO = JSON.stringify(abcObj); -const dictTrees = dicts.map(makePrefixTree); + const getEm = (scramble) => { + const abcTemp = JSON.parse(abcO); + [...scramble].forEach((c) => { + abcTemp[c] += 1; + }); -const abcObj = {}; + return abcTemp; + }; + // eslint-disable-next-line no-undef + const solve = (scramble, prefixTreeRoot = validWordTrie) => { + const abcObjMem = getEm(scramble); -[...abc].forEach((c) => { - abcObj[c] = 0; -}); + const solutions = []; -const abcO = JSON.stringify(abcObj); + const solveHelper = (prefixTreeNode, prefix = '') => { + if (prefixTreeNode === '') return; -const getEm = (scramble) => { - const abcTemp = JSON.parse(abcO); - [...scramble].forEach((c) => { - abcTemp[c] += 1; - }); + if ('' in prefixTreeNode) solutions.push(prefix); - return abcTemp; -}; + Object.keys(prefixTreeNode).forEach((letter) => { + if (abcObjMem[letter] > 0) { + abcObjMem[letter] -= 1; -const makeSolve = (wildCards) => (scramble, length, prefixTreeRoot = dictTrees[length]) => { - const abcObjMem = getEm(scramble); + solveHelper(prefixTreeNode[letter], prefix + letter); - const solutions = []; + abcObjMem[letter] += 1; + } + }); + }; - const solveHelper = (prefixTreeNode, prefix = '') => { - if (prefixTreeNode === '') return; + solveHelper(prefixTreeRoot); + return solutions; + }; + /* + const fake = (scramble) => ( + [...scramble].map((_, i) => [...scramble].slice(0, i).concat([...scramble].slice(i + 1))) + ); + + // eslint-disable-next-line no-undef + const solveFake = (scramble, prefixTreeRoot = validWordTrie) => { + const preDict = makePrefixTree(solve(scramble, prefixTreeRoot)); + return fake(scramble).map((e) => ( + solve(e, preDict).sort((a, b) => score(b) - score(a)) + )).filter((solution) => solution[0]?.length === scramble.length - 1); + }; - if ('' in prefixTreeNode) solutions.push(prefix); + const solveHidden = (scramble) => ( + [...abc].map((c) => solveFake(scramble + c)) + ); - Object.keys(prefixTreeNode).forEach((letter) => { - if (abcObjMem[letter] > 0) { - abcObjMem[letter] -= 1; + const solveAndFilterHidden = (scramble, targetCount) => { + const solutions = solveHidden(scramble); + const sufficientSolutions = solutions.flat().filter((solution) => solution.length >= targetCount); + const dupes = new Set(); - solveHelper(prefixTreeNode[letter], prefix + letter); + const dedupedSolutions = sufficientSolutions.filter((solution) => solution.every((word) => { + const seen = dupes.has(word); + dupes.add(word); + return !seen; + })); - abcObjMem[letter] += 1; - } else if (wildCards > 0) { - // eslint-disable-next-line no-param-reassign - wildCards -= 1; + return dedupedSolutions; + }; - solveHelper(prefixTreeNode[letter], prefix + letter); + const solveAndDisplayHidden = (scramble, targetCount, otherTargets) => { + const dedupedSolutions = solveAndFilterHidden(scramble, targetCount); + const fullSolutions = dedupedSolutions.map((solution) => ( + otherTargets.map((target) => ( + solve(solution[0], target[0]) + )).concat(solution) + )).filter((solution) => ( + solution.every((otherSolution, i) => ( + (i >= otherTargets.length) + || (otherSolution.length >= otherTargets[i][1]) + )) + )); + + const displaySolutions = fullSolutions.map((solution) => ( + solution.flat().map((word) => `${word} ${score(word)}`).join(' ') + )); + + return displaySolutions.sort((a, b) => a.length - b.length); + }; - // eslint-disable-next-line no-param-reassign - wildCards += 1; - } - }); + const display = (solutions) => ( + JSON.stringify(solutions.filter((w) => w.length > 3).map((w) => ( + `${w} ${score(w)}` + )).sort((a, b) => ( + Number(b.match(/\d+/)) - Number(a.match(/\d+/)) + )))); + + const solveHiddenFakeX2 = (scramble) => { + const doubleFakes = new Set(fake(scramble).map((faked) => fake(faked)).flat()); + return [...doubleFakes].map((e) => [...abc].map((c) => solve(e + c))); }; +*/ + // create the UI + + let minimumWordLength = 4; + const minimumWordLengthHTML = ` +Minimum word length: + + + + +`; + + const wosForm = document.createElement('div'); + wosForm.id = 'wos'; + + const minimumWordLengthFieldset = document.createElement('fieldset'); + minimumWordLengthFieldset.id = 'minimum-word-length'; + minimumWordLengthFieldset.addEventListener('change', ({ target: { value: minWordLength } }) => { + minimumWordLength = minWordLength; + }); + + wosForm.innerHTML = minimumWordLengthHTML; + + const solutions = document.createElement('div'); + solutions.id = 'wos-solutions'; + const scrambleInput = document.createElement('input'); + scrambleInput.addEventListener('change', ({ target: { value: scramble } }) => { + console.log(`got scramble: ${scramble}`); + solutions.innerHTML = ''; + solve(scramble) + .filter((w) => w.length >= minimumWordLength) + .filter((w) => !fakeWords.has(w)) + .sort((a, b) => a.length - b.length) + .forEach((word) => { + const solution = document.createElement('div'); + solution.appendChild(new Text(`${word} ${score(word)}`)); + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.name = 'not-in-dictionary'; + checkbox.value = word; + solution.appendChild(checkbox); + + solutions.appendChild(solution); + }); + }); + + wosForm.appendChild(scrambleInput); + document.body.appendChild(wosForm); + document.body.appendChild(solutions); + + const style = document.createElement('style'); + style.appendChild(new Text(` +div#wos-solutions { + display: flex; + flex-direction: column; + flex-wrap: wrap; + height: 400px; +} + +div#wos-solutions > div { + border: solid black 1px; + margin: 2px; + padding: 4px; + border-radius: 6px; +} + `)); + + document.body.appendChild(style); + + const fakeWordButton = document.createElement('button'); + fakeWordButton.id = 'fake-word'; + fakeWordButton.appendChild(new Text('update dictionary')); + fakeWordButton.addEventListener('click', () => { + const words = [...document.querySelectorAll('input[type=checkbox')].filter((e) => e.checked).map((e) => e.value); + words.forEach((word) => fakeWords.add(word)); + localStorage.setItem('wos-fake-words', JSON.stringify([...fakeWords])); + }); - solveHelper(prefixTreeRoot); - return solutions; -}; - -const solve = makeSolve(0); - -const fake = (scramble) => ( - [...scramble].map((_, i) => [...scramble].slice(0, i).concat([...scramble].slice(i + 1))) -); - -const solveFake = (scramble, length, prefixTreeRoot = dictTrees[length]) => { - const preDict = makePrefixTree(solve(scramble, length, prefixTreeRoot)); - return fake(scramble).map((e) => solve(e, length, preDict)); -}; - -const preSolve = makeSolve(1); - -const solveHidden = (scramble, length = scramble.length) => { - const preDict = makePrefixTree(preSolve(scramble, length)); - return [...abc].map((c) => solveFake(scramble + c, length, preDict)); -}; - -const solveAndFilterHidden = (scramble, length, targetCount) => { - const solutions = solveHidden(scramble, length); - const sufficientSolutions = solutions.flat().filter((solution) => solution.length >= targetCount); - const dupes = new Set(); - - const dedupedSolutions = sufficientSolutions.filter((solution) => solution.every((word) => { - const seen = dupes.has(word); - dupes.add(word); - return !seen; - })); - - return dedupedSolutions; -}; - -const solveAndDisplayHidden = (scramble, length, targetCount, otherTargets) => { - const dedupedSolutions = solveAndFilterHidden(scramble, length, targetCount); - const fullSolutions = dedupedSolutions.map((solution) => ( - otherTargets.map((target) => ( - solve(solution[0], target[0]) - )).concat(solution) - )).filter((solution) => ( - solution.every((otherSolution, i) => ( - (i >= otherTargets.length) - || (otherSolution.length >= otherTargets[i][1]) - )) - )); - - const displaySolutions = fullSolutions.map((solution) => ( - solution.flat().map((word) => `${word} ${score(word)}`).join(' ') - )); - - return displaySolutions.sort((a, b) => a.length - b.length); -}; - -// Next: Solve for two fake and one hidden. - -const display = (solutions) => ( - JSON.stringify(solutions.map((w) => ( - `${w} ${score(w)}` - )).sort((a, b) => ( - Number(b.match(/\d+/)) - Number(a.match(/\d+/)) - )))); - -const solveHiddenFakeX2 = (scramble, length = scramble.length - 1) => { - const preDict = makePrefixTree(preSolve(scramble, length)); - const doubleFakes = new Set(fake(scramble).map((faked) => fake(faked)).flat()); - return [...doubleFakes].map((e) => [...abc].map((c) => solve(e + c, length, preDict))); -}; - -export default { - solve, - solveFake, - solveHidden, - display, - solveAndFilterHidden, - solveAndDisplayHidden, - solveHiddenFakeX2, -}; + document.body.appendChild(fakeWordButton); +}());