import { Dict } from "../dict.ts"; import { GuessResult, Info } from "../game/game.ts"; import { Value } from "../utils.ts"; import { Awaitable, enumerate, range, zip } from "../utils.ts"; import { Guessing } from "./guesser.ts"; export class ReducingGuesser implements Guessing { length; words; possibilities; public constructor(words: Dict) { this.words = words; this.length = words.length; this.possibilities = words.clone(); } public async guess(try_: (guess: string, known: string) => Awaitable) { const guess = this.make_guess(); const result = await try_(guess, ""); if (result.kind === "success") return result; this.learn(guess, result.informations); return null; } make_guess() { const letters = [...this.words.letters]; const letters_ranks = [...range(0, this.length)].map(() => new Map(letters.map((l) => [l, { value: 0 }]))); for (const word of this.possibilities.words) { for (const [index, letter] of enumerate(word)) { letters_ranks[index].get(letter)!.value += 1; } } const candidates = [...this.words.words]; const scored = candidates.map((word) => [word, score_word_from_ranks(word, letters_ranks)] as const); const [best] = scored.reduce((a, b) => a[1] > b[1] ? a : b); return best; } learn(word: string, infos: Info[]) { const to_delete = new Set(); const pos = this.possibilities; for (const [index, [letter, info]] of enumerate(zip(word, infos))) { if (info.kind === "there") for (const word of pos.words) if (word[index] !== letter) to_delete.add(word); if (info.kind === "somewhere") for (const word of pos.words) if (word[index] === letter) to_delete.add(word); if (info.kind === "abscent") for (const word of pos.words) if (word.includes(letter)) to_delete.add(word); } for (const d of to_delete) pos.words.delete(d); } } // note : does not take into account knowledge we already have. function score_word_from_ranks(word: string, ranks: Map>[]) { let result = 0; for (const [index, letter] of enumerate(word)) { // note : bonus for ANY letter. for (const index_ranks of ranks) { const letter_rank = index_ranks.get(letter)!.value; result += letter_rank; } // note : bonus for THIS letter. const index_ranks = ranks[index]; const letter_rank = index_ranks.get(letter)!.value; result += letter_rank; } return result; } /* note : The algorithm must proceed as follow : 1. establish a list of possible words 2. loop : 2.1 for each word, for each letter, estimate by how much this letter cuts possibilities for all three possible outcomes. (2.1-bis weigts those scores by the frequency of the word ?) 2.2 play the word which individual letters cuts most of the possible space. */