add "Explore game tree"

This commit is contained in:
ashastral 2022-03-01 21:41:09 -08:00
parent 2f09571673
commit 26135d641f
4 changed files with 105 additions and 35 deletions

View file

@ -124,6 +124,32 @@ impl Game {
.collect() .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) { pub fn reset(&mut self) {
self.current_attempt = Vec::new(); self.current_attempt = Vec::new();
} }

View file

@ -294,3 +294,11 @@ a:active {
.App-container.color-blind .App-about b.yellow-bg { .App-container.color-blind .App-about b.yellow-bg {
background-color: #85c0f9; background-color: #85c0f9;
} }
.win-buttons button {
margin: 0 0.5em;
}
.game-tree-option {
margin-bottom: 0.5em;
}

View file

@ -55,6 +55,7 @@ export function Game(props: GameProps) {
); );
const tableRef = useRef<HTMLTableElement>(null); const tableRef = useRef<HTMLTableElement>(null);
const game = useMemo(() => wasm.Game.new(COMMON_WORDS, puzzle.history), []); const game = useMemo(() => wasm.Game.new(COMMON_WORDS, puzzle.history), []);
const [gameTree, setGameTree] = useState<GuessResult[][]>();
const reset = useCallback(() => { const reset = useCallback(() => {
game.reset(); game.reset();
setGuesses([...puzzle.history]); setGuesses([...puzzle.history]);
@ -78,6 +79,9 @@ export function Game(props: GameProps) {
...puzzle.history, ...puzzle.history,
...storedTodayStats.winningGuesses, ...storedTodayStats.winningGuesses,
]); ]);
storedTodayStats.winningGuesses.forEach(
(result) => game.guess(result.word)
);
} }
} }
} }
@ -151,9 +155,6 @@ export function Game(props: GameProps) {
setHint("Not a valid word"); setHint("Not a valid word");
return; return;
} }
for (const g of guesses) {
const c = clue(g);
}
const matches: number[] = game.guess(currentGuess); const matches: number[] = game.guess(currentGuess);
const result: GuessResult = { const result: GuessResult = {
word: currentGuess, word: currentGuess,
@ -264,30 +265,56 @@ export function Game(props: GameProps) {
/> />
<p> <p>
{gameState === GameState.Won && ( {gameState === GameState.Won && (
<button <div class="win-buttons">
onClick={() => { <button
const emoji = props.colorBlind onClick={() => {
? ["⬛", "🟦", "🟧"] const emoji = props.colorBlind
: ["⬛", "🟨", "🟩"]; ? ["⬛", "🟦", "🟧"]
const score = todayStats.tries === 1 ? "my first try!" : `try #${todayStats.tries}!`; : ["⬛", "🟨", "🟩"];
share( const score = todayStats.tries === 1 ? "my first try!" : `try #${todayStats.tries}!`;
"Result copied to clipboard!", share(
`I solved ${gameName} #${puzzle.seed} on ${score}\n` + "Result copied to clipboard!",
guesses `I solved ${gameName} #${puzzle.seed} on ${score}\n` +
.slice(puzzle.history.length) guesses
.map((result) => .slice(puzzle.history.length)
clue(result) .map((result) =>
.map((c) => emoji[c.clue ?? 0]) clue(result)
.join("") .map((c) => emoji[c.clue ?? 0])
) .join("")
.join("\n") )
); .join("\n")
}} );
> }}
Share results >
</button> Share results
</button>
<button onClick={() => setGameTree(game.explore_game_tree())}>
Explore game tree
</button>
</div>
)} )}
</p> </p>
{gameTree && (
<div className="game-tree">
<p>Here are all the ways the game could've responded:</p>
{gameTree?.map((option) =>
<div className="game-tree-option">
{option.map((guessResult, i) => {
const cluedLetters = clue(guessResult);
return (
<Row
key={i}
wordLength={WORD_LENGTH}
rowState={RowState.LockedIn}
cluedLetters={cluedLetters}
isPartOfPuzzle={false}
/>
);
})}
</div>
)}
</div>
)}
<p> <p>
Today's puzzle (#{puzzle.seed}) has {puzzle.solutions} path{puzzle.solutions === 1 ? '' : 's'} to a win. Today's puzzle (#{puzzle.seed}) has {puzzle.solutions} path{puzzle.solutions === 1 ? '' : 's'} to a win.
</p> </p>

View file

@ -47,13 +47,11 @@ impl<const N: usize> AdversarialEvaluator<N> {
pub fn possible_word(&self) -> Option<&Word<N>> { pub fn possible_word(&self) -> Option<&Word<N>> {
self.possible_words.first() self.possible_words.first()
} }
}
impl<const N: usize> Evaluator<N> for AdversarialEvaluator<N> { pub fn best_secrets(&self, word: Word<N>) -> Vec<Word<N>> {
fn evaluate(&self, word: Word<N>) -> Matches<N> {
let mut evaluated_matches: HashSet<Matches<N>> = HashSet::new(); let mut evaluated_matches: HashSet<Matches<N>> = HashSet::new();
let mut best_secret: Option<Word<N>> = None; let mut best_secrets: Vec<Word<N>> = Vec::new();
let mut best_possible_words_count: Option<usize> = None; let mut best_possible_words_count: usize = 0;
let mut resulting_constraints = constraints::Total::new(); let mut resulting_constraints = constraints::Total::new();
for possible_secret in &self.possible_words { for possible_secret in &self.possible_words {
@ -71,14 +69,25 @@ impl<const N: usize> Evaluator<N> for AdversarialEvaluator<N> {
.filter(|&word| resulting_constraints.check(*word)) .filter(|&word| resulting_constraints.check(*word))
.count() .count()
}; };
if best_possible_words_count.is_none() || possible_words_count > best_possible_words_count.unwrap() { if possible_words_count > best_possible_words_count {
best_possible_words_count = Some(possible_words_count); best_possible_words_count = possible_words_count;
best_secret = Some(*possible_secret); 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<const N: usize> Evaluator<N> for AdversarialEvaluator<N> {
fn evaluate(&self, word: Word<N>) -> Matches<N> {
let best_secrets = self.best_secrets(word);
return guess(&word, best_secrets.first().unwrap());
} }
fn push(&mut self, result: GuessResult<N>) { fn push(&mut self, result: GuessResult<N>) {