Compare commits
10 commits
f5e8aa12d9
...
241bcd29e3
Author | SHA1 | Date | |
---|---|---|---|
241bcd29e3 | |||
43c835d1eb | |||
bbc4e53a9a | |||
ba87099e0e | |||
1156752e0a | |||
5d2ca505d7 | |||
aa98d8805b | |||
72c7373343 | |||
dc32188c91 | |||
e5b23bfd16 |
11 changed files with 262 additions and 320 deletions
77
deno.lock
generated
Normal file
77
deno.lock
generated
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
"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
245
main.ts
|
@ -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<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,5 +1,3 @@
|
||||||
import { remove_accent } from "./utils.ts";
|
|
||||||
|
|
||||||
export class Dict {
|
export class Dict {
|
||||||
words;
|
words;
|
||||||
letters;
|
letters;
|
||||||
|
@ -17,8 +15,8 @@ export class Dict {
|
||||||
for (const word of content.split("\n")) {
|
for (const word of content.split("\n")) {
|
||||||
const word_ = word.trim().toLowerCase();
|
const word_ = word.trim().toLowerCase();
|
||||||
if (word_.length !== length) continue;
|
if (word_.length !== length) continue;
|
||||||
for (const forbidden of [" ", "-", "."]) if (word_.includes(forbidden)) continue;
|
if (contains_any(word_, [" ", "-", "."])) continue;
|
||||||
words.add(remove_accent(word_));
|
words.add(remove_accents(word_));
|
||||||
}
|
}
|
||||||
return new Dict(words, length);
|
return new Dict(words, length);
|
||||||
}
|
}
|
||||||
|
@ -27,3 +25,22 @@ export class Dict {
|
||||||
return `Dict { ${this.words.size} words }`;
|
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,3 +1,10 @@
|
||||||
|
import { Awaitable } from "../utils.ts";
|
||||||
|
|
||||||
export type Info = { kind: "abscent" } | { kind: "somewhere" } | { kind: "there" };
|
export type Info = { kind: "abscent" } | { kind: "somewhere" } | { kind: "there" };
|
||||||
|
|
||||||
export type GuessResult = { kind: "success" } | { kind: "failure"; informations: Info[] };
|
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 { Dict } from "../dict.ts";
|
||||||
import { enumerate, range, zip } from "../utils.ts";
|
import { enumerate, range, zip } from "../utils.ts";
|
||||||
import { GuessResult, Info } from "./game.ts";
|
import { Gaming, GuessResult, Info } from "./game.ts";
|
||||||
|
|
||||||
export class Simulator {
|
export class Simulator implements Gaming {
|
||||||
word;
|
word;
|
||||||
|
|
||||||
constructor(word: string) {
|
constructor(word: string) {
|
||||||
|
@ -15,10 +15,10 @@ export class Simulator {
|
||||||
return new Simulator(word);
|
return new Simulator(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
try_guess(guess: string): GuessResult {
|
guess(guess_: string, _known: string): GuessResult {
|
||||||
if (guess === this.word) return { kind: "success" };
|
if (guess_ === this.word) return { kind: "success" };
|
||||||
const rest_actual = [...this.word].map((letter) => letter as (string | null));
|
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)[];
|
const info = [...range(0, this.word.length)].map(() => null) as (Info | null)[];
|
||||||
|
|
||||||
for (const [index, [guessed, actual]] of enumerate(zip(rest_guess, rest_actual))) {
|
for (const [index, [guessed, actual]] of enumerate(zip(rest_guess, rest_actual))) {
|
||||||
|
@ -40,4 +40,8 @@ export class Simulator {
|
||||||
const informations = info.map((i) => i != undefined ? i : ({ kind: "abscent" }) as Info);
|
const informations = info.map((i) => i != undefined ? i : ({ kind: "abscent" }) as Info);
|
||||||
return { kind: "failure", informations };
|
return { kind: "failure", informations };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
length(): number {
|
||||||
|
return this.word.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts";
|
import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts";
|
||||||
|
|
||||||
import { enumerate, range } from "./utils.ts";
|
import { Awaitable, enumerate, range } from "./utils.ts";
|
||||||
import { Dict } from "./dict.ts";
|
import { Dict } from "./dict.ts";
|
||||||
import { GuessResult } from "./game/game.ts";
|
import { GuessResult } from "./game/game.ts";
|
||||||
|
|
||||||
|
export interface Guessing {
|
||||||
|
guess(try_: (guess: string, known: string) => Awaitable<GuessResult>): Promise<GuessResult | null>;
|
||||||
|
}
|
||||||
|
|
||||||
type Knowledge = {
|
type Knowledge = {
|
||||||
letter: string;
|
letter: string;
|
||||||
at: Set<number>;
|
at: Set<number>;
|
||||||
|
@ -11,7 +15,7 @@ type Knowledge = {
|
||||||
not_at: Set<number>;
|
not_at: Set<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Gueser {
|
export class Guesser implements Guessing {
|
||||||
length;
|
length;
|
||||||
dict;
|
dict;
|
||||||
informations;
|
informations;
|
||||||
|
@ -25,9 +29,9 @@ export class Gueser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async next_guess(operation: (guess: string, known: string) => GuessResult | Promise<GuessResult>) {
|
async guess(try_: (guess: string, known: string) => Awaitable<GuessResult>) {
|
||||||
const res = this.expected_result();
|
const res = this.expected_result();
|
||||||
if (res.completed) return operation(res.result, res.result);
|
if (res.completed) return await try_(res.result, res.result);
|
||||||
|
|
||||||
const words = [...this.dict.words.values()];
|
const words = [...this.dict.words.values()];
|
||||||
const scored = words.map((word) => [this.info_score_for(word), word] as const);
|
const scored = words.map((word) => [this.info_score_for(word), word] as const);
|
||||||
|
@ -35,8 +39,8 @@ export class Gueser {
|
||||||
const bests = scored.filter(([s]) => s === best_score).map(([_, word]) => word);
|
const bests = scored.filter(([s]) => s === best_score).map(([_, word]) => word);
|
||||||
const guess = pick_random_common_word(bests);
|
const guess = pick_random_common_word(bests);
|
||||||
|
|
||||||
const result = await operation(guess, res.result);
|
const result = await try_(guess, res.result);
|
||||||
if (result.kind === "success") return guess;
|
if (result.kind === "success") return result;
|
||||||
for (const [index, info] of enumerate(result.informations)) {
|
for (const [index, info] of enumerate(result.informations)) {
|
||||||
const letter = guess[index];
|
const letter = guess[index];
|
||||||
if (info.kind === "there") this.learn_letter_at(letter, index);
|
if (info.kind === "there") this.learn_letter_at(letter, index);
|
||||||
|
@ -46,6 +50,7 @@ export class Gueser {
|
||||||
this.learn_letter_not_at(letter, index);
|
this.learn_letter_not_at(letter, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
learn_does_not_exist(letter: string) {
|
learn_does_not_exist(letter: string) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export { Dict } from "./dict.ts";
|
export { Dict } from "./dict.ts";
|
||||||
export { Gueser } from "./guesser.ts";
|
export { Guesser } from "./guesser.ts";
|
||||||
export { Simulator } from "./game/simulator.ts";
|
export { Simulator } from "./game/simulator.ts";
|
||||||
|
export { Runner } from "./runner.ts";
|
||||||
|
|
91
src/lib/runner.ts
Normal file
91
src/lib/runner.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
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,23 +23,10 @@ export async function wait(ms: number) {
|
||||||
await new Promise((resolver) => setTimeout(resolver, ms));
|
await new Promise((resolver) => setTimeout(resolver, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function remove_accent(text: string) {
|
export type Awaitable<T> = T | Promise<T>;
|
||||||
const accents = [
|
|
||||||
["à", "a"],
|
export function last<T>(iterable: Iterable<T>) {
|
||||||
["â", "a"],
|
let last = undefined as T | undefined;
|
||||||
["ä", "a"],
|
for (const item of iterable) last = item;
|
||||||
["ç", "c"],
|
return last;
|
||||||
["é", "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
40
src/main.ts
|
@ -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();
|
|
38
src/simulation.ts
Executable file
38
src/simulation.ts
Executable file
|
@ -0,0 +1,38 @@
|
||||||
|
#!/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