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"]
|
||||
game = ["builtin_wlist"]
|
||||
solvers = []
|
||||
tui = ["game"]
|
||||
tui = ["cli"]
|
||||
cli = ["dep:clap"]
|
||||
bench = []
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
rand = "0.8.5"
|
||||
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]]
|
||||
name = "wordlec"
|
||||
path = "src/bin/game/cli.rs"
|
||||
required-features = ["game"]
|
||||
required-features = ["game", "cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wordlet"
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
#![warn(clippy::all)]
|
||||
#![warn(missing_docs)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use libpt::log::*;
|
||||
use wordle_analyzer::error::GameError;
|
||||
use wordle_analyzer::game::response::GuessResponse;
|
||||
use wordle_analyzer::game::Game;
|
||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||
use wordle_analyzer::wlist::word::Word;
|
||||
use wordle_analyzer::{self, game};
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[command(version, about, long_about, author)]
|
||||
struct Cli {
|
||||
|
@ -19,31 +27,74 @@ struct Cli {
|
|||
/// how many times can we guess?
|
||||
#[arg(short, long, default_value_t = wordle_analyzer::DEFAULT_MAX_STEPS)]
|
||||
max_steps: usize,
|
||||
/// more verbose logs
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
Logger::build_mini(Some(Level::TRACE))?;
|
||||
if cli.verbose {
|
||||
Logger::build_mini(Some(Level::TRACE))?;
|
||||
} else {
|
||||
Logger::build_mini(Some(Level::INFO))?;
|
||||
}
|
||||
debug!("dumping CLI: {:#?}", cli);
|
||||
|
||||
let game: Game<BuiltinWList> = game::Game::builder()
|
||||
let mut game = game::Game::<BuiltinWList>::builder()
|
||||
.length(cli.length)
|
||||
.precompute(cli.precompute)
|
||||
.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(())
|
||||
}
|
||||
|
||||
fn get_word(cli: &Cli) -> String {
|
||||
let mut word = String::new();
|
||||
fn get_word(cli: &Cli, step: usize) -> anyhow::Result<Word> {
|
||||
let mut word = Word::new();
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdout = std::io::stdout();
|
||||
|
||||
// TODO: get 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);
|
||||
word
|
||||
Ok(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 self::response::GuessResponse;
|
||||
|
||||
pub mod response;
|
||||
use response::GuessResponse;
|
||||
|
||||
use self::response::Status;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Game<WL>
|
||||
|
@ -16,6 +18,7 @@ where
|
|||
step: usize,
|
||||
solution: Solution,
|
||||
wordlist: WL,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
impl<WL: WordList> Game<WL> {
|
||||
|
@ -40,15 +43,16 @@ impl<WL: WordList> Game<WL> {
|
|||
precompute: bool,
|
||||
max_steps: usize,
|
||||
wlist: WL,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> GameResult<Self> {
|
||||
let solution = wlist.rand_solution();
|
||||
let mut game = Game {
|
||||
let game = Game {
|
||||
length,
|
||||
precompute,
|
||||
max_steps,
|
||||
step: 0,
|
||||
step: 1,
|
||||
solution,
|
||||
wordlist: wlist,
|
||||
finished: false,
|
||||
};
|
||||
|
||||
Ok(game)
|
||||
|
@ -56,12 +60,50 @@ impl<WL: WordList> Game<WL> {
|
|||
|
||||
pub fn reset(mut self) -> Self {
|
||||
self.solution = self.wordlist.rand_solution();
|
||||
self.step = 0;
|
||||
self.step = 1;
|
||||
self.finished = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn guess(&mut self, word: Word) -> anyhow::Result<GuessResponse> {
|
||||
todo!()
|
||||
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> {
|
||||
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> {
|
||||
/// 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> =
|
||||
Game::build(self.length, self.precompute, self.max_steps, WL::default())?;
|
||||
Ok(game)
|
||||
|
|
|
@ -1,13 +1,73 @@
|
|||
use crate::wlist::word::Word;
|
||||
use anyhow::Ok;
|
||||
use colored::{ColoredString, Colorize};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GuessResponse {
|
||||
guess: Word,
|
||||
status: Vec<(char, Status)>,
|
||||
evaluation: Vec<(char, Status)>,
|
||||
step: usize,
|
||||
finish: bool,
|
||||
win: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Status {
|
||||
None,
|
||||
Exists,
|
||||
Matched,
|
||||
None = 0,
|
||||
Exists = 1,
|
||||
Matched = 2,
|
||||
}
|
||||
|
||||
impl GuessResponse {
|
||||
pub(crate) fn new(
|
||||
guess: Word,
|
||||
status: Vec<(char, Status)>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GuessResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for s in &self.evaluation {
|
||||
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
|
||||
pub const DEFAULT_MAX_STEPS: usize = 6;
|
||||
|
||||
pub mod error;
|
||||
#[cfg(feature = "bench")]
|
||||
pub mod bench;
|
||||
#[cfg(feature = "game")]
|
||||
|
|
Loading…
Reference in New Issue