diff --git a/deno.lock b/deno.lock deleted file mode 100644 index a72822d..0000000 --- a/deno.lock +++ /dev/null @@ -1,77 +0,0 @@ -{ - "version": "3", - "redirects": { - "https://deno.land/x/cliffy/command/mod.ts": "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts" - }, - "remote": { - "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", - "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", - "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", - "https://deno.land/std@0.221.0/console/_run_length.ts": "7da8642a0f4f41ac27c0adb1364e18886be856c1d08c5cce6c6b5c00543c8722", - "https://deno.land/std@0.221.0/console/unicode_width.ts": "d92f085c0ab9c7ab171e4e7862dfd9d3a36ffd369939be5d3e1140ec58bc820f", - "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", - "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", - "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", - "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", - "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_utils.ts": "fa0e88cc4215b18554a7308e8e2ae3a12be0fb91c54d1473c54c530dbd4adfcb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/command.ts": "83cbece11c1459d5bc5add32c3cad0bf49e92c4ddd3ef00f22f80efdae30994e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_zsh_completions_generator.ts": "9df79fbac17a32b9645d01628c41a2bfd295d7976b87b0ae235f50a9c8975fbc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deps.ts": "a58ea2fa4e2ed9b39bb8dd8c35dd0498c74f05392517ff230a9a4d04c4c766b7", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/_help_generator.ts": "98619da83ff25523280a6fdcad89af3f13a6fafefc81b71f8230f3344b5ff2c5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/upgrade_command.ts": "27191f4b1ce93581b6d5ee2fff6003fe4fca437f476ecb98b6eae92f2b4d0716", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_utils.ts": "25e519ce1f35acc8b43c75d1ca1c4ab591e7dab08327b7b408705b591e27d8bd", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deps.ts": "bed26afff36eeb25509440edec9d5d141b3411e08cc7a90e38a370969b5166bb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_layout.ts": "73a9bcb8a87b3a6817c4c9d2a31a21b874a7dd690ade1c64c9a1f066d628d626", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_utils.ts": "13390db3f11977b7a4fc1202fa8386be14696b475a7f46a65178354f9a6640b7", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/cell.ts": "65e3ee699c3cebeb4d4d44e8f156e37a8532a0f317359d73178a95724d3f9267", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/consume_words.ts": "369d065dbf7f15c664ea8523e0ef750fb952aea6d88e146c375e64aec9503052", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/deps.ts": "cbb896e8d7a6b5e3c2b9dda7d16638c202d9b46eb738c2dae1fa9480d8091486", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684" - } -} diff --git a/main.ts b/main.ts new file mode 100755 index 0000000..3e206ee --- /dev/null +++ b/main.ts @@ -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, 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/lib/dict.ts b/src/lib/dict.ts index 539e0c4..8e08a95 100644 --- a/src/lib/dict.ts +++ b/src/lib/dict.ts @@ -1,3 +1,5 @@ +import { remove_accent } from "./utils.ts"; + export class Dict { words; letters; @@ -15,8 +17,8 @@ export class Dict { for (const word of content.split("\n")) { const word_ = word.trim().toLowerCase(); if (word_.length !== length) continue; - if (contains_any(word_, [" ", "-", "."])) continue; - words.add(remove_accents(word_)); + for (const forbidden of [" ", "-", "."]) if (word_.includes(forbidden)) continue; + words.add(remove_accent(word_)); } return new Dict(words, length); } @@ -25,22 +27,3 @@ export class Dict { return `Dict { ${this.words.size} words }`; } } - -export function contains_any(text: string, words: string[]) { - for (const word of words) if (text.includes(word)) return true; - return false; -} - -export function remove_accents(text: string) { - const accents = [ - ...[["à", "a"], ["â", "a"], ["ä", "a"]], - ...[["ç", "c"]], - ...[["é", "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; -} diff --git a/src/lib/game/game.ts b/src/lib/game/game.ts index e6acbe4..be79b9d 100644 --- a/src/lib/game/game.ts +++ b/src/lib/game/game.ts @@ -1,10 +1,3 @@ -import { Awaitable } from "../utils.ts"; - export type Info = { kind: "abscent" } | { kind: "somewhere" } | { kind: "there" }; export type GuessResult = { kind: "success" } | { kind: "failure"; informations: Info[] }; - -export interface Gaming { - length(): number; - guess(guess: string, known: string): Awaitable; -} diff --git a/src/lib/game/simulator.ts b/src/lib/game/simulator.ts index 7b4d247..b71a82d 100644 --- a/src/lib/game/simulator.ts +++ b/src/lib/game/simulator.ts @@ -1,8 +1,8 @@ import { Dict } from "../dict.ts"; import { enumerate, range, zip } from "../utils.ts"; -import { Gaming, GuessResult, Info } from "./game.ts"; +import { GuessResult, Info } from "./game.ts"; -export class Simulator implements Gaming { +export class Simulator { word; constructor(word: string) { @@ -15,10 +15,10 @@ export class Simulator implements Gaming { return new Simulator(word); } - guess(guess_: string, _known: string): GuessResult { - if (guess_ === this.word) return { kind: "success" }; + 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 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))) { @@ -40,8 +40,4 @@ export class Simulator implements Gaming { const informations = info.map((i) => i != undefined ? i : ({ kind: "abscent" }) as Info); return { kind: "failure", informations }; } - - length(): number { - return this.word.length; - } } diff --git a/src/lib/guesser.ts b/src/lib/guesser.ts index c0afca6..6ba23d9 100644 --- a/src/lib/guesser.ts +++ b/src/lib/guesser.ts @@ -1,13 +1,9 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; -import { Awaitable, enumerate, range } from "./utils.ts"; +import { enumerate, range } from "./utils.ts"; import { Dict } from "./dict.ts"; import { GuessResult } from "./game/game.ts"; -export interface Guessing { - guess(try_: (guess: string, known: string) => Awaitable): Promise; -} - type Knowledge = { letter: string; at: Set; @@ -15,7 +11,7 @@ type Knowledge = { not_at: Set; }; -export class Guesser implements Guessing { +export class Gueser { length; dict; informations; @@ -29,9 +25,9 @@ export class Guesser implements Guessing { } } - async guess(try_: (guess: string, known: string) => Awaitable) { + async next_guess(operation: (guess: string, known: string) => GuessResult | Promise) { const res = this.expected_result(); - if (res.completed) return await try_(res.result, res.result); + if (res.completed) return operation(res.result, res.result); const words = [...this.dict.words.values()]; const scored = words.map((word) => [this.info_score_for(word), word] as const); @@ -39,8 +35,8 @@ export class Guesser implements Guessing { const bests = scored.filter(([s]) => s === best_score).map(([_, word]) => word); const guess = pick_random_common_word(bests); - const result = await try_(guess, res.result); - if (result.kind === "success") return result; + const result = await operation(guess, res.result); + if (result.kind === "success") return guess; for (const [index, info] of enumerate(result.informations)) { const letter = guess[index]; if (info.kind === "there") this.learn_letter_at(letter, index); @@ -50,7 +46,6 @@ export class Guesser implements Guessing { this.learn_letter_not_at(letter, index); } } - return null; } learn_does_not_exist(letter: string) { diff --git a/src/lib/lib.ts b/src/lib/lib.ts index 3ad2e8e..e5f3473 100644 --- a/src/lib/lib.ts +++ b/src/lib/lib.ts @@ -1,4 +1,3 @@ export { Dict } from "./dict.ts"; -export { Guesser } from "./guesser.ts"; +export { Gueser } from "./guesser.ts"; export { Simulator } from "./game/simulator.ts"; -export { Runner } from "./runner.ts"; diff --git a/src/lib/runner.ts b/src/lib/runner.ts deleted file mode 100644 index cf01559..0000000 --- a/src/lib/runner.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; - -import { Guessing } from "./guesser.ts"; -import { Gaming, GuessResult } from "./game/game.ts"; -import { last, wait } from "./utils.ts"; - -export class Runner { - game; - guesser; - delay_ms; - turns; - logging; - - constructor(game: Ga, guesser: Gu, logging: LoggingStrategy, delay_ms = 0) { - this.game = game; - this.guesser = guesser; - this.delay_ms = delay_ms; - this.turns = [] as Turn[]; - this.logging = logging; - } - - async play_all() { - this.logging.on_start(this.game.length()); - while (true) { - const result = await this.guesser.guess(async (guess, known) => { - const result = await this.game.guess(guess, known); - this.turns.push({ guess, result }); - this.logging.on_guess(known, guess, result); - return result; - }); - if (result !== null) break; - await wait(this.delay_ms); - } - this.logging.on_finish(this.turns); - } -} - -type Turn = { guess: string; result: GuessResult }; - -function format_result(result: GuessResult) { - if (result.kind === "success") return "success"; - let line = ""; - for (const info of result.informations) { - if (info.kind === "abscent") line += "."; - if (info.kind === "somewhere") line += "*"; - if (info.kind === "there") line += "#"; - } - return line; -} - -interface LoggingStrategy { - on_start(length: number): void; - on_guess(known: string, guess: string, result: GuessResult): void; - on_finish(turns: Turn[]): void; -} - -export class VerboseLogging implements LoggingStrategy { - on_start(length: number) {} - - on_guess(known: string, guess: string, result: GuessResult): void { - console.log(" Knows:", known); - console.log("Guessing:", guess); - console.log(" Got:", format_result(result)); - console.log(); - } - - on_finish(turns: Turn[]): void { - const last_ = last(turns); - assertExists(last_); - console.log("Stopped on", last_.guess, "."); - console.log("With", last_.result.kind, "."); - console.log("In", turns.length, "steps"); - } -} - -export class TableLogging implements LoggingStrategy { - on_start(length: number): void { - const pad = (title: string) => title.padEnd(length); - console.log(`${pad("known")}\t${pad("guess")}\t${pad("result")}`); - console.log(); - } - on_guess(known: string, guess: string, result: GuessResult): void { - console.log(`${known}\t${guess}\t${format_result(result)}`); - } - on_finish(turns: Turn[]): void { - const last_ = last(turns); - assertExists(last_); - console.log(); - console.log(last_.result.kind, "in", turns.length, "steps"); - } -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index be4f837..cf2ebfd 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -23,10 +23,23 @@ export async function wait(ms: number) { await new Promise((resolver) => setTimeout(resolver, ms)); } -export type Awaitable = T | Promise; - -export function last(iterable: Iterable) { - let last = undefined as T | undefined; - for (const item of iterable) last = item; - return last; +export function remove_accent(text: string) { + const accents = [ + ["à", "a"], + ["â", "a"], + ["ä", "a"], + ["ç", "c"], + ["é", "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; } diff --git a/src/main.ts b/src/main.ts new file mode 100755 index 0000000..8990a82 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,40 @@ +#!/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(); diff --git a/src/simulation.ts b/src/simulation.ts deleted file mode 100755 index d33245b..0000000 --- a/src/simulation.ts +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env -S deno run --allow-read - -import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; - -import { Dict, Guesser, Runner, Simulator } from "./lib/lib.ts"; -import { TableLogging } from "./lib/runner.ts"; - -async function main() { - const args = await new Command().name("simulation") - .description( - "Program to simulate TUSMO game with guesser controller.", - ) - .option( - "-d, --dictionnary ", - "Sets dictionnary to use words from.", - { default: "./data/liste_francais.txt" }, - ).option( - "-n, --length ", - "Length of the word to use from the dictionnary.", - { default: 6 }, - ).option( - "-w, --wait ", - "Time to wait between guesses, in ms.", - { default: 500 }, - ).parse(Deno.args); - - const dict = await Dict.from_file(args.options.dictionnary, args.options.length); - - const guesser = new Guesser(dict); - const game = Simulator.from_dict_rand(dict); - console.log("Target is", game.word); - console.log(); - - const runner = new Runner(game, guesser, new TableLogging(), args.options.wait); - await runner.play_all(); -} - -if (import.meta.main) await main();