refactor: game without solution seems to become possible?
cargo devel CI / cargo CI (push) Successful in 1m54s Details

This commit is contained in:
Christoph J. Scherr 2024-07-22 14:47:11 +02:00
parent e9a2223fc0
commit 1f8b85a152
6 changed files with 84 additions and 40 deletions

View File

@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> {
let mut guess: Word; let mut guess: Word;
loop { loop {
guess = get_word(&cli, game.step())?; guess = get_word(&cli, game.step())?;
response = match game.guess(guess) { response = match game.guess(guess, None) {
Ok(r) => r, Ok(r) => r,
Err(err) => match err { Err(err) => match err {
GameError::GuessHasWrongLength(len) => { GameError::GuessHasWrongLength(len) => {

View File

@ -2,11 +2,13 @@
// #![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
use clap::Parser; use clap::{Parser, Subcommand};
use libpt::cli::console::style;
use libpt::cli::{repl::Repl, strum}; use libpt::cli::{repl::Repl, strum};
use libpt::log::*; use libpt::log::*;
use strum::{EnumIter, IntoEnumIterator}; use strum::{EnumIter, IntoEnumIterator};
use wordle_analyzer::game::response::Evaluation;
use wordle_analyzer::game::{response::GuessResponse, Game, GameBuilder}; use wordle_analyzer::game::{response::GuessResponse, Game, GameBuilder};
use wordle_analyzer::solve::{BuiltinSolverNames, Solver}; use wordle_analyzer::solve::{BuiltinSolverNames, Solver};
@ -50,8 +52,11 @@ enum ReplCommand {
/// ///
/// 'c' means correct character /// 'c' means correct character
Response { encoded: String }, Response { encoded: String },
/// Let the user input a word they guessed /// Let the user input a word and the response for that word
Guess { your_guess: String }, Guess {
your_guess: String,
evalutation: Evaluation,
},
/// Let the solver make a guess /// Let the solver make a guess
Solve, Solve,
/// Leave the Repl /// Leave the Repl
@ -68,7 +73,7 @@ fn main() -> anyhow::Result<()> {
if cli.non_interactive { if cli.non_interactive {
play_native_non_interactive(cli)?; play_native_non_interactive(cli)?;
exit(0); std::process::exit(0);
} }
help_guess_interactive(cli) 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 // only None if the repl has not stepped yet
match repl.command().to_owned().unwrap() { match repl.command().to_owned().unwrap() {
ReplCommand::Exit => break, ReplCommand::Exit => break,
ReplCommand::Guess { your_guess } => { ReplCommand::Guess {
println!("{}", game.guess(your_guess)?) your_guess,
evalutation,
} => {
println!("{}", game.guess(your_guess, Some(evalutation))?)
} }
_ => todo!(), _ => todo!(),
} }

View File

@ -1,3 +1,5 @@
use core::panic;
use crate::error::*; use crate::error::*;
use crate::wlist::word::{ManyWordsRef, Word, WordData}; use crate::wlist::word::{ManyWordsRef, Word, WordData};
use crate::wlist::WordList; use crate::wlist::WordList;
@ -9,7 +11,7 @@ use response::GuessResponse;
pub mod summary; pub mod summary;
use self::response::Status; use self::response::{Evaluation, Status};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Game<'wl, WL> 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 /// This function will return an error if the length of the [Word] is wrong It will also error
/// if the game is finished. /// if the game is finished.
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> { pub fn guess(&mut self, guess: Word, eval: Option<Evaluation>) -> GameResult<GuessResponse> {
if guess.len() != self.length { if guess.len() != self.length {
return Err(GameError::GuessHasWrongLength(guess.len())); return Err(GameError::GuessHasWrongLength(guess.len()));
} }
@ -90,13 +92,20 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
} }
self.step += 1; 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.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<()> { pub fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation {
let mut evaluation = Vec::new(); let mut evaluation = Vec::new();
let mut status: Status; let mut status: Status;
for (idx, c) in guess.chars().enumerate() { for (idx, c) in guess.chars().enumerate() {
@ -111,7 +120,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
} }
evaluation.push((c, status)); evaluation.push((c, status));
} }
todo!() evaluation.into()
} }
pub fn length(&self) -> usize { pub fn length(&self) -> usize {

View File

@ -3,7 +3,9 @@ use crate::wlist::WordList;
use colored::Colorize; use colored::Colorize;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr;
use super::Game; use super::Game;
@ -13,15 +15,42 @@ pub struct AtomicEvaluation {
char: char, char: char,
status: Status, status: Status,
} }
pub type Evaluation = Vec<(char, Status)>; #[derive(Debug, Clone, PartialEq, Default)]
pub struct Evaluation {
inner: Vec<EvaluationUnit>,
}
pub type EvaluationUnit = (char, Status);
impl IntoIterator for Evaluation {
type Item = EvaluationUnit;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl From<Vec<EvaluationUnit>> for Evaluation {
fn from(value: Vec<EvaluationUnit>) -> Self {
Self { inner: value }
}
}
impl FromStr for Evaluation {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// TODO: make this proper
Ok(vec![('x', Status::None)].into())
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] // #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GuessResponse { pub struct GuessResponse {
guess: Word, guess: Word,
evaluation: Evaluation, evaluation: Evaluation,
finish: bool, finish: bool,
solution: WordData, solution: Option<WordData>,
step: usize, step: usize,
max_steps: usize, max_steps: usize,
} }
@ -35,24 +64,16 @@ pub enum Status {
} }
impl GuessResponse { impl GuessResponse {
pub(crate) fn new<WL: WordList>( pub(crate) fn new<WL: WordList>(guess: &Word, status: Evaluation, game: &Game<WL>) -> Self {
guess: Word, let new = Self {
status: Vec<(char, Status)>, guess: guess.to_owned(),
game: &Game<WL>,
) -> Self {
let finish: bool = if game.step() > game.max_steps() {
true
} else {
guess == game.solution().0
};
Self {
guess,
evaluation: status, evaluation: status,
finish, finish: game.step() > game.max_steps(),
solution: game.solution().clone(), solution: game.solution().cloned(),
step: game.step(), step: game.step(),
max_steps: game.max_steps(), max_steps: game.max_steps(),
} };
new
} }
pub fn finished(&self) -> bool { pub fn finished(&self) -> bool {
@ -60,18 +81,18 @@ impl GuessResponse {
} }
pub fn won(&self) -> bool { 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<WordData> { pub fn solution(&self) -> Option<WordData> {
if self.won() { self.solution.clone()
Some(self.solution.clone())
} else {
None
}
} }
pub fn evaluation(&self) -> &[(char, Status)] { pub fn evaluation(&self) -> &Evaluation {
&self.evaluation &self.evaluation
} }
@ -90,7 +111,7 @@ impl GuessResponse {
impl Display for GuessResponse { impl Display for GuessResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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!( write!(
f, f,
"{}", "{}",

View File

@ -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. /// This function will return an error if [guess_for](Solver::guess_for) fails.
fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> { fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
Ok(game.guess(self.guess_for(game))?) Ok(game.guess(self.guess_for(game), None)?)
} }
/// Play a [Game] and return the last [GuessResponse]. /// Play a [Game] and return the last [GuessResponse].
/// ///

View File

@ -21,7 +21,13 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
let mut other_chars: Vec<char> = Vec::new(); let mut other_chars: Vec<char> = Vec::new();
let response = game.last_response(); let response = game.last_response();
if response.is_some() { 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 { if p.1 == Status::Matched {
pattern.replace_range(idx..idx + 1, &p.0.to_string()); pattern.replace_range(idx..idx + 1, &p.0.to_string());
} else if p.1 == Status::Exists { } else if p.1 == Status::Exists {