Compare commits
No commits in common. "241bcd29e311a5934b4c807cb0a1cb80c273f733" and "f5e8aa12d92d8f8ec9174dfbbeb80d1dc6edb236" have entirely different histories.
241bcd29e3
...
f5e8aa12d9
11 changed files with 320 additions and 262 deletions
77
deno.lock
generated
77
deno.lock
generated
|
@ -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"
|
||||
}
|
||||
}
|
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();
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<GuessResult>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<GuessResult>): Promise<GuessResult | null>;
|
||||
}
|
||||
|
||||
type Knowledge = {
|
||||
letter: string;
|
||||
at: Set<number>;
|
||||
|
@ -15,7 +11,7 @@ type Knowledge = {
|
|||
not_at: Set<number>;
|
||||
};
|
||||
|
||||
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<GuessResult>) {
|
||||
async next_guess(operation: (guess: string, known: string) => GuessResult | Promise<GuessResult>) {
|
||||
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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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<Ga extends Gaming, Gu extends Guessing> {
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -23,10 +23,23 @@ export async function wait(ms: number) {
|
|||
await new Promise((resolver) => setTimeout(resolver, ms));
|
||||
}
|
||||
|
||||
export type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
export function last<T>(iterable: Iterable<T>) {
|
||||
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;
|
||||
}
|
||||
|
|
40
src/main.ts
Executable file
40
src/main.ts
Executable file
|
@ -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();
|
|
@ -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 <path:string>",
|
||||
"Sets dictionnary to use words from.",
|
||||
{ default: "./data/liste_francais.txt" },
|
||||
).option(
|
||||
"-n, --length <length:number>",
|
||||
"Length of the word to use from the dictionnary.",
|
||||
{ default: 6 },
|
||||
).option(
|
||||
"-w, --wait <wait:number>",
|
||||
"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();
|
Loading…
Add table
Add a link
Reference in a new issue