diff --git a/main.ts b/main.ts deleted file mode 100755 index 3e206ee..0000000 --- a/main.ts +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env -S deno run --allow-read - -import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; - -async function main() { - const length = 6; - const dict = await Dict.from_file("./data/liste_francais.txt", length); - console.log(dict); - const history = new History(dict); - const game = Game.from_dict_rand(dict); - console.log("Target is", game.word); - let index = 0; - while (true) { - index += 1; - const found = await history.next_guess((guess) => { - console.log("Guessing", guess); - const result = game.try_guess(guess); - console.log(format_result(result)); - return result; - }); - if (found !== undefined) { - console.log("Found", found, "in", index); - break; - } - await wait(500); - } -} - -type GuessResult = { kind: "success" } | { kind: "failure"; informations: Info[] }; - -function format_result(result: GuessResult) { - if (result.kind === "success") return `success`; - let line = "failure: "; - for (const info of result.informations) line += info.kind === "abscent" ? "-" : info.kind === "somewhere" ? "+" : "#"; - return line; -} - -class Dict { - words; - letters; - length; - - constructor(words: Set, length: number) { - this.words = words; - this.length = length; - this.letters = new Set([...words.values()].map((w) => w.split("")).flat()); - } - - static async from_file(path: string, length: number) { - const words = new Set(); - const content = await Deno.readTextFile(path); - for (const word of content.split("\n")) { - const word_ = word.trim().toLowerCase(); - if (word_.length !== length) continue; - for (const forbidden of [" ", "-", "."]) if (word_.includes(forbidden)) continue; - words.add(remove_accent(word_)); - } - return new Dict(words, length); - } - - [Symbol.for("Deno.customInspect")]() { - return `Dict { ${this.words.size} words }`; - } -} - -type Knowledge = { - letter: string; - at: Set; - not_at: Set; -}; - -class History { - length; - dict; - informations; - - constructor(dict: Dict) { - this.length = dict.length; - this.dict = dict; - this.informations = new Map(); - for (const letter of dict.letters.values()) { - this.informations.set(letter, { letter, at: new Set(), not_at: new Set() }); - } - } - - async next_guess(operation: (guess: string) => GuessResult | Promise) { - const res = this.expected_result(); - console.log("knows: ", res.known); - if (res.completed) return operation(res.known); - - const words = [...this.dict.words.values()]; - const scored = words.map((word) => [this.info_for(word), word] as const); - const sorted = scored.toSorted(([sa], [sb]) => sb - sa); - const [_score, guess] = sorted[0]; - - const result = await operation(guess); - if (result.kind === "success") return guess; - - for (const [letter, new_info] of zip(guess, result.informations)) { - // - } - } - - learn_letter_at(letter: string, at: number) { - const letter_info = this.informations.get(letter); - assertExists(letter_info); - letter_info.at.add(at); - for (const l of this.informations.keys()) { - if (letter === l) continue; - this.learn_letter_not_at(l, at); - } - } - - learn_letter_not_at(letter: string, at: number) { - const letter_info = this.informations.get(letter); - assertExists(letter_info); - letter_info.not_at.add(at); - } - - info_for(word: string) { - let total = 0; - for (const [index, letter] of enumerate(word)) { - const information = this.informations.get(letter); - if (information === undefined) { - total += 1; - continue; - } - - if () - - if (information.kind === "abscent") { - continue; - } - - if (information.kind === "present") { - const known_pos = information.not_at.has(index); - if (!known_pos) total += this.length; // note : not 1 to priorize locating known letters. - continue; - } - } - return total; - } - - expected_result() { - const known_arr = [...range(0, this.length)].map(() => null as string | null); - for (const [letter, info] of this.informations.entries()) { - if (info.kind !== "known") continue; - for (const i of info.at.values()) known_arr[i] = letter; - } - - const known = known_arr.map((l) => l === null ? "." : l).join(""); - if (known.includes(".")) return { completed: false as const, known }; - const guess = known_arr as string[]; - return { completed: true as const, known, guess }; - } -} - -type Info = { kind: "abscent" } | { kind: "somewhere" } | { kind: "there" }; - -function remove_accent(text: string) { - const accents = [ - ["à", "a"], - ["â", "a"], - ["ä", "a"], - ["é", "e"], - ["è", "e"], - ["ê", "e"], - ["ë", "e"], - ["î", "i"], - ["ï", "i"], - ["ô", "o"], - ["ö", "o"], - ["û", "u"], - ]; - let result = text; - for (const [accent, alternative] of accents) result = result.replaceAll(accent, alternative); - return result; -} - -function* enumerate(iterator: Iterable) { - let index = 0; - for (const item of iterator) yield [index++, item] as const; -} - -function* zip(iterable_a: Iterable, iterable_b: Iterable) { - const iter_a = iterable_a[Symbol.iterator](); - const iter_b = iterable_b[Symbol.iterator](); - while (true) { - const next_a = iter_a.next().value; - const next_b = iter_b.next().value; - if (next_a === undefined) return; - if (next_b === undefined) return; - yield [next_a as A, next_b as B] as const; - } -} - -class Game { - word; - - constructor(word: string) { - this.word = word; - } - - static from_dict_rand(dict: Dict) { - const index = Math.floor(Math.random() * dict.words.size); - const word = [...dict.words.values()][index]; - return new Game(word); - } - - try_guess(guess: string): GuessResult { - if (guess === this.word) return { kind: "success" }; - const rest_actual = [...this.word].map((letter) => letter as (string | null)); - const rest_guess = [...guess].map((letter) => letter as (string | null)); - const info = [...range(0, this.word.length)].map(() => null) as (Info | null)[]; - - for (const [index, [guessed, actual]] of enumerate(zip(rest_guess, rest_actual))) { - if (guessed !== actual) continue; - rest_actual[index] = null; - rest_guess[index] = null; - info[index] = { kind: "known", at: new Set([index]) }; - } - - for (const [index, guessed] of enumerate(rest_guess)) { - if (guessed === null) continue; - if (!rest_actual.includes(guessed)) continue; - info[index] = { kind: "present", not_at: new Set([index]) }; - rest_guess[index] = null; - // note : removes only one. - rest_actual[rest_actual.indexOf(guessed)] = null; - } - - const informations = info.map((i) => i != undefined ? i : ({ kind: "abscent" }) as Info); - return { kind: "failure", informations }; - } -} - -function* range(from: number, to: number) { - while (from < to) yield from++; -} - -async function wait(ms: number) { - await new Promise((resolver) => setTimeout(resolver, ms)); -} - -if (import.meta.main) await main(); diff --git a/src/main.ts b/src/main.ts deleted file mode 100755 index 8990a82..0000000 --- a/src/main.ts +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env -S deno run --allow-read - -import { GuessResult } from "./lib/game/game.ts"; -import { Dict, Gueser, Simulator } from "./lib/lib.ts"; -import { wait } from "./lib/utils.ts"; - -async function main() { - const length = 6; - const dict = await Dict.from_file("./data/liste_francais.txt", length); - console.log(dict); - const guesser = new Gueser(dict); - const game = Simulator.from_dict_rand(dict); - console.log("Target is", game.word); - let index = 0; - while (true) { - console.log(); - index += 1; - const found = await guesser.next_guess((guess, known) => { - console.log("knows: ", known); - console.log("Guessing:", guess); - const result = game.try_guess(guess); - console.log(format_result(result)); - return result; - }); - if (found !== undefined) { - console.log("Found", found, "in", index); - break; - } - await wait(500); - } -} - -function format_result(result: GuessResult) { - if (result.kind === "success") return `success`; - let line = "failure: "; - for (const info of result.informations) line += info.kind === "abscent" ? "-" : info.kind === "somewhere" ? "+" : "#"; - return line; -} - -if (import.meta.main) await main();