init
This commit is contained in:
commit
238b273734
5 changed files with 357858 additions and 0 deletions
245
main.ts
Executable file
245
main.ts
Executable file
|
@ -0,0 +1,245 @@
|
|||
#!/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<string>, 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<string>();
|
||||
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<number>;
|
||||
not_at: Set<number>;
|
||||
};
|
||||
|
||||
class History {
|
||||
length;
|
||||
dict;
|
||||
informations;
|
||||
|
||||
constructor(dict: Dict) {
|
||||
this.length = dict.length;
|
||||
this.dict = dict;
|
||||
this.informations = new Map<string, Knowledge>();
|
||||
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<GuessResult>) {
|
||||
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<T>(iterator: Iterable<T>) {
|
||||
let index = 0;
|
||||
for (const item of iterator) yield [index++, item] as const;
|
||||
}
|
||||
|
||||
function* zip<A, B>(iterable_a: Iterable<A>, iterable_b: Iterable<B>) {
|
||||
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();
|
Loading…
Add table
Add a link
Reference in a new issue