From 873bc25ffe8f61eafb0c2a1d7bd7fb3a0fa1d741 Mon Sep 17 00:00:00 2001 From: Ryan Carpenter Date: Sat, 15 Jan 2022 00:32:50 +0800 Subject: [PATCH 1/2] possible-answer-counter: repetitive, 5-letter draft --- src/Game.tsx | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/Game.tsx b/src/Game.tsx index afd848a70..2b17d2742 100644 --- a/src/Game.tsx +++ b/src/Game.tsx @@ -29,13 +29,26 @@ function Game(props: GameProps) { const [guesses, setGuesses] = useState([]); const [currentGuess, setCurrentGuess] = useState(""); const [wordLength, setWordLength] = useState(5); - const [hint, setHint] = useState(`Make your first guess!`); + const [hint, setHint] = useState( + `${targets.length.toLocaleString()} possibilities` + ); const [srStatus, setSrStatus] = useState(``); const [target, setTarget] = useState(() => { resetRng(); return randomTarget(wordLength); }); const [gameNumber, setGameNumber] = useState(1); + const [exclusions, setExclusions] = useState< + Record + >({ + found: ["", "", "", "", ""], + nowhere: [], + 0: [], + 1: [], + 2: [], + 3: [], + 4: [], + }); const startNextGame = () => { setTarget(randomTarget(wordLength)); @@ -85,12 +98,89 @@ function Game(props: GameProps) { ); setGameState(GameState.Lost); } else { - setHint(""); speak(describeClue(clue(currentGuess, target))); + const currentClue = clue(currentGuess, target); + const notFound = currentClue + .filter(({ clue }) => clue === 0) + .filter( + ({ letter }) => + !currentClue.some( + (otherPosition) => + otherPosition.letter === letter && otherPosition.clue + ) + ) + .map(({ letter }) => letter); + + setExclusions({ + found: currentClue.reduce((agg, cur, index) => { + if (cur.clue === 2) agg.splice(index, 1, cur.letter); + return agg; + }, exclusions.found), + nowhere: [...exclusions.nowhere, ...notFound], + 0: + currentClue[0].clue === 1 + ? [...exclusions[0], currentClue[0].letter] + : exclusions[0], + 1: + currentClue[1].clue === 1 + ? [...exclusions[1], currentClue[1].letter] + : exclusions[1], + 2: + currentClue[2].clue === 1 + ? [...exclusions[2], currentClue[2].letter] + : exclusions[2], + 3: + currentClue[3].clue === 1 + ? [...exclusions[3], currentClue[3].letter] + : exclusions[3], + 4: + currentClue[4].clue === 1 + ? [...exclusions[4], currentClue[4].letter] + : exclusions[4], + }); } } }; + useEffect(() => { + setTimeout(() => setHint(`Make your first guess!`), 3000); + }, [target]); + + useEffect(() => { + if (exclusions.nowhere.length === 0) return; + const nowherePattern = new RegExp(`^[^${exclusions.nowhere.join("")}]+$`); + const notHerePattern = new RegExp( + `^${ + exclusions.found[0] || exclusions[0].length + ? `[^${exclusions[0].join("")}]` + : "." + }${ + exclusions.found[1] || exclusions[1].length + ? `[^${exclusions[1].join("")}]` + : "." + }${ + exclusions.found[2] || exclusions[2].length + ? `[^${exclusions[2].join("")}]` + : "." + }${ + exclusions.found[3] || exclusions[3].length + ? `[^${exclusions[3].join("")}]` + : "." + }${ + exclusions.found[4] || exclusions[4].length + ? `[^${exclusions[4].join("")}]` + : "." + }$` + ); + + console.log(exclusions, nowherePattern, notHerePattern); + + const possibilityCount = targets.filter( + (word) => nowherePattern.test(word) && notHerePattern.test(word) + ).length; + setHint(`${possibilityCount.toLocaleString()} possibilities`); + }, [exclusions]); + useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (!e.ctrlKey && !e.metaKey) { From 6781a4caa71fdb71b57c287e93322b1f1585a804 Mon Sep 17 00:00:00 2001 From: Ryan Carpenter Date: Sun, 16 Jan 2022 22:42:59 +0800 Subject: [PATCH 2/2] possible-answer-counter: generalized --- src/Game.tsx | 150 ++++++++++++++++++++++----------------------------- src/util.ts | 11 ++++ 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/src/Game.tsx b/src/Game.tsx index 2b17d2742..f361f3dc2 100644 --- a/src/Game.tsx +++ b/src/Game.tsx @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, ChangeEvent } from "react"; import { Row, RowState } from "./Row"; import dictionary from "./dictionary.json"; import { Clue, clue, describeClue } from "./clue"; import { Keyboard } from "./Keyboard"; import targetList from "./targets.json"; -import { dictionarySet, pick, resetRng, seed, speak } from "./util"; +import { dictionarySet, initExclusions, pick, resetRng, seed, speak } from "./util"; enum GameState { Playing, @@ -30,7 +30,9 @@ function Game(props: GameProps) { const [currentGuess, setCurrentGuess] = useState(""); const [wordLength, setWordLength] = useState(5); const [hint, setHint] = useState( - `${targets.length.toLocaleString()} possibilities` + `${targets + .filter(({ length }) => length === wordLength) + .length.toLocaleString()} possibilities` ); const [srStatus, setSrStatus] = useState(``); const [target, setTarget] = useState(() => { @@ -39,16 +41,8 @@ function Game(props: GameProps) { }); const [gameNumber, setGameNumber] = useState(1); const [exclusions, setExclusions] = useState< - Record - >({ - found: ["", "", "", "", ""], - nowhere: [], - 0: [], - 1: [], - 2: [], - 3: [], - 4: [], - }); + Record<"found" | "nowhere" | number, string[]> + >(initExclusions(wordLength)); const startNextGame = () => { setTarget(randomTarget(wordLength)); @@ -57,6 +51,7 @@ function Game(props: GameProps) { setHint(""); setGameState(GameState.Playing); setGameNumber((x) => x + 1); + setExclusions(initExclusions(wordLength)); }; const onKey = (key: string) => { @@ -67,15 +62,11 @@ function Game(props: GameProps) { return; } if (guesses.length === props.maxGuesses) return; - if (/^[a-z]$/i.test(key)) { - setCurrentGuess((guess) => - (guess + key.toLowerCase()).slice(0, wordLength) - ); - setHint(""); + if (/^[a-z]$/.test(key)) { + setCurrentGuess((guess) => (guess + key).slice(0, wordLength)); setSrStatus(""); } else if (key === "Backspace") { setCurrentGuess((guess) => guess.slice(0, -1)); - setHint(""); } else if (key === "Enter") { if (currentGuess.length !== wordLength) { setHint("Too short"); @@ -111,33 +102,22 @@ function Game(props: GameProps) { ) .map(({ letter }) => letter); - setExclusions({ - found: currentClue.reduce((agg, cur, index) => { - if (cur.clue === 2) agg.splice(index, 1, cur.letter); - return agg; - }, exclusions.found), - nowhere: [...exclusions.nowhere, ...notFound], - 0: - currentClue[0].clue === 1 - ? [...exclusions[0], currentClue[0].letter] - : exclusions[0], - 1: - currentClue[1].clue === 1 - ? [...exclusions[1], currentClue[1].letter] - : exclusions[1], - 2: - currentClue[2].clue === 1 - ? [...exclusions[2], currentClue[2].letter] - : exclusions[2], - 3: - currentClue[3].clue === 1 - ? [...exclusions[3], currentClue[3].letter] - : exclusions[3], - 4: - currentClue[4].clue === 1 - ? [...exclusions[4], currentClue[4].letter] - : exclusions[4], - }); + setExclusions( + currentClue.reduce( + (agg, { letter, clue }, index) => ({ + ...agg, + [index]: + clue === 1 ? [...exclusions[index], letter] : exclusions[index], + }), + { + found: currentClue.reduce((agg, cur, index) => { + if (cur.clue === 2) agg.splice(index, 1, cur.letter); + return agg; + }, exclusions.found), + nowhere: [...exclusions.nowhere, ...notFound], + } + ) + ); } } }; @@ -148,37 +128,32 @@ function Game(props: GameProps) { useEffect(() => { if (exclusions.nowhere.length === 0) return; - const nowherePattern = new RegExp(`^[^${exclusions.nowhere.join("")}]+$`); - const notHerePattern = new RegExp( - `^${ - exclusions.found[0] || exclusions[0].length - ? `[^${exclusions[0].join("")}]` - : "." - }${ - exclusions.found[1] || exclusions[1].length - ? `[^${exclusions[1].join("")}]` - : "." - }${ - exclusions.found[2] || exclusions[2].length - ? `[^${exclusions[2].join("")}]` - : "." - }${ - exclusions.found[3] || exclusions[3].length - ? `[^${exclusions[3].join("")}]` - : "." - }${ - exclusions.found[4] || exclusions[4].length - ? `[^${exclusions[4].join("")}]` - : "." - }$` - ); - console.log(exclusions, nowherePattern, notHerePattern); + const { found, nowhere, ...rest } = exclusions; + const nowherePattern = `(?=^[^${exclusions.nowhere.join("")}]+$)`; + const somewherePattern = Object.values(rest) + .reduce((agg: string[], cur: string[]) => [...agg, ...cur], []) + .filter( + (letter: string, index: number, array: string[]) => + array.indexOf(letter) === index && !found.includes(letter) + ) + .map((letter: string) => `(?=.*${letter})`) + .join(""); + const byPositionPattern = `(?=^${exclusions.found + .map((foundLetter, index) => { + return ( + foundLetter || + (exclusions[index].length ? `[^${exclusions[index].join("")}]` : ".") + ); + }) + .join("")}$)`; - const possibilityCount = targets.filter( - (word) => nowherePattern.test(word) && notHerePattern.test(word) - ).length; - setHint(`${possibilityCount.toLocaleString()} possibilities`); + const re = new RegExp( + [somewherePattern, nowherePattern, byPositionPattern].join("") + ); + const possibilities = targets.filter((word) => re.test(word)); + setHint(`${possibilities.length.toLocaleString()} possibilities`); + console.log({ exclusions, possibilities }); }, [exclusions]); useEffect(() => { @@ -228,6 +203,19 @@ function Game(props: GameProps) { ); }); + const handleWordLengthChange = (e: ChangeEvent) => { + const length = Number(e.target.value); + resetRng(); + setGameNumber(1); + setGameState(GameState.Playing); + setGuesses([]); + setTarget(randomTarget(length)); + setWordLength(length); + setHint(`${length} letters`); + setExclusions(initExclusions(length)); + (document.activeElement as HTMLElement)?.blur(); + }; + return (
@@ -242,17 +230,7 @@ function Game(props: GameProps) { (guesses.length > 0 || currentGuess !== "") } value={wordLength} - onChange={(e) => { - const length = Number(e.target.value); - resetRng(); - setGameNumber(1); - setGameState(GameState.Playing); - setGuesses([]); - setCurrentGuess(""); - setTarget(randomTarget(length)); - setWordLength(length); - setHint(`${length} letters`); - }} + onChange={handleWordLengthChange} >