From 334c7361fc68fa08225dc2b1c2114c0d39e0c2b4 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Mon, 3 Jun 2024 05:17:38 +0200 Subject: [PATCH 1/2] change stdin api for better testability --- build.sh | 3 ++ deno.lock | 13 ++++- profile/aies.sh | 6 +++ src/game.ts | 47 +++++++++++++++++++ src/lib/guesser/explorer.ts | 2 +- src/lib/guesser/input.ts | 42 +++++++++++++++++ src/lib/guesser/reducing.ts | 55 +++++++++++++--------- src/lib/lib.ts | 3 +- src/lib/prompt.ts | 15 +++--- src/lib/runner.ts | 53 ++++++++++++++++++--- src/lib/utils.ts | 94 +++++++++++++++++++++++++++++++++++++ src/manual_proxy.ts | 6 ++- src/simulation.ts | 17 +++++-- 13 files changed, 312 insertions(+), 44 deletions(-) create mode 100755 profile/aies.sh create mode 100644 src/game.ts create mode 100644 src/lib/guesser/input.ts diff --git a/build.sh b/build.sh index 52377e9..95996d8 100755 --- a/build.sh +++ b/build.sh @@ -2,7 +2,10 @@ set -e 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 +deno compile -o bin/game src/game.ts diff --git a/deno.lock b/deno.lock index 832355f..e5c2464 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,7 @@ { "version": "3", "redirects": { + "https://deno.land/std@0.224.0/streams/": "https://deno.land/std@0.224.0/streams", "https://deno.land/x/cliffy/command/mod.ts": "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts" }, "remote": { @@ -16,12 +17,15 @@ "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038", "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", @@ -39,6 +43,7 @@ "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/streams/text_line_stream.ts": "21f33d3922e019ec1a1676474beb543929cb564ec99b69cd2654e029e0f45bd5", "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", @@ -97,6 +102,12 @@ "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" + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/data/generated.ts": "3af769f4f14914c8fca84054f580754b797fd5c0fef9ef59e9f47bfed7ba6f7c", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts": "bc9436bcf45f3204ebcd0b2559f53e5d38162195fc48ac6d435cf8ca38c77ae3", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/src/lib.ts": "d0d9af31237f93f6b6c2b695ac2fdcc7e761ca3904382e140f4676fc109d8453", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/src/lib/observer.ts": "58beebe5f1854b16470df7a715f9cb60dc7db4d4f8648a71eee58a3d1cb7bb53", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/src/lib/types.ts": "ecd5f1260a839576787e22ff205fee0399837c66f037f31f0322df723f1490c2", + "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/src/lib/utils.ts": "e79ae3583e148fd8ccf5d53f9d7d316ee658b6a62c3a0f66517c630c1586e0a8" } } diff --git a/profile/aies.sh b/profile/aies.sh new file mode 100755 index 0000000..a353f66 --- /dev/null +++ b/profile/aies.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +cd "$(dirname "$(realpath "$0")")" + + +PROFILE=true ../src/simulation.ts --target=aies diff --git a/src/game.ts b/src/game.ts new file mode 100644 index 0000000..324e64a --- /dev/null +++ b/src/game.ts @@ -0,0 +1,47 @@ +#!/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, GameLogging, InputGuesser, Runner, Simulator } from "./lib/lib.ts"; +import { gutenberg } from "../data/data.ts"; +import { range } from "./lib/utils.ts"; + +async function main() { + const args = await new Command().name("simulation") + .description( + "Program to simulate TUSMO game with guesser controller.", + ) + .option( + "-f, --file ", + "Sets dictionnary to use words from (defaults to internal french dict).", + ).option( + "-l, --length ", + "Length of the word to use from the dictionnary.", + ).option( + "-n, --iterations ", + "Number of iterations.", + ).option( + "-t, --target ", + "Target word to search for.", + ).parse(Deno.args); + + const length = args.options.length ?? args.options.target?.length ?? 6; + + let dict = Dict.from_lines(gutenberg, length); + if (args.options.file !== undefined) dict = await Dict.from_text_file(args.options.file, length); + + let game = Simulator.from_dict_rand(dict); + if (args.options.target !== undefined) game = new Simulator(validate_target(args.options.target, length)); + + const guesser = new InputGuesser(length); + const runner = new Runner(game, guesser, new GameLogging()); + + const iterations = args.options.iterations ?? 100_000; + for (const _ of range(0, iterations)) await runner.play_once(); +} + +function validate_target(target: string, length: number) { + if (target.length !== length) throw new Error("Invalid target length"); + return target.toLowerCase(); +} + +if (import.meta.main) await main(); diff --git a/src/lib/guesser/explorer.ts b/src/lib/guesser/explorer.ts index 73dd703..1be4322 100644 --- a/src/lib/guesser/explorer.ts +++ b/src/lib/guesser/explorer.ts @@ -3,7 +3,7 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists import { Awaitable, enumerate, range, zip } from "../utils.ts"; import { Dict } from "../dict.ts"; import { GuessResult } from "../game/game.ts"; -import { Guessing } from "./guesser.ts"; +import { Guessing, pick_random_common_word } from "./guesser.ts"; type Knowledge = { letter: string; diff --git a/src/lib/guesser/input.ts b/src/lib/guesser/input.ts new file mode 100644 index 0000000..2a1570a --- /dev/null +++ b/src/lib/guesser/input.ts @@ -0,0 +1,42 @@ +import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; +import { GuessResult } from "../game/game.ts"; +import { async_next, Awaitable, LineReader } from "../utils.ts"; +import { Guessing } from "./guesser.ts"; + +export class InputGuesser implements Guessing { + length; + lines; + + constructor(length: number) { + this.length = length; + this.lines = new LineReader(Deno.stdin.readable); + } + + declare_properties(): string[] { + return []; + } + + async next_line() { + const value = await this.lines.read(); + assertExists(value); + return value; + } + + async guess(try_: (guess: string, ...properties: unknown[]) => Awaitable) { + let input = ""; + + while (true) { + input = await this.next_line(); + if (input.length === this.length && are_letters(input)) break; + console.log("Word is length", this.length, "and composed of letters."); + } + + return await try_(input.slice(0)); + } +} + +function are_letters(text: string) { + const letters = "azertyuiopqsdfghjklmwxcvbn"; + for (const char of text) if (!letters.includes(char)) return false; + return true; +} diff --git a/src/lib/guesser/reducing.ts b/src/lib/guesser/reducing.ts index 2f34558..a29177c 100644 --- a/src/lib/guesser/reducing.ts +++ b/src/lib/guesser/reducing.ts @@ -6,6 +6,11 @@ import { GuessResult, Info } from "../game/game.ts"; import { info_of_guess } from "../game/simulator.ts"; import { Awaitable, dbg, enumerate, first, range, zip } from "../utils.ts"; import { Guessing, pick_random_common_word } from "./guesser.ts"; +import { + section, + tip, + top, +} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; export class ReducingGuesser implements Guessing { length; @@ -23,19 +28,21 @@ export class ReducingGuesser implements Guessing { } public async guess(try_: (guess: string, candidates: number, score: number) => Awaitable) { - if (this.candidates.size === 1) return await try_(first(this.candidates)!, this.candidates.size, 1); - const [guess, score] = get_word_with_smallest_cuts(this.candidates, this.words); - if (score >= this.candidates.size) { - const pick = pick_random_common_word([...this.candidates.values()]); - assertExists(pick); - const result = await try_(pick, this.candidates.size, score); + return await section("guess", async () => { + if (this.candidates.size === 1) return await try_(first(this.candidates)!, this.candidates.size, 1); + const [guess, score] = get_word_with_smallest_cuts(this.candidates, this.words); + if (score >= this.candidates.size) { + const pick = pick_random_common_word([...this.candidates.values()]); + assertExists(pick); + const result = await try_(pick, this.candidates.size, score); + if (result.kind === "success") return result; + else return null; + } + const result = await try_(guess, this.candidates.size, score); if (result.kind === "success") return result; - else return null; - } - const result = await try_(guess, this.candidates.size, score); - if (result.kind === "success") return result; - this.learn(guess, result.informations); - return null; + this.learn(guess, result.informations); + return null; + }); } learn(word: string, infos: Info[]) { @@ -92,6 +99,7 @@ note : The algorithm must proceed as follow : */ function word_cuts(word: string, dict: Set) { + tip("word_cuts"); const results = [...range(0, word.length)] .map(() => [new Set(), new Set(), new Set()] as const); for (const option of dict) { @@ -102,20 +110,23 @@ function word_cuts(word: string, dict: Set) { if (info.kind === "abscent") results[index][2].add(option); } } + top("word_cuts"); return results; } function get_word_with_smallest_cuts(candidates: Set, dict: Set) { - let best = null as null | [string, number]; - for (const candidate of dict.values()) { - const cuts = word_cuts(candidate, candidates); - let max_part_size = 0; - for (const cut of cuts) for (const part of cut) if (part.size > max_part_size) max_part_size = part.size; - if (best === null) best = [candidate, max_part_size]; - else if (max_part_size < best[1]) best = [candidate, max_part_size]; - } - assertExists(best); - return best; + return section("get_word_with_smallest_cuts", () => { + let best = null as null | [string, number]; + for (const candidate of dict.values()) { + const cuts = word_cuts(candidate, candidates); + let max_part_size = 0; + for (const cut of cuts) for (const part of cut) if (part.size > max_part_size) max_part_size = part.size; + if (best === null) best = [candidate, max_part_size]; + else if (max_part_size < best[1]) best = [candidate, max_part_size]; + } + assertExists(best); + return best; + }); } function matches_constraints(candidate: string, constraints: [string, Info][]) { diff --git a/src/lib/lib.ts b/src/lib/lib.ts index 4dfe6c4..9a6affb 100644 --- a/src/lib/lib.ts +++ b/src/lib/lib.ts @@ -4,6 +4,7 @@ export type { LoggingStrategy } from "./runner.ts"; export { Dict } from "./dict.ts"; export { ExplorerGuesser } from "./guesser/explorer.ts"; export { ReducingGuesser } from "./guesser/reducing.ts"; +export { InputGuesser } from "./guesser/input.ts"; export { Simulator } from "./game/simulator.ts"; export { ManualProxy } from "./game/proxy.ts"; -export { Runner, TableLogging, VerboseLogging } from "./runner.ts"; +export { GameLogging, Runner, TableLogging, VerboseLogging } from "./runner.ts"; diff --git a/src/lib/prompt.ts b/src/lib/prompt.ts index 01f42a8..e377a74 100644 --- a/src/lib/prompt.ts +++ b/src/lib/prompt.ts @@ -1,14 +1,14 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; -import { enumerate } from "./utils.ts"; +import { async_next, enumerate, LineReader, next } from "./utils.ts"; -export function initialize_prompt() { +export async function initialize_prompt(lines: LineReader) { 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(); + const inputted = await parse_initialization_until_correct(lines); console.log(""); console.log("From now on, please try the following guesses and report results. Format is :"); console.log(" plus + correct"); @@ -19,13 +19,14 @@ export function initialize_prompt() { return inputted; } -function parse_initialization_until_correct() { - let input = prompt("initial :"); +async function parse_initialization_until_correct(lines: LineReader) { + console.log("initial : "); while (true) { + const input = await lines.read(); assertExists(input); const parsed = parse_initialization(input); - if (parsed === null) input = prompt("Invalid, please try again :"); - else return parsed; + if (parsed !== null) return parsed; + console.log("Invalid, please try again : "); } } diff --git a/src/lib/runner.ts b/src/lib/runner.ts index a9edf50..bd4bcf0 100644 --- a/src/lib/runner.ts +++ b/src/lib/runner.ts @@ -3,6 +3,10 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists import { Guessing } from "./guesser/guesser.ts"; import { Gaming, GuessResult } from "./game/game.ts"; import { last, wait, zip } from "./utils.ts"; +import { + tip, + top, +} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; export class Runner { game; @@ -21,17 +25,24 @@ export class Runner { this.max = max; } + async play_once() { + tip("play_once"); + const res = await this.guesser.guess(async (guess, ...properties) => { + const result = await this.game.guess(guess); + this.turns.push({ guess, result }); + this.logging.on_guess(guess, result, ...properties); + return result; + }); + top("play_once"); + return res; + } + async play_all() { this.logging.on_start(this.game.length(), ...this.guesser.declare_properties()); while (true) { - const result = await this.guesser.guess(async (guess, ...properties) => { - const result = await this.game.guess(guess); - this.turns.push({ guess, result }); - this.logging.on_guess(guess, result, ...properties); - return result; - }); + const result = await this.play_once(); if (result !== null) break; - if (this.max !== undefined) if (this.turns.length >= this.max) return this.turns; + if (this.max !== undefined) { if (this.turns.length >= this.max) return this.turns; } await wait(this.delay_ms); } this.logging.on_finish(this.turns); @@ -122,3 +133,31 @@ export class TableLogging implements LoggingStrategy { console.log(last_.result.kind, "in", turns.length, "steps"); } } + +export class GameLogging implements LoggingStrategy { + on_finish(turns: Turn[]): void { + const last_ = last(turns); + assertExists(last_); + console.log(); + console.log(last_.result.kind, "in", turns.length, "steps"); + } + + on_guess(_guess: string, result: GuessResult, ..._properties: unknown[]): void { + console.log(this.result_to_display(result)); + } + + on_start(_length: number, ..._properties: string[]): void { + } + + result_to_display(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; + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4903927..e3d79fe 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,7 @@ +import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; +import { dirname } from "https://deno.land/std@0.224.0/path/dirname.ts"; +import { TextLineStream } from "https://deno.land/std@0.224.0/streams/text_line_stream.ts"; + export function* enumerate(iterator: Iterable) { let index = 0; for (const item of iterator) yield [index++, item] as const; @@ -41,3 +45,93 @@ export function dbg(value: T, ...rest: unknown[]) { } export type Value = { value: T }; + +export function next(iterator: Iterator) { + const result = iterator.next().value; + if (result === undefined) return null; + else return result as T; +} + +export async function async_next(iterator: AsyncIterator) { + const result = await iterator.next(); + const value = result.value; + if (value === undefined) return null; + else return value as T; +} + +export class LineReader { + iterator; + + constructor(readable: ReadableStream) { + this.iterator = readable + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()) + [Symbol.asyncIterator](); + } + + async read() { + return await async_next(this.iterator); + } +} + +export class LineWriter { + writable; + + constructor(writable: WritableStream) { + const encoder = new TextEncoderStream(); + encoder.readable.pipeTo(writable); + this.writable = encoder.writable.getWriter(); + } + + async write(line: string) { + await this.writable.write(line); + } +} + +export function write_lines(writable: WritableStream) { + const queue = new AsyncQueue(); + ReadableStream.from(queue.iter()).pipeThrough(new TextEncoderStream()).pipeTo(writable); + return { queue, write: (line: string) => queue.push(line) }; +} + +export type Consumer = (value: T) => void; + +export function split_promise() { + let resolver = null as null | Consumer; + const promise = new Promise((res) => resolver = res); + assertExists(resolver); + return [promise, resolver] as const; +} + +class AsyncQueue { + items; + item_resolver; + + constructor() { + this.items = [] as T[]; + this.item_resolver = null as null | Consumer; + } + + push(item: T) { + if (this.item_resolver !== null) this.item_resolver(item); + else this.items.push(item); + } + + async pull() { + const [first] = this.items.splice(0, 1); + if (first !== undefined) return first; + const [promise, resolver] = split_promise(); + this.item_resolver = resolver; + return await promise; + } + + async *iter() { + while (true) yield await this.pull(); + } +} + +export async function project_root() { + const this_url = new URL(import.meta.url); + const this_path = await Deno.realPath(this_url.pathname); + return dirname(dirname(dirname(this_path))); +} diff --git a/src/manual_proxy.ts b/src/manual_proxy.ts index 1b7d935..93a99ea 100755 --- a/src/manual_proxy.ts +++ b/src/manual_proxy.ts @@ -5,8 +5,9 @@ import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; import { Dict, ExplorerGuesser, Guessing, ManualProxy, ReducingGuesser, Runner } from "./lib/lib.ts"; import { initialize_prompt } from "./lib/prompt.ts"; import { VerboseLogging } from "./lib/runner.ts"; +import { LineReader } from "./lib/utils.ts"; -import { francais, gutenberg } from "../data/data.ts"; +import { gutenberg } from "../data/data.ts"; async function main() { const args = await new Command().name("manual_proxy") @@ -22,7 +23,8 @@ async function main() { { default: "reducing" }, ).parse(Deno.args); - const init = initialize_prompt(); + const lines_ = new LineReader(Deno.stdin.readable); + const init = await initialize_prompt(lines_); let dict = Dict.from_lines(gutenberg, init.length); if (args.options.file !== undefined) dict = await Dict.from_text_file(args.options.file, init.length); diff --git a/src/simulation.ts b/src/simulation.ts index 4b4ed65..30ebf50 100755 --- a/src/simulation.ts +++ b/src/simulation.ts @@ -1,6 +1,13 @@ -#!/usr/bin/env -S deno run --allow-read +#!/usr/bin/env -S deno run -A --unstable-temporal import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; +import { + report, + section, + startup, + tip, + top, +} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; import { Dict, @@ -14,7 +21,7 @@ import { VerboseLogging, } from "./lib/lib.ts"; -import { francais } from "../data/data.ts"; +import { gutenberg } from "../data/data.ts"; import { last } from "./lib/utils.ts"; async function main() { @@ -51,8 +58,10 @@ async function main() { .parse(Deno.args); const length = args.options.length ?? args.options.target?.length ?? 6; - let dict = Dict.from_lines(francais, length); + tip("dict"); + let dict = Dict.from_lines(gutenberg, length); if (args.options.file !== undefined) dict = await Dict.from_text_file(args.options.file, length); + top("dict"); const guesser = guessers.get(args.options.guesser)!(dict); let game = Simulator.from_dict_rand(dict); @@ -64,6 +73,8 @@ async function main() { const result = last(await runner.play_all()); if (result === undefined) Deno.exit(1); if (result.result.kind === "failure") Deno.exit(1); + + report(); } function validate_target(target: string, length: number) { From 8e399fd2bf386cb8c831ea8e2ac452733b430362 Mon Sep 17 00:00:00 2001 From: Matthieu Jolimaitre Date: Mon, 10 Jun 2024 11:03:54 +0200 Subject: [PATCH 2/2] fix profiling preventing progress --- src/lib/guesser/reducing.ts | 65 +++++++++++++++++++------------------ src/lib/prompt.ts | 13 ++++++-- src/lib/runner.ts | 7 ++-- src/manual_proxy.ts | 2 +- src/simulation.ts | 8 +---- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/lib/guesser/reducing.ts b/src/lib/guesser/reducing.ts index a29177c..aa7f090 100644 --- a/src/lib/guesser/reducing.ts +++ b/src/lib/guesser/reducing.ts @@ -6,11 +6,7 @@ import { GuessResult, Info } from "../game/game.ts"; import { info_of_guess } from "../game/simulator.ts"; import { Awaitable, dbg, enumerate, first, range, zip } from "../utils.ts"; import { Guessing, pick_random_common_word } from "./guesser.ts"; -import { - section, - tip, - top, -} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; +import { tip, top } from "https://git.barnulf.net/mb/profiterole/raw/branch/master/mod.ts"; export class ReducingGuesser implements Guessing { length; @@ -27,22 +23,27 @@ export class ReducingGuesser implements Guessing { return ["candidates", "best score"]; } - public async guess(try_: (guess: string, candidates: number, score: number) => Awaitable) { - return await section("guess", async () => { - if (this.candidates.size === 1) return await try_(first(this.candidates)!, this.candidates.size, 1); - const [guess, score] = get_word_with_smallest_cuts(this.candidates, this.words); - if (score >= this.candidates.size) { - const pick = pick_random_common_word([...this.candidates.values()]); - assertExists(pick); - const result = await try_(pick, this.candidates.size, score); - if (result.kind === "success") return result; - else return null; - } - const result = await try_(guess, this.candidates.size, score); + public async guess_impl(try_: (guess: string, candidates: number, score: number) => Awaitable) { + if (this.candidates.size === 1) return await try_(first(this.candidates)!, this.candidates.size, 1); + const [guess, score] = get_word_with_smallest_cuts(this.candidates, this.words); + if (score >= this.candidates.size) { + const pick = pick_random_common_word([...this.candidates.values()]); + assertExists(pick); + const result = await try_(pick, this.candidates.size, score); if (result.kind === "success") return result; - this.learn(guess, result.informations); - return null; - }); + else return null; + } + const result = await try_(guess, this.candidates.size, score); + if (result.kind === "success") return result; + this.learn(guess, result.informations); + return null; + } + + public async guess(try_: (guess: string, candidates: number, score: number) => Awaitable) { + tip("guess"); + const res = await this.guess_impl(try_); + top("guess"); + return res; } learn(word: string, infos: Info[]) { @@ -115,18 +116,18 @@ function word_cuts(word: string, dict: Set) { } function get_word_with_smallest_cuts(candidates: Set, dict: Set) { - return section("get_word_with_smallest_cuts", () => { - let best = null as null | [string, number]; - for (const candidate of dict.values()) { - const cuts = word_cuts(candidate, candidates); - let max_part_size = 0; - for (const cut of cuts) for (const part of cut) if (part.size > max_part_size) max_part_size = part.size; - if (best === null) best = [candidate, max_part_size]; - else if (max_part_size < best[1]) best = [candidate, max_part_size]; - } - assertExists(best); - return best; - }); + tip("get_word_with_smallest_cuts"); + let best = null as null | [string, number]; + for (const candidate of dict.values()) { + const cuts = word_cuts(candidate, candidates); + let max_part_size = 0; + for (const cut of cuts) for (const part of cut) if (part.size > max_part_size) max_part_size = part.size; + if (best === null) best = [candidate, max_part_size]; + else if (max_part_size < best[1]) best = [candidate, max_part_size]; + } + assertExists(best); + top("get_word_with_smallest_cuts"); + return best; } function matches_constraints(candidate: string, constraints: [string, Info][]) { diff --git a/src/lib/prompt.ts b/src/lib/prompt.ts index e377a74..80f866f 100644 --- a/src/lib/prompt.ts +++ b/src/lib/prompt.ts @@ -1,6 +1,8 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts"; -import { async_next, enumerate, LineReader, next } from "./utils.ts"; +import { enumerate, LineReader } from "./utils.ts"; +import { writeAll } from "https://deno.land/std@0.224.0/io/write_all.ts"; +import { Writer } from "https://deno.land/std@0.224.0/io/types.ts"; export async function initialize_prompt(lines: LineReader) { console.log("Please input initial state of the game. Format is :"); @@ -20,13 +22,13 @@ export async function initialize_prompt(lines: LineReader) { } async function parse_initialization_until_correct(lines: LineReader) { - console.log("initial : "); + print(Deno.stdout, "initial : "); while (true) { const input = await lines.read(); assertExists(input); const parsed = parse_initialization(input); if (parsed !== null) return parsed; - console.log("Invalid, please try again : "); + print(Deno.stdout, "Invalid, please try again : "); } } @@ -45,3 +47,8 @@ function parse_initialization(input: string) { } return { length, constraints }; } + +async function print(writer: Writer, ...rest: unknown[]) { + const text = rest.map((o) => `${o}`).join(" "); + await writeAll(writer, new TextEncoder().encode(text)); +} diff --git a/src/lib/runner.ts b/src/lib/runner.ts index bd4bcf0..5fa9229 100644 --- a/src/lib/runner.ts +++ b/src/lib/runner.ts @@ -3,10 +3,7 @@ import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists import { Guessing } from "./guesser/guesser.ts"; import { Gaming, GuessResult } from "./game/game.ts"; import { last, wait, zip } from "./utils.ts"; -import { - tip, - top, -} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; +import { tip, top } from "https://git.barnulf.net/mb/profiterole/raw/branch/master/mod.ts"; export class Runner { game; @@ -42,7 +39,7 @@ export class Runner { while (true) { const result = await this.play_once(); if (result !== null) break; - if (this.max !== undefined) { if (this.turns.length >= this.max) return this.turns; } + if (this.max !== undefined) if (this.turns.length >= this.max) return this.turns; await wait(this.delay_ms); } this.logging.on_finish(this.turns); diff --git a/src/manual_proxy.ts b/src/manual_proxy.ts index 93a99ea..6dce195 100755 --- a/src/manual_proxy.ts +++ b/src/manual_proxy.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno run --allow-read +#!/usr/bin/env -S deno run --allow-read --allow-env import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; diff --git a/src/simulation.ts b/src/simulation.ts index 30ebf50..0b965ee 100755 --- a/src/simulation.ts +++ b/src/simulation.ts @@ -1,13 +1,7 @@ #!/usr/bin/env -S deno run -A --unstable-temporal import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; -import { - report, - section, - startup, - tip, - top, -} from "https://git.barnulf.net/mb/profiterole/raw/commit/02d19fce2a0878abd176801cf8f2a663f6db6c16/mod.ts"; +import { report, tip, top } from "https://git.barnulf.net/mb/profiterole/raw/branch/master/mod.ts"; import { Dict,