diff --git a/web-optimle/src/lib.rs b/web-optimle/src/lib.rs index 5365776..ccb4fe2 100644 --- a/web-optimle/src/lib.rs +++ b/web-optimle/src/lib.rs @@ -124,6 +124,32 @@ impl Game { .collect() } + pub fn explore_game_tree(&self) -> JsValue { + let mut adversary = evaluator::AdversarialEvaluator::new(&self.dictionary); + for result in &self.initial_history { + adversary.push(guess::GuessResult { + word: word!(result.word), + matches: matches_to_wordlelike!(result.matches), + }); + } + let first_word = &self.current_attempt.first().unwrap().word; + let secrets = adversary.best_secrets(word![first_word]); + let mut game_tree = Vec::new(); + for secret in secrets { + game_tree.push(vec![ + GuessResult { + word: first_word.clone(), + matches: matches_from_wordlelike![guess::guess(word![first_word], &secret)], + }, + GuessResult { + word: secret.iter().collect(), + matches: vec![2,2,2,2,2], + } + ]); + } + serde_wasm_bindgen::to_value(&game_tree).unwrap() + } + pub fn reset(&mut self) { self.current_attempt = Vec::new(); } diff --git a/web-optimle/www/src/App.css b/web-optimle/www/src/App.css index feb2de3..edf8bd5 100644 --- a/web-optimle/www/src/App.css +++ b/web-optimle/www/src/App.css @@ -294,3 +294,11 @@ a:active { .App-container.color-blind .App-about b.yellow-bg { background-color: #85c0f9; } + +.win-buttons button { + margin: 0 0.5em; +} + +.game-tree-option { + margin-bottom: 0.5em; +} \ No newline at end of file diff --git a/web-optimle/www/src/Game.tsx b/web-optimle/www/src/Game.tsx index b3b9c15..9b82637 100644 --- a/web-optimle/www/src/Game.tsx +++ b/web-optimle/www/src/Game.tsx @@ -55,6 +55,7 @@ export function Game(props: GameProps) { ); const tableRef = useRef(null); const game = useMemo(() => wasm.Game.new(COMMON_WORDS, puzzle.history), []); + const [gameTree, setGameTree] = useState(); const reset = useCallback(() => { game.reset(); setGuesses([...puzzle.history]); @@ -78,6 +79,9 @@ export function Game(props: GameProps) { ...puzzle.history, ...storedTodayStats.winningGuesses, ]); + storedTodayStats.winningGuesses.forEach( + (result) => game.guess(result.word) + ); } } } @@ -151,9 +155,6 @@ export function Game(props: GameProps) { setHint("Not a valid word"); return; } - for (const g of guesses) { - const c = clue(g); - } const matches: number[] = game.guess(currentGuess); const result: GuessResult = { word: currentGuess, @@ -264,30 +265,56 @@ export function Game(props: GameProps) { />

{gameState === GameState.Won && ( - +

+ + +
)}

+ {gameTree && ( +
+

Here are all the ways the game could've responded:

+ {gameTree?.map((option) => +
+ {option.map((guessResult, i) => { + const cluedLetters = clue(guessResult); + return ( + + ); + })} +
+ )} +
+ )}

Today's puzzle (#{puzzle.seed}) has {puzzle.solutions} path{puzzle.solutions === 1 ? '' : 's'} to a win.

diff --git a/wordlelike/src/evaluator.rs b/wordlelike/src/evaluator.rs index 85ca399..d632aa6 100644 --- a/wordlelike/src/evaluator.rs +++ b/wordlelike/src/evaluator.rs @@ -47,13 +47,11 @@ impl AdversarialEvaluator { pub fn possible_word(&self) -> Option<&Word> { self.possible_words.first() } -} -impl Evaluator for AdversarialEvaluator { - fn evaluate(&self, word: Word) -> Matches { + pub fn best_secrets(&self, word: Word) -> Vec> { let mut evaluated_matches: HashSet> = HashSet::new(); - let mut best_secret: Option> = None; - let mut best_possible_words_count: Option = None; + let mut best_secrets: Vec> = Vec::new(); + let mut best_possible_words_count: usize = 0; let mut resulting_constraints = constraints::Total::new(); for possible_secret in &self.possible_words { @@ -71,14 +69,25 @@ impl Evaluator for AdversarialEvaluator { .filter(|&word| resulting_constraints.check(*word)) .count() }; - - if best_possible_words_count.is_none() || possible_words_count > best_possible_words_count.unwrap() { - best_possible_words_count = Some(possible_words_count); - best_secret = Some(*possible_secret); + + if possible_words_count > best_possible_words_count { + best_possible_words_count = possible_words_count; + best_secrets.clear(); + } + + if possible_words_count == best_possible_words_count { + best_secrets.push(*possible_secret); } } - return guess(&word, &best_secret.unwrap()); + best_secrets + } +} + +impl Evaluator for AdversarialEvaluator { + fn evaluate(&self, word: Word) -> Matches { + let best_secrets = self.best_secrets(word); + return guess(&word, best_secrets.first().unwrap()); } fn push(&mut self, result: GuessResult) {