diff --git a/src/bin/game/cli.rs b/src/bin/game/cli.rs index 1001e44..e8bbf14 100644 --- a/src/bin/game/cli.rs +++ b/src/bin/game/cli.rs @@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> { let mut guess: Word; loop { guess = get_word(&cli, game.step())?; - response = match game.guess(guess) { + response = match game.guess(guess, None) { Ok(r) => r, Err(err) => match err { GameError::GuessHasWrongLength(len) => { diff --git a/src/bin/solve/simple.rs b/src/bin/solve/simple.rs index 6c0ab5c..3e80e8f 100644 --- a/src/bin/solve/simple.rs +++ b/src/bin/solve/simple.rs @@ -2,11 +2,13 @@ // #![warn(missing_docs)] #![warn(missing_debug_implementations)] -use clap::Parser; +use clap::{Parser, Subcommand}; +use libpt::cli::console::style; use libpt::cli::{repl::Repl, strum}; use libpt::log::*; use strum::{EnumIter, IntoEnumIterator}; +use wordle_analyzer::game::response::Evaluation; use wordle_analyzer::game::{response::GuessResponse, Game, GameBuilder}; use wordle_analyzer::solve::{BuiltinSolverNames, Solver}; @@ -50,8 +52,11 @@ enum ReplCommand { /// /// 'c' means correct character Response { encoded: String }, - /// Let the user input a word they guessed - Guess { your_guess: String }, + /// Let the user input a word and the response for that word + Guess { + your_guess: String, + evalutation: Evaluation, + }, /// Let the solver make a guess Solve, /// Leave the Repl @@ -68,7 +73,7 @@ fn main() -> anyhow::Result<()> { if cli.non_interactive { play_native_non_interactive(cli)?; - exit(0); + std::process::exit(0); } help_guess_interactive(cli) } @@ -109,8 +114,11 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> { // 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)?) + ReplCommand::Guess { + your_guess, + evalutation, + } => { + println!("{}", game.guess(your_guess, Some(evalutation))?) } _ => todo!(), } diff --git a/src/game/mod.rs b/src/game/mod.rs index cec0e30..8ce9996 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,3 +1,5 @@ +use core::panic; + use crate::error::*; use crate::wlist::word::{ManyWordsRef, Word, WordData}; use crate::wlist::WordList; @@ -9,7 +11,7 @@ use response::GuessResponse; pub mod summary; -use self::response::Status; +use self::response::{Evaluation, Status}; #[derive(Debug, Clone, PartialEq)] pub struct Game<'wl, WL> @@ -78,7 +80,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> { /// /// This function will return an error if the length of the [Word] is wrong It will also error /// if the game is finished. - pub fn guess(&mut self, guess: Word) -> GameResult { + pub fn guess(&mut self, guess: Word, eval: Option) -> GameResult { if guess.len() != self.length { return Err(GameError::GuessHasWrongLength(guess.len())); } @@ -90,13 +92,20 @@ impl<'wl, WL: WordList> Game<'wl, WL> { } self.step += 1; - let response = GuessResponse::new(guess, evaluation, self); + let response; + if eval.is_some() && self.solution.is_none() { + response = GuessResponse::new(&guess, eval.unwrap(), self); + } else if let Some(solution) = self.solution.clone() { + response = GuessResponse::new(&guess, Self::evaluate(solution, &guess), self); + } else { + panic!("there is neither an evaluation nor a predefined solution for this guess"); + } self.responses.push(response.clone()); self.finished = response.finished(); Ok(response) } - pub fn evaluate(mut solution: WordData, guess: Word) -> Vec<()> { + pub fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation { let mut evaluation = Vec::new(); let mut status: Status; for (idx, c) in guess.chars().enumerate() { @@ -111,7 +120,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> { } evaluation.push((c, status)); } - todo!() + evaluation.into() } pub fn length(&self) -> usize { diff --git a/src/game/response.rs b/src/game/response.rs index 2395e78..966c9a0 100644 --- a/src/game/response.rs +++ b/src/game/response.rs @@ -3,7 +3,9 @@ use crate::wlist::WordList; use colored::Colorize; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use std::convert::Infallible; use std::fmt::Display; +use std::str::FromStr; use super::Game; @@ -13,15 +15,42 @@ pub struct AtomicEvaluation { char: char, status: Status, } -pub type Evaluation = Vec<(char, Status)>; +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Evaluation { + inner: Vec, +} +pub type EvaluationUnit = (char, Status); + +impl IntoIterator for Evaluation { + type Item = EvaluationUnit; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl From> for Evaluation { + fn from(value: Vec) -> Self { + Self { inner: value } + } +} + +impl FromStr for Evaluation { + type Err = Infallible; + fn from_str(s: &str) -> Result { + // TODO: make this proper + Ok(vec![('x', Status::None)].into()) + } +} #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GuessResponse { guess: Word, evaluation: Evaluation, finish: bool, - solution: WordData, + solution: Option, step: usize, max_steps: usize, } @@ -35,24 +64,16 @@ pub enum Status { } impl GuessResponse { - pub(crate) fn new( - guess: Word, - status: Vec<(char, Status)>, - game: &Game, - ) -> Self { - let finish: bool = if game.step() > game.max_steps() { - true - } else { - guess == game.solution().0 - }; - Self { - guess, + pub(crate) fn new(guess: &Word, status: Evaluation, game: &Game) -> Self { + let new = Self { + guess: guess.to_owned(), evaluation: status, - finish, - solution: game.solution().clone(), + finish: game.step() > game.max_steps(), + solution: game.solution().cloned(), step: game.step(), max_steps: game.max_steps(), - } + }; + new } pub fn finished(&self) -> bool { @@ -60,18 +81,18 @@ impl GuessResponse { } pub fn won(&self) -> bool { - self.guess == self.solution.0 + let mut ok = true; + for i in self.evaluation.clone().into_iter() { + ok &= i.1 == Status::Matched + } + ok } pub fn solution(&self) -> Option { - if self.won() { - Some(self.solution.clone()) - } else { - None - } + self.solution.clone() } - pub fn evaluation(&self) -> &[(char, Status)] { + pub fn evaluation(&self) -> &Evaluation { &self.evaluation } @@ -90,7 +111,7 @@ impl GuessResponse { impl Display for GuessResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for s in &self.evaluation { + for s in self.evaluation.clone().into_iter() { write!( f, "{}", diff --git a/src/solve/mod.rs b/src/solve/mod.rs index d398f4f..5ef34c9 100644 --- a/src/solve/mod.rs +++ b/src/solve/mod.rs @@ -51,7 +51,7 @@ pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized + Sync { /// /// This function will return an error if [guess_for](Solver::guess_for) fails. fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult { - Ok(game.guess(self.guess_for(game))?) + Ok(game.guess(self.guess_for(game), None)?) } /// Play a [Game] and return the last [GuessResponse]. /// diff --git a/src/solve/naive/mod.rs b/src/solve/naive/mod.rs index bfe7625..4a01e9a 100644 --- a/src/solve/naive/mod.rs +++ b/src/solve/naive/mod.rs @@ -21,7 +21,13 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> { let mut other_chars: Vec = Vec::new(); let response = game.last_response(); if response.is_some() { - for (idx, p) in response.unwrap().evaluation().iter().enumerate() { + for (idx, p) in response + .unwrap() + .evaluation() + .clone() + .into_iter() + .enumerate() + { if p.1 == Status::Matched { pattern.replace_range(idx..idx + 1, &p.0.to_string()); } else if p.1 == Status::Exists {