From 6a9a99aa5113776a9f9ed3a1666e769db0257c69 Mon Sep 17 00:00:00 2001 From: "Christoph J. Scherr" Date: Mon, 22 Jul 2024 14:11:29 +0200 Subject: [PATCH] refactor(solve): try to hack the idea of "idk the solution yet" into the game --- Cargo.toml | 1 + src/bin/solve/simple.rs | 86 +++++++++++++++++++++++++++++++++++++++-- src/game/mod.rs | 44 +++++++++++---------- 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aadc5a2..7f7dd9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ rayon = "1.10.0" regex = "1.10.3" serde = { version = "1.0.197", optional = true, features = ["serde_derive"] } serde_json = { version = "1.0.114", optional = true } +strum = "0.26.3" # serde_with = "3.7.0" thiserror = "1.0.58" diff --git a/src/bin/solve/simple.rs b/src/bin/solve/simple.rs index 0219d70..17784fc 100644 --- a/src/bin/solve/simple.rs +++ b/src/bin/solve/simple.rs @@ -2,15 +2,19 @@ // #![warn(missing_docs)] #![warn(missing_debug_implementations)] +use std::process::exit; + use clap::{Parser, Subcommand}; use libpt::cli::{ + console::style, + indicatif, repl::{DefaultRepl, Repl}, strum, }; use libpt::log::*; -use strum::IntoEnumIterator; +use strum::{EnumIter, IntoEnumIterator}; -use wordle_analyzer::game::response::GuessResponse; +use wordle_analyzer::game::{response::GuessResponse, Game, GameBuilder}; use wordle_analyzer::solve::{BuiltinSolverNames, Solver}; use wordle_analyzer::wlist::builtin::BuiltinWList; @@ -35,6 +39,30 @@ struct Cli { /// which solver to use #[arg(short, long, default_value_t = BuiltinSolverNames::default())] solver: BuiltinSolverNames, + + /// set if the solver should play a full native game without interaction + #[arg(short, long)] + non_interactive: bool, +} + +#[derive(Subcommand, Debug, EnumIter, Clone)] +enum ReplCommand { + /// Let the user input the response to the last guess + /// + /// Format: + /// + /// 'x' means wrong character + /// + /// 'p' means present character + /// + /// 'c' means correct character + Response { encoded: String }, + /// Let the user input a word they guessed + Guess { your_guess: String }, + /// Let the solver make a guess + Solve, + /// Leave the Repl + Exit, } fn main() -> anyhow::Result<()> { @@ -45,8 +73,59 @@ fn main() -> anyhow::Result<()> { .unwrap(); trace!("dumping CLI: {:#?}", cli); - // let repl = libpt::cli::repl::DefaultRepl::::default(); + if cli.non_interactive { + play_native_non_interactive(cli)?; + exit(0); + } + help_guess_interactive(cli) +} +fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> { + let wl = BuiltinWList::default(); + let builder = game::Game::builder(&wl) + .length(cli.length) + .max_steps(cli.max_steps) + .precompute(cli.precompute); + let solver = cli.solver.to_solver(&wl); + let mut game = builder.build()?; + + let mut repl = libpt::cli::repl::DefaultRepl::::default(); + + debug!("entering the repl"); + loop { + // repl.step() should be at the start of your loop + // It is here that the repl will get the user input, validate it, and so on + match repl.step() { + Ok(c) => c, + Err(e) => { + // if the user requested the help, print in blue, otherwise in red as it's just an + // error + if let libpt::cli::repl::error::Error::Parsing(e) = &e { + if e.kind() == clap::error::ErrorKind::DisplayHelp { + println!("{}", style(e).cyan()); + continue; + } + } + println!("{}", style(e).red().bold()); + continue; + } + }; + + // now we can match our defined commands + // + // only None if the repl has not stepped yet + match repl.command().to_owned().unwrap() { + ReplCommand::Exit => break, + ReplCommand::Guess { your_guess } => { + println!("{}", game.guess(your_guess)?) + } + _ => todo!(), + } + } + Ok(()) +} + +fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> { let wl = BuiltinWList::default(); let builder = game::Game::builder(&wl) .length(cli.length) @@ -72,6 +151,5 @@ fn main() -> anyhow::Result<()> { } else { println!("You lose! The solution was {:?}.", game.solution()); } - Ok(()) } diff --git a/src/game/mod.rs b/src/game/mod.rs index e61f1ce..cec0e30 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -20,7 +20,7 @@ where precompute: bool, max_steps: usize, step: usize, - solution: WordData, + solution: Option, wordlist: &'wl WL, finished: bool, responses: Vec, @@ -57,7 +57,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> { precompute, max_steps, step: 0, - solution, + solution: Some(solution), wordlist: wlist, finished: false, responses: Vec::new(), @@ -72,6 +72,8 @@ impl<'wl, WL: WordList> Game<'wl, WL> { /// A [GuessResponse] will be formulated, showing us which letters are correctly placed, in the /// solution, or just wrong. /// + /// Note that you do not need to use the [GuessResponse], it is appended to the game state. + /// /// # Errors /// /// This function will return an error if the length of the [Word] is wrong It will also error @@ -88,34 +90,36 @@ impl<'wl, WL: WordList> Game<'wl, WL> { } self.step += 1; - let mut compare_solution = self.solution.0.clone(); - let mut evaluation = Vec::new(); - let mut status: Status; - for (idx, c) in guess.chars().enumerate() { - if compare_solution.chars().nth(idx) == Some(c) { - status = Status::Matched; - compare_solution.replace_range(idx..idx + 1, "_"); - } else if compare_solution.contains(c) { - status = Status::Exists; - compare_solution = compare_solution.replacen(c, "_", 1); - } else { - status = Status::None - } - evaluation.push((c, status)); - } - let response = GuessResponse::new(guess, evaluation, self); self.responses.push(response.clone()); self.finished = response.finished(); Ok(response) } + pub fn evaluate(mut solution: WordData, guess: Word) -> Vec<()> { + let mut evaluation = Vec::new(); + let mut status: Status; + for (idx, c) in guess.chars().enumerate() { + if solution.0.chars().nth(idx) == Some(c) { + status = Status::Matched; + solution.0.replace_range(idx..idx + 1, "_"); + } else if solution.0.contains(c) { + status = Status::Exists; + solution.0 = solution.0.replacen(c, "_", 1); + } else { + status = Status::None + } + evaluation.push((c, status)); + } + todo!() + } + pub fn length(&self) -> usize { self.length } - pub fn solution(&self) -> &WordData { - &self.solution + pub fn solution(&self) -> Option<&WordData> { + self.solution.as_ref() } pub fn step(&self) -> usize {