refactor(solve): try to hack the idea of "idk the solution yet" into the game

This commit is contained in:
Christoph J. Scherr 2024-07-22 14:11:29 +02:00
parent 44ed7210c4
commit 6a9a99aa51
3 changed files with 107 additions and 24 deletions

View File

@ -34,6 +34,7 @@ rayon = "1.10.0"
regex = "1.10.3" regex = "1.10.3"
serde = { version = "1.0.197", optional = true, features = ["serde_derive"] } serde = { version = "1.0.197", optional = true, features = ["serde_derive"] }
serde_json = { version = "1.0.114", optional = true } serde_json = { version = "1.0.114", optional = true }
strum = "0.26.3"
# serde_with = "3.7.0" # serde_with = "3.7.0"
thiserror = "1.0.58" thiserror = "1.0.58"

View File

@ -2,15 +2,19 @@
// #![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
use std::process::exit;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use libpt::cli::{ use libpt::cli::{
console::style,
indicatif,
repl::{DefaultRepl, Repl}, repl::{DefaultRepl, Repl},
strum, strum,
}; };
use libpt::log::*; 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::solve::{BuiltinSolverNames, Solver};
use wordle_analyzer::wlist::builtin::BuiltinWList; use wordle_analyzer::wlist::builtin::BuiltinWList;
@ -35,6 +39,30 @@ struct Cli {
/// which solver to use /// which solver to use
#[arg(short, long, default_value_t = BuiltinSolverNames::default())] #[arg(short, long, default_value_t = BuiltinSolverNames::default())]
solver: BuiltinSolverNames, 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<()> { fn main() -> anyhow::Result<()> {
@ -45,8 +73,59 @@ fn main() -> anyhow::Result<()> {
.unwrap(); .unwrap();
trace!("dumping CLI: {:#?}", cli); trace!("dumping CLI: {:#?}", cli);
// let repl = libpt::cli::repl::DefaultRepl::<ReplCommand>::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::<ReplCommand>::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 wl = BuiltinWList::default();
let builder = game::Game::builder(&wl) let builder = game::Game::builder(&wl)
.length(cli.length) .length(cli.length)
@ -72,6 +151,5 @@ fn main() -> anyhow::Result<()> {
} else { } else {
println!("You lose! The solution was {:?}.", game.solution()); println!("You lose! The solution was {:?}.", game.solution());
} }
Ok(()) Ok(())
} }

View File

@ -20,7 +20,7 @@ where
precompute: bool, precompute: bool,
max_steps: usize, max_steps: usize,
step: usize, step: usize,
solution: WordData, solution: Option<WordData>,
wordlist: &'wl WL, wordlist: &'wl WL,
finished: bool, finished: bool,
responses: Vec<GuessResponse>, responses: Vec<GuessResponse>,
@ -57,7 +57,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
precompute, precompute,
max_steps, max_steps,
step: 0, step: 0,
solution, solution: Some(solution),
wordlist: wlist, wordlist: wlist,
finished: false, finished: false,
responses: Vec::new(), 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 /// A [GuessResponse] will be formulated, showing us which letters are correctly placed, in the
/// solution, or just wrong. /// solution, or just wrong.
/// ///
/// Note that you do not need to use the [GuessResponse], it is appended to the game state.
///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the length of the [Word] is wrong It will also error /// 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; 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); let response = GuessResponse::new(guess, evaluation, self);
self.responses.push(response.clone()); self.responses.push(response.clone());
self.finished = response.finished(); self.finished = response.finished();
Ok(response) 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 { pub fn length(&self) -> usize {
self.length self.length
} }
pub fn solution(&self) -> &WordData { pub fn solution(&self) -> Option<&WordData> {
&self.solution self.solution.as_ref()
} }
pub fn step(&self) -> usize { pub fn step(&self) -> usize {