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()
}
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();
}

View file

@ -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;
}

View file

@ -55,6 +55,7 @@ export function Game(props: GameProps) {
);
const tableRef = useRef<HTMLTableElement>(null);
const game = useMemo(() => wasm.Game.new(COMMON_WORDS, puzzle.history), []);
const [gameTree, setGameTree] = useState<GuessResult[][]>();
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,6 +265,7 @@ export function Game(props: GameProps) {
/>
<p>
{gameState === GameState.Won && (
<div class="win-buttons">
<button
onClick={() => {
const emoji = props.colorBlind
@ -286,8 +288,33 @@ export function Game(props: GameProps) {
>
Share results
</button>
<button onClick={() => setGameTree(game.explore_game_tree())}>
Explore game tree
</button>
</div>
)}
</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>
Today's puzzle (#{puzzle.seed}) has {puzzle.solutions} path{puzzle.solutions === 1 ? '' : 's'} to a win.
</p>

View file

@ -47,13 +47,11 @@ impl<const N: usize> AdversarialEvaluator<N> {
pub fn possible_word(&self) -> Option<&Word<N>> {
self.possible_words.first()
}
}
impl<const N: usize> Evaluator<N> for AdversarialEvaluator<N> {
fn evaluate(&self, word: Word<N>) -> Matches<N> {
pub fn best_secrets(&self, word: Word<N>) -> Vec<Word<N>> {
let mut evaluated_matches: HashSet<Matches<N>> = HashSet::new();
let mut best_secret: Option<Word<N>> = None;
let mut best_possible_words_count: Option<usize> = None;
let mut best_secrets: Vec<Word<N>> = Vec::new();
let mut best_possible_words_count: usize = 0;
let mut resulting_constraints = constraints::Total::new();
for possible_secret in &self.possible_words {
@ -72,13 +70,24 @@ impl<const N: usize> Evaluator<N> for AdversarialEvaluator<N> {
.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<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>) {