generated from PlexSheep/rs-base
add interactive solver #4
|
@ -37,6 +37,7 @@ serde_json = { version = "1.0.114", optional = true }
|
|||
strum = "0.26.3"
|
||||
# serde_with = "3.7.0"
|
||||
thiserror = "1.0.58"
|
||||
tracing-test = "0.2.5"
|
||||
|
||||
[[bin]]
|
||||
name = "wordlec"
|
||||
|
@ -57,3 +58,6 @@ required-features = ["solve", "cli", "builtin"]
|
|||
name = "wordlebench"
|
||||
path = "src/bin/bench/cli.rs"
|
||||
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: make the benchmark optionally multithreaded
|
||||
// NOTE: This is blocking, use start to let it run in another thread
|
||||
// FIXME: this never stops? Reports just keep getting printed
|
||||
fn bench(
|
||||
&self,
|
||||
n: usize,
|
||||
|
|
|
@ -75,7 +75,7 @@ enum ReplCommand {
|
|||
/// is correct
|
||||
Guess {
|
||||
your_guess: String,
|
||||
evalutation: Evaluation,
|
||||
evalutation: String,
|
||||
},
|
||||
/// Let the solver make a guess
|
||||
Solve,
|
||||
|
@ -163,7 +163,9 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
|
|||
your_guess,
|
||||
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:?}");
|
||||
if guess.is_err() {
|
||||
eprintln!("{}", style(guess.unwrap_err()).red().bold());
|
||||
|
|
|
@ -51,6 +51,10 @@ pub enum GameError {
|
|||
TryingToPlayAFinishedGame,
|
||||
#[error("Tried to guess or use a word that is not in the wordlist ({0})")]
|
||||
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)]
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
use libpt::cli::console::{style, StyledObject};
|
||||
|
||||
use crate::wlist::word::Word;
|
||||
|
||||
use super::response::Status;
|
||||
use super::{GameError, WResult};
|
||||
|
||||
/// the [char] of the guess and the [Status] associated with it
|
||||
pub type EvaluationUnit = (char, Status);
|
||||
|
||||
/// Basically a [String] with extra information associated with each char
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Evaluation {
|
||||
inner: Vec<EvaluationUnit>,
|
||||
}
|
||||
|
||||
impl Evaluation {
|
||||
pub(crate) fn colorized_display(&self, guess: &Word) -> Vec<StyledObject<String>> {
|
||||
assert_eq!(guess.len(), self.inner.len());
|
||||
/// Display the evaluation color coded
|
||||
pub fn colorized_display(&self) -> Vec<StyledObject<String>> {
|
||||
let mut buf = Vec::new();
|
||||
for (i, e) in self.inner.iter().enumerate() {
|
||||
let mut c = style(guess.chars().nth(i).unwrap().to_string());
|
||||
for e in self.inner.iter() {
|
||||
let mut c = style(e.0.to_string());
|
||||
if e.1 == Status::Matched {
|
||||
c = c.green();
|
||||
} else if e.1 == Status::Exists {
|
||||
|
@ -29,6 +29,45 @@ impl Evaluation {
|
|||
}
|
||||
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 {
|
||||
|
@ -46,19 +85,8 @@ impl From<Vec<EvaluationUnit>> for Evaluation {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Evaluation {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from_str(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
impl From<Evaluation> for Word {
|
||||
fn from(value: Evaluation) -> Self {
|
||||
Word::from(value.inner.into_iter().map(|v| v.0).collect::<String>())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -320,7 +320,7 @@ impl<'wl, WL: WordList> Display for Game<'wl, WL> {
|
|||
for s in self
|
||||
.responses()
|
||||
.iter()
|
||||
.map(|v| v.evaluation().to_owned().colorized_display(v.guess()))
|
||||
.map(|v| v.evaluation().to_owned().colorized_display())
|
||||
{
|
||||
write!(f, "\"")?;
|
||||
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.
|
||||
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;
|
||||
loop {
|
||||
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");
|
||||
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> {
|
||||
// HACK: hardcoded length
|
||||
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::solve::{AnyBuiltinSolver, NaiveSolver, Solver, StupidSolver};
|
||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||
|
@ -69,7 +72,7 @@ fn test_naive_play_predetermined_game() -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
#[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 sl =
|
||||
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
|
||||
let mut game = Game::build(5, false, 6, &wl, false)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
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"
|
||||
assert!(game.finished());
|
||||
|
|
Loading…
Reference in New Issue