diff --git a/README.md b/README.md index c79560d..f3c80b6 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,67 @@ Programme pour résolution de Tusmo. # -n, --length - Length of the word to use from the dictionnary. (Default: 6) # -w, --wait - Time to wait between guesses, in ms. (Default: 0) ``` + +### Proxy + +```sh +./src/manual_proxy.ts +# Please input initial state of the game. Format is : +# letter [a-z] known letter +# dot . unknown letter +# example : .rb.. + +# initial : a.... + +# From now on, please try the following guesses and report results. Format is : +# plus + correct +# minus - wrong placement +# dot . incorrect +# example : -++.. + +# Guessing: aires +# Result: +.--. + +# Knows: ..... +# Guessing: aires +# Got: #.**. + +# Guessing: acere +# Result: +..++ + +# Knows: a.... +# Guessing: acere +# Got: #..## + +# Guessing: arabe +# Result: ++.-+ + +# Knows: a..re +# Guessing: arabe +# Got: ##.*# + +# Guessing: aveux +# Result: +.-.. + +# Knows: ar.re +# Guessing: aveux +# Got: #.*.. + +# Guessing: album +# Result: +.+.. + +# Knows: ar.re +# Guessing: album +# Got: #.#.. + +# Guessing: arbre +# Result: +++++ + +# Knows: arbre +# Guessing: arbre +# Got: success + +# Stopped on arbre . +# With success . +# In 6 steps +``` diff --git a/build.sh b/build.sh index 57e17f1..52377e9 100755 --- a/build.sh +++ b/build.sh @@ -4,4 +4,5 @@ cd "$(dirname "$(realpath "$0")")" mkdir -p bin +deno compile -o bin/manual_proxy src/manual_proxy.ts deno compile -o bin/simulation src/simulation.ts diff --git a/src/lib/dict.ts b/src/lib/dict.ts index 50603a6..cb679c9 100644 --- a/src/lib/dict.ts +++ b/src/lib/dict.ts @@ -25,6 +25,13 @@ export class Dict { return Dict.from_lines(content.split("\n"), length); } + constraint(at: number, letter: string) { + const to_delete = new Set(); + for (const word of this.words.values()) if (word[at] !== letter) to_delete.add(word); + for (const item of to_delete) this.words.delete(item); + return to_delete.size; + } + [Symbol.for("Deno.customInspect")]() { return `Dict { ${this.words.size} words, ${this.letters.size} letters }`; } diff --git a/src/lib/game/proxy.ts b/src/lib/game/proxy.ts new file mode 100644 index 0000000..ced5f21 --- /dev/null +++ b/src/lib/game/proxy.ts @@ -0,0 +1,48 @@ +import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; + +import { Awaitable } from "../utils.ts"; +import { Gaming, GuessResult, Info } from "./game.ts"; + +export class ManualProxy implements Gaming { + word_length; + + constructor(length: number) { + this.word_length = length; + } + + guess(guess: string, _known: string): Awaitable { + console.log(" Guessing:", guess); + const result = read_until_correct(this.word_length); + console.log(); + return result; + } + + length(): number { + return this.word_length; + } +} + +function read_until_correct(length: number): GuessResult { + while (true) { + const input = prompt(" Result:"); + assertExists(input); + const informations = parse_input(input, length); + if (informations === null) { + console.log(" incorrect input, try again"); + continue; + } + if (informations.every((i) => i.kind === "there")) return { kind: "success" }; + return { kind: "failure", informations }; + } +} + +function parse_input(input: string, length: number) { + const parsed = [] as Info[]; + for (const character of input.trim()) { + if (character === ".") parsed.push({ kind: "abscent" }); + if (character === "-") parsed.push({ kind: "somewhere" }); + if (character === "+") parsed.push({ kind: "there" }); + } + if (parsed.length !== length) return null; + else return parsed; +} diff --git a/src/lib/guesser.ts b/src/lib/guesser.ts index c0afca6..841dc88 100644 --- a/src/lib/guesser.ts +++ b/src/lib/guesser.ts @@ -53,13 +53,13 @@ export class Guesser implements Guessing { return null; } - learn_does_not_exist(letter: string) { + public learn_does_not_exist(letter: string) { const letter_info = this.informations.get(letter); assertExists(letter_info); letter_info.exists = false; } - learn_does_exist(letter: string) { + public learn_does_exist(letter: string) { const letter_info = this.informations.get(letter); assertExists(letter_info); letter_info.exists = true; @@ -68,7 +68,8 @@ export class Guesser implements Guessing { for (const info of this.informations.values()) if (info.exists === "unknown") info.exists = false; } - learn_letter_at(letter: string, at: number) { + public learn_letter_at(letter: string, at: number) { + this.learn_does_exist(letter); const letter_info = this.informations.get(letter); assertExists(letter_info); letter_info.at.add(at); @@ -78,7 +79,7 @@ export class Guesser implements Guessing { } } - learn_letter_not_at(letter: string, at: number) { + public learn_letter_not_at(letter: string, at: number) { const letter_info = this.informations.get(letter); assertExists(letter_info); letter_info.not_at.add(at); diff --git a/src/lib/lib.ts b/src/lib/lib.ts index 3ad2e8e..bd84cb1 100644 --- a/src/lib/lib.ts +++ b/src/lib/lib.ts @@ -1,4 +1,5 @@ export { Dict } from "./dict.ts"; export { Guesser } from "./guesser.ts"; export { Simulator } from "./game/simulator.ts"; +export { ManualProxy } from "./game/proxy.ts"; export { Runner } from "./runner.ts"; diff --git a/src/lib/prompt.ts b/src/lib/prompt.ts new file mode 100644 index 0000000..01f42a8 --- /dev/null +++ b/src/lib/prompt.ts @@ -0,0 +1,46 @@ +import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; + +import { enumerate } from "./utils.ts"; + +export function initialize_prompt() { + console.log("Please input initial state of the game. Format is :"); + console.log(" letter [a-z] known letter"); + console.log(" dot . unknown letter"); + console.log("example : .rb.."); + console.log(""); + const inputted = parse_initialization_until_correct(); + console.log(""); + console.log("From now on, please try the following guesses and report results. Format is :"); + console.log(" plus + correct"); + console.log(" minus - wrong placement"); + console.log(" dot . incorrect"); + console.log("example : -++.."); + console.log(); + return inputted; +} + +function parse_initialization_until_correct() { + let input = prompt("initial :"); + while (true) { + assertExists(input); + const parsed = parse_initialization(input); + if (parsed === null) input = prompt("Invalid, please try again :"); + else return parsed; + } +} + +function parse_initialization(input: string) { + const code_a = "a".charCodeAt(0); + const code_z = "z".charCodeAt(0); + let length = 0; + const constraints = [] as [number, string][]; + for (const [index, char] of enumerate(input.trim().toLowerCase())) { + length += 1; + if (char === ".") continue; + const code = char.charCodeAt(0); + if (code < code_a) return null; + if (code > code_z) return null; + constraints.push([index, char]); + } + return { length, constraints }; +} diff --git a/src/manual_proxy.ts b/src/manual_proxy.ts new file mode 100755 index 0000000..4ffaf87 --- /dev/null +++ b/src/manual_proxy.ts @@ -0,0 +1,34 @@ +#!/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, ManualProxy, Runner } from "./lib/lib.ts"; +import { initialize_prompt } from "./lib/prompt.ts"; +import { VerboseLogging } from "./lib/runner.ts"; + +import { francais } from "../data/data.ts"; + +async function main() { + const args = await new Command().name("manual_proxy") + .description( + "Program to solve manually proxied TUSMO games with guesser controller.", + ) + .option( + "-f, --file ", + "Sets dictionnary to use words from (defaults to internal french dict).", + ).parse(Deno.args); + + const init = initialize_prompt(); + + let dict = Dict.from_lines(francais, init.length); + if (args.options.file !== undefined) dict = await Dict.from_text_file(args.options.file, init.length); + for (const [index, letter] of init.constraints) dict.constraint(index, letter); + + const guesser = new Guesser(dict); + const game = new ManualProxy(init.length); + + const runner = new Runner(game, guesser, new VerboseLogging()); + await runner.play_all(); +} + +if (import.meta.main) await main();