generated from PlexSheep/rs-base
workle
cargo devel CI / cargo CI (push) Successful in 44s
Details
cargo devel CI / cargo CI (push) Successful in 44s
Details
This commit is contained in:
parent
8ee32759ee
commit
94d16b345f
11
Cargo.toml
11
Cargo.toml
|
@ -17,22 +17,25 @@ default = ["game", "bench", "tui", "solvers", "builtin_wlist", "serde"]
|
||||||
builtin_wlist = ["dep:serde_json", "serde"]
|
builtin_wlist = ["dep:serde_json", "serde"]
|
||||||
game = ["builtin_wlist"]
|
game = ["builtin_wlist"]
|
||||||
solvers = []
|
solvers = []
|
||||||
tui = ["game"]
|
tui = ["cli"]
|
||||||
|
cli = ["dep:clap"]
|
||||||
bench = []
|
bench = []
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.81"
|
||||||
clap = { version = "4.5.3", features = ["derive"] }
|
clap = { version = "4.5.3", features = ["derive"], optional = true }
|
||||||
|
colored = { version = "2.1.0", optional = false }
|
||||||
libpt = "0.4.2"
|
libpt = "0.4.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
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 }
|
||||||
|
thiserror = "1.0.58"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "wordlec"
|
name = "wordlec"
|
||||||
path = "src/bin/game/cli.rs"
|
path = "src/bin/game/cli.rs"
|
||||||
required-features = ["game"]
|
required-features = ["game", "cli"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "wordlet"
|
name = "wordlet"
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
#![warn(clippy::all)]
|
#![warn(clippy::all)]
|
||||||
#![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
#![warn(missing_debug_implementations)]
|
#![warn(missing_debug_implementations)]
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use libpt::log::*;
|
use libpt::log::*;
|
||||||
|
use wordle_analyzer::error::GameError;
|
||||||
|
use wordle_analyzer::game::response::GuessResponse;
|
||||||
use wordle_analyzer::game::Game;
|
use wordle_analyzer::game::Game;
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||||
|
use wordle_analyzer::wlist::word::Word;
|
||||||
use wordle_analyzer::{self, game};
|
use wordle_analyzer::{self, game};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
#[derive(Parser, Clone, Debug)]
|
#[derive(Parser, Clone, Debug)]
|
||||||
#[command(version, about, long_about, author)]
|
#[command(version, about, long_about, author)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
@ -19,31 +27,74 @@ struct Cli {
|
||||||
/// how many times can we guess?
|
/// how many times can we guess?
|
||||||
#[arg(short, long, default_value_t = wordle_analyzer::DEFAULT_MAX_STEPS)]
|
#[arg(short, long, default_value_t = wordle_analyzer::DEFAULT_MAX_STEPS)]
|
||||||
max_steps: usize,
|
max_steps: usize,
|
||||||
|
/// more verbose logs
|
||||||
|
#[arg(short, long)]
|
||||||
|
verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
if cli.verbose {
|
||||||
Logger::build_mini(Some(Level::TRACE))?;
|
Logger::build_mini(Some(Level::TRACE))?;
|
||||||
|
} else {
|
||||||
|
Logger::build_mini(Some(Level::INFO))?;
|
||||||
|
}
|
||||||
debug!("dumping CLI: {:#?}", cli);
|
debug!("dumping CLI: {:#?}", cli);
|
||||||
|
|
||||||
let game: Game<BuiltinWList> = game::Game::builder()
|
let mut game = game::Game::<BuiltinWList>::builder()
|
||||||
.length(cli.length)
|
.length(cli.length)
|
||||||
.precompute(cli.precompute)
|
.precompute(cli.precompute)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
debug!("game: {:#?}", game);
|
debug!("{game:#?}");
|
||||||
|
|
||||||
|
let mut response: GuessResponse;
|
||||||
|
let mut guess: Word;
|
||||||
|
loop {
|
||||||
|
guess = match get_word(&cli, game.step()) {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(err) => match err.downcast::<GameError>() {
|
||||||
|
Ok(game_err) => match game_err {
|
||||||
|
GameError::GuessHasWrongLength => {
|
||||||
|
println!("wring length: must be {} long", game.length());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(game_err.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => return Err(anyhow!(err.to_string())),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
response = game.guess(guess)?;
|
||||||
|
|
||||||
|
println!("{response}");
|
||||||
|
|
||||||
|
if response.finished() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if response.won() {
|
||||||
|
println!("You win! You took {} guesses.", game.step() - 1);
|
||||||
|
} else {
|
||||||
|
println!("You lose! The solution was {:?}.", game.solution());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_word(cli: &Cli) -> String {
|
fn get_word(cli: &Cli, step: usize) -> anyhow::Result<Word> {
|
||||||
let mut word = String::new();
|
let mut word = Word::new();
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
|
||||||
// TODO: get user input
|
// TODO: get user input
|
||||||
// TODO: validate user input
|
// TODO: validate user input
|
||||||
|
|
||||||
todo!();
|
print!("guess {step} > ");
|
||||||
|
stdout.flush()?;
|
||||||
|
stdin.read_line(&mut word)?;
|
||||||
|
word = word.replace('\n', "");
|
||||||
|
|
||||||
assert_eq!(word.len(), cli.length);
|
Ok(word)
|
||||||
word
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
pub type GameResult<T> = std::result::Result<T, GameError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("GameError")]
|
||||||
|
GameError {
|
||||||
|
#[from]
|
||||||
|
source: GameError,
|
||||||
|
},
|
||||||
|
#[error(transparent)]
|
||||||
|
Other {
|
||||||
|
#[from]
|
||||||
|
source: anyhow::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum GameError {
|
||||||
|
#[error("The guess has the wrong length")]
|
||||||
|
GuessHasWrongLength,
|
||||||
|
#[error("The game is finished but a guess is being made")]
|
||||||
|
TryingToPlayAFinishedGame,
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::wlist::word::{Frequency, Solution, Word};
|
use crate::error::*;
|
||||||
|
use crate::wlist::word::{Solution, Word};
|
||||||
use crate::wlist::WordList;
|
use crate::wlist::WordList;
|
||||||
|
|
||||||
use self::response::GuessResponse;
|
|
||||||
|
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
use response::GuessResponse;
|
||||||
|
|
||||||
|
use self::response::Status;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Game<WL>
|
pub struct Game<WL>
|
||||||
|
@ -16,6 +18,7 @@ where
|
||||||
step: usize,
|
step: usize,
|
||||||
solution: Solution,
|
solution: Solution,
|
||||||
wordlist: WL,
|
wordlist: WL,
|
||||||
|
finished: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<WL: WordList> Game<WL> {
|
impl<WL: WordList> Game<WL> {
|
||||||
|
@ -40,15 +43,16 @@ impl<WL: WordList> Game<WL> {
|
||||||
precompute: bool,
|
precompute: bool,
|
||||||
max_steps: usize,
|
max_steps: usize,
|
||||||
wlist: WL,
|
wlist: WL,
|
||||||
) -> anyhow::Result<Self> {
|
) -> GameResult<Self> {
|
||||||
let solution = wlist.rand_solution();
|
let solution = wlist.rand_solution();
|
||||||
let mut game = Game {
|
let game = Game {
|
||||||
length,
|
length,
|
||||||
precompute,
|
precompute,
|
||||||
max_steps,
|
max_steps,
|
||||||
step: 0,
|
step: 1,
|
||||||
solution,
|
solution,
|
||||||
wordlist: wlist,
|
wordlist: wlist,
|
||||||
|
finished: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(game)
|
Ok(game)
|
||||||
|
@ -56,12 +60,50 @@ impl<WL: WordList> Game<WL> {
|
||||||
|
|
||||||
pub fn reset(mut self) -> Self {
|
pub fn reset(mut self) -> Self {
|
||||||
self.solution = self.wordlist.rand_solution();
|
self.solution = self.wordlist.rand_solution();
|
||||||
self.step = 0;
|
self.step = 1;
|
||||||
|
self.finished = false;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn guess(&mut self, word: Word) -> anyhow::Result<GuessResponse> {
|
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> {
|
||||||
todo!()
|
if guess.len() != self.length {
|
||||||
|
return Err(GameError::GuessHasWrongLength);
|
||||||
|
}
|
||||||
|
if self.finished || self.step > self.max_steps {
|
||||||
|
return Err(GameError::TryingToPlayAFinishedGame);
|
||||||
|
}
|
||||||
|
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 = compare_solution.replace(c, "_");
|
||||||
|
} 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 mut response = GuessResponse::new(guess, evaluation, self.step, self.max_steps);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> usize {
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solution(&self) -> &Solution {
|
||||||
|
&self.solution
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&self) -> usize {
|
||||||
|
self.step
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +149,7 @@ pub struct GameBuilder<WL: WordList> {
|
||||||
|
|
||||||
impl<WL: WordList> GameBuilder<WL> {
|
impl<WL: WordList> GameBuilder<WL> {
|
||||||
/// build a [`Game`] with the stored configuration
|
/// build a [`Game`] with the stored configuration
|
||||||
pub fn build(self) -> anyhow::Result<Game<WL>> {
|
pub fn build(self) -> GameResult<Game<WL>> {
|
||||||
let game: Game<WL> =
|
let game: Game<WL> =
|
||||||
Game::build(self.length, self.precompute, self.max_steps, WL::default())?;
|
Game::build(self.length, self.precompute, self.max_steps, WL::default())?;
|
||||||
Ok(game)
|
Ok(game)
|
||||||
|
|
|
@ -1,13 +1,73 @@
|
||||||
use crate::wlist::word::Word;
|
use crate::wlist::word::Word;
|
||||||
|
use anyhow::Ok;
|
||||||
|
use colored::{ColoredString, Colorize};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct GuessResponse {
|
pub struct GuessResponse {
|
||||||
|
guess: Word,
|
||||||
|
evaluation: Vec<(char, Status)>,
|
||||||
|
step: usize,
|
||||||
|
finish: bool,
|
||||||
|
win: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Status {
|
||||||
|
None = 0,
|
||||||
|
Exists = 1,
|
||||||
|
Matched = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuessResponse {
|
||||||
|
pub(crate) fn new(
|
||||||
guess: Word,
|
guess: Word,
|
||||||
status: Vec<(char, Status)>,
|
status: Vec<(char, Status)>,
|
||||||
step: usize,
|
step: usize,
|
||||||
|
max_step: usize,
|
||||||
|
) -> Self {
|
||||||
|
let mut win = false;
|
||||||
|
let mut finish: bool = if step >= max_step {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let mut matched = true;
|
||||||
|
for p in &status {
|
||||||
|
matched &= p.1 == Status::Matched;
|
||||||
|
}
|
||||||
|
win = matched;
|
||||||
|
win
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
guess,
|
||||||
|
evaluation: status,
|
||||||
|
step,
|
||||||
|
finish,
|
||||||
|
win
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finished(&self) -> bool {
|
||||||
|
self.finish
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn won(&self) -> bool {
|
||||||
|
self.win
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Status {
|
impl Display for GuessResponse {
|
||||||
None,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
Exists,
|
for s in &self.evaluation {
|
||||||
Matched,
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match s.1 {
|
||||||
|
Status::None => s.0.to_string().into(),
|
||||||
|
Status::Exists => s.0.to_string().yellow(),
|
||||||
|
Status::Matched => s.0.to_string().green(),
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
std::fmt::Result::Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub const DEFAULT_WORD_LENGTH: usize = 5;
|
||||||
/// Default amount of guesses per game
|
/// Default amount of guesses per game
|
||||||
pub const DEFAULT_MAX_STEPS: usize = 6;
|
pub const DEFAULT_MAX_STEPS: usize = 6;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
pub mod bench;
|
pub mod bench;
|
||||||
#[cfg(feature = "game")]
|
#[cfg(feature = "game")]
|
||||||
|
|
Loading…
Reference in New Issue