add "Explore game tree"
This commit is contained in:
parent
2f09571673
commit
26135d641f
4 changed files with 105 additions and 35 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
Loading…
Reference in a new issue