generated from PlexSheep/rs-base
fix(evaluation): evaluation did not get built from string correctly #9
cargo devel CI / cargo CI (push) Successful in 1m58s
Details
cargo devel CI / cargo CI (push) Successful in 1m58s
Details
This commit is contained in:
parent
713a661cc5
commit
18a5125028
|
@ -37,6 +37,7 @@ serde_json = { version = "1.0.114", optional = true }
|
||||||
strum = "0.26.3"
|
strum = "0.26.3"
|
||||||
# serde_with = "3.7.0"
|
# serde_with = "3.7.0"
|
||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
|
tracing-test = "0.2.5"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "wordlec"
|
name = "wordlec"
|
||||||
|
@ -57,3 +58,6 @@ required-features = ["solve", "cli", "builtin"]
|
||||||
name = "wordlebench"
|
name = "wordlebench"
|
||||||
path = "src/bin/bench/cli.rs"
|
path = "src/bin/bench/cli.rs"
|
||||||
required-features = ["solve", "cli", "bench", "builtin"]
|
required-features = ["solve", "cli", "bench", "builtin"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
test-log = { version = "0.2.16", default-features = false, features = ["color", "trace"] }
|
||||||
|
|
|
@ -45,7 +45,6 @@ where
|
||||||
// TODO: add some interface to get reports while the benchmark runs
|
// TODO: add some interface to get reports while the benchmark runs
|
||||||
// TODO: make the benchmark optionally multithreaded
|
// TODO: make the benchmark optionally multithreaded
|
||||||
// NOTE: This is blocking, use start to let it run in another thread
|
// NOTE: This is blocking, use start to let it run in another thread
|
||||||
// FIXME: this never stops? Reports just keep getting printed
|
|
||||||
fn bench(
|
fn bench(
|
||||||
&self,
|
&self,
|
||||||
n: usize,
|
n: usize,
|
||||||
|
|
|
@ -75,7 +75,7 @@ enum ReplCommand {
|
||||||
/// is correct
|
/// is correct
|
||||||
Guess {
|
Guess {
|
||||||
your_guess: String,
|
your_guess: String,
|
||||||
evalutation: Evaluation,
|
evalutation: String,
|
||||||
},
|
},
|
||||||
/// Let the solver make a guess
|
/// Let the solver make a guess
|
||||||
Solve,
|
Solve,
|
||||||
|
@ -163,7 +163,9 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
|
||||||
your_guess,
|
your_guess,
|
||||||
evalutation,
|
evalutation,
|
||||||
} => {
|
} => {
|
||||||
let guess = game.guess(your_guess, Some(evalutation));
|
let evaluation_converted: Evaluation =
|
||||||
|
Evaluation::build(&your_guess, &evalutation)?;
|
||||||
|
let guess = game.guess(your_guess, Some(evaluation_converted));
|
||||||
debug!("your guess: {guess:?}");
|
debug!("your guess: {guess:?}");
|
||||||
if guess.is_err() {
|
if guess.is_err() {
|
||||||
eprintln!("{}", style(guess.unwrap_err()).red().bold());
|
eprintln!("{}", style(guess.unwrap_err()).red().bold());
|
||||||
|
|
|
@ -51,6 +51,10 @@ pub enum GameError {
|
||||||
TryingToPlayAFinishedGame,
|
TryingToPlayAFinishedGame,
|
||||||
#[error("Tried to guess or use a word that is not in the wordlist ({0})")]
|
#[error("Tried to guess or use a word that is not in the wordlist ({0})")]
|
||||||
WordNotInWordlist(Word),
|
WordNotInWordlist(Word),
|
||||||
|
#[error("Invalid syntax for manual evaluation creation")]
|
||||||
|
InvalidEvaluationSyntax(String),
|
||||||
|
#[error("The length of guess and evaluation must be the same")]
|
||||||
|
GuessAndEvalNotSameLen((String, String)),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
#[derive(Debug, Clone, Error)]
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
use std::convert::Infallible;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use libpt::cli::console::{style, StyledObject};
|
use libpt::cli::console::{style, StyledObject};
|
||||||
|
|
||||||
use crate::wlist::word::Word;
|
use crate::wlist::word::Word;
|
||||||
|
|
||||||
use super::response::Status;
|
use super::response::Status;
|
||||||
|
use super::{GameError, WResult};
|
||||||
|
|
||||||
|
/// the [char] of the guess and the [Status] associated with it
|
||||||
pub type EvaluationUnit = (char, Status);
|
pub type EvaluationUnit = (char, Status);
|
||||||
|
|
||||||
|
/// Basically a [String] with extra information associated with each char
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
pub struct Evaluation {
|
pub struct Evaluation {
|
||||||
inner: Vec<EvaluationUnit>,
|
inner: Vec<EvaluationUnit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evaluation {
|
impl Evaluation {
|
||||||
pub(crate) fn colorized_display(&self, guess: &Word) -> Vec<StyledObject<String>> {
|
/// Display the evaluation color coded
|
||||||
assert_eq!(guess.len(), self.inner.len());
|
pub fn colorized_display(&self) -> Vec<StyledObject<String>> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
for (i, e) in self.inner.iter().enumerate() {
|
for e in self.inner.iter() {
|
||||||
let mut c = style(guess.chars().nth(i).unwrap().to_string());
|
let mut c = style(e.0.to_string());
|
||||||
if e.1 == Status::Matched {
|
if e.1 == Status::Matched {
|
||||||
c = c.green();
|
c = c.green();
|
||||||
} else if e.1 == Status::Exists {
|
} else if e.1 == Status::Exists {
|
||||||
|
@ -29,6 +29,45 @@ impl Evaluation {
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The first string is the word the evaluation is for, The second string defines how the
|
||||||
|
/// characters of the first string match the solution.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// ## Evaluation Format:
|
||||||
|
///
|
||||||
|
/// 'x' means wrong character
|
||||||
|
///
|
||||||
|
/// 'p' means present character
|
||||||
|
///
|
||||||
|
/// 'c' means correct character
|
||||||
|
///
|
||||||
|
/// ### Example:
|
||||||
|
///
|
||||||
|
/// 'xxxcc' --- means the first 3 chars are wrong but the second 2 chars are correct
|
||||||
|
///
|
||||||
|
/// 'xppxc' --- means the first character is wrong, the next two characters are present, the last
|
||||||
|
/// is correct
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// "wordle xxxcff" --- the guess was wordle, the d is in the correct spot, the solution
|
||||||
|
/// contains 'l' and 'e', but on another index.
|
||||||
|
///
|
||||||
|
pub fn build(guess: &Word, eval_str: &str) -> WResult<Self> {
|
||||||
|
if guess.len() != eval_str.len() {
|
||||||
|
return Err(GameError::GuessAndEvalNotSameLen((
|
||||||
|
guess.to_string(),
|
||||||
|
eval_str.to_string(),
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
let mut v: Vec<EvaluationUnit> = Vec::new();
|
||||||
|
for (c, e) in guess.chars().zip(eval_str.chars()) {
|
||||||
|
v.push((c, Status::from(e)))
|
||||||
|
}
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Evaluation {
|
impl IntoIterator for Evaluation {
|
||||||
|
@ -46,19 +85,8 @@ impl From<Vec<EvaluationUnit>> for Evaluation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Evaluation {
|
impl From<Evaluation> for Word {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: Evaluation) -> Self {
|
||||||
Self::from_str(value).unwrap()
|
Word::from(value.inner.into_iter().map(|v| v.0).collect::<String>())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Evaluation {
|
|
||||||
type Err = Infallible;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut v: Vec<EvaluationUnit> = Vec::new();
|
|
||||||
for c in s.chars() {
|
|
||||||
v.push((c, Status::from(c)))
|
|
||||||
}
|
|
||||||
Ok(v.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,7 +320,7 @@ impl<'wl, WL: WordList> Display for Game<'wl, WL> {
|
||||||
for s in self
|
for s in self
|
||||||
.responses()
|
.responses()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| v.evaluation().to_owned().colorized_display(v.guess()))
|
.map(|v| v.evaluation().to_owned().colorized_display())
|
||||||
{
|
{
|
||||||
write!(f, "\"")?;
|
write!(f, "\"")?;
|
||||||
for si in s {
|
for si in s {
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized + Sync {
|
||||||
///
|
///
|
||||||
/// This function will return an error if [make_a_move](Solver::make_a_move) fails.
|
/// This function will return an error if [make_a_move](Solver::make_a_move) fails.
|
||||||
fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
|
fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
|
||||||
|
// TODO: check if the game is finished already and return an Err if so
|
||||||
let mut resp: GuessResponse;
|
let mut resp: GuessResponse;
|
||||||
loop {
|
loop {
|
||||||
resp = self.make_a_move(game)?;
|
resp = self.make_a_move(game)?;
|
||||||
|
|
|
@ -16,6 +16,15 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
info!("using naive solver");
|
info!("using naive solver");
|
||||||
Ok(Self { wl: wordlist })
|
Ok(Self { wl: wordlist })
|
||||||
}
|
}
|
||||||
|
/// Guess a word from the wordlist for the given game
|
||||||
|
///
|
||||||
|
/// ## Algorithm
|
||||||
|
///
|
||||||
|
/// * Look at the evaluation for the last response and keep the correct letters
|
||||||
|
/// * Get all words that have these letters at the right position
|
||||||
|
/// * Discard words that have already been tried
|
||||||
|
/// * Discard all words that don't have the chars that we know from the last guess are in the
|
||||||
|
/// word, but don't know the position of.
|
||||||
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
|
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
|
||||||
// HACK: hardcoded length
|
// HACK: hardcoded length
|
||||||
let mut pattern: String = String::from(".....");
|
let mut pattern: String = String::from(".....");
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use test_log::test; // set the log level with an envvar: `RUST_LOG=trace cargo test`
|
||||||
|
|
||||||
|
use wordle_analyzer::game::evaluation::Evaluation;
|
||||||
use wordle_analyzer::game::Game;
|
use wordle_analyzer::game::Game;
|
||||||
use wordle_analyzer::solve::{AnyBuiltinSolver, NaiveSolver, Solver, StupidSolver};
|
use wordle_analyzer::solve::{AnyBuiltinSolver, NaiveSolver, Solver, StupidSolver};
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||||
|
@ -69,7 +72,7 @@ fn test_naive_play_predetermined_game() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_naive_play_predetermined_game_by_manual_guess_and_eval() -> anyhow::Result<()> {
|
fn test_naive_play_predetermined_game_manually() -> anyhow::Result<()> {
|
||||||
let wl = wordlist();
|
let wl = wordlist();
|
||||||
let sl =
|
let sl =
|
||||||
AnyBuiltinSolver::Naive(NaiveSolver::build(&wl).expect("could not build naive solver"));
|
AnyBuiltinSolver::Naive(NaiveSolver::build(&wl).expect("could not build naive solver"));
|
||||||
|
@ -77,35 +80,56 @@ fn test_naive_play_predetermined_game_by_manual_guess_and_eval() -> anyhow::Resu
|
||||||
// pretend that a user inputs guesses manually
|
// pretend that a user inputs guesses manually
|
||||||
let mut game = Game::build(5, false, 6, &wl, false)?;
|
let mut game = Game::build(5, false, 6, &wl, false)?;
|
||||||
let _actual_solution: Option<WordData> = Some(("nines".into(), 0.002));
|
let _actual_solution: Option<WordData> = Some(("nines".into(), 0.002));
|
||||||
let mut next_guess;
|
let mut next_guess: Word;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("which"));
|
assert_eq!(next_guess, Word::from("which"));
|
||||||
game.guess(next_guess, Some("xxfxx".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xxfxx")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("their"));
|
assert_eq!(next_guess, Word::from("their"));
|
||||||
game.guess(next_guess, Some("xxffx".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xxffx")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("being"));
|
assert_eq!(next_guess, Word::from("being"));
|
||||||
game.guess(next_guess, Some("xfffx".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xfffx")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("since"));
|
assert_eq!(next_guess, Word::from("since"));
|
||||||
game.guess(next_guess, Some("fcfxf".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "fcfxf")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("lines"));
|
assert_eq!(next_guess, Word::from("lines"));
|
||||||
game.guess(next_guess, Some("xcccc".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xcccc")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("mines"));
|
assert_eq!(next_guess, Word::from("mines"));
|
||||||
game.guess(next_guess, Some("xcccc".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xcccc")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
next_guess = sl.guess_for(&game)?;
|
next_guess = sl.guess_for(&game)?;
|
||||||
assert_eq!(next_guess, Word::from("wines"));
|
assert_eq!(next_guess, Word::from("wines"));
|
||||||
game.guess(next_guess, Some("xcccc".into()))?;
|
game.guess(
|
||||||
|
next_guess.clone(),
|
||||||
|
Some(Evaluation::build(&next_guess, "xcccc")?),
|
||||||
|
)?;
|
||||||
|
|
||||||
// naive is at the moment too bad to solve "nines"
|
// naive is at the moment too bad to solve "nines"
|
||||||
assert!(game.finished());
|
assert!(game.finished());
|
||||||
|
|
Loading…
Reference in New Issue