generated from PlexSheep/rs-base
match and get by frequency
This commit is contained in:
parent
74ef508841
commit
9beb38a5bb
|
@ -28,6 +28,7 @@ clap = { version = "4.5.3", features = ["derive"], optional = true }
|
||||||
colored = { version = "2.1.0", optional = false }
|
colored = { version = "2.1.0", optional = false }
|
||||||
libpt = "0.4.2"
|
libpt = "0.4.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
regex = "1.10.3"
|
||||||
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"
|
thiserror = "1.0.58"
|
||||||
|
|
|
@ -54,8 +54,8 @@ fn main() -> anyhow::Result<()> {
|
||||||
response = match game.guess(guess) {
|
response = match game.guess(guess) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
GameError::GuessHasWrongLength => {
|
GameError::GuessHasWrongLength(len) => {
|
||||||
println!("word length: must be {} long", game.length());
|
println!("word length: must be {} long but is {}", game.length(), len);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
13
src/error.rs
13
src/error.rs
|
@ -1,5 +1,7 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::wlist::word::Word;
|
||||||
|
|
||||||
pub type WResult<T> = std::result::Result<T, Error>;
|
pub type WResult<T> = std::result::Result<T, Error>;
|
||||||
pub type GameResult<T> = std::result::Result<T, GameError>;
|
pub type GameResult<T> = std::result::Result<T, GameError>;
|
||||||
|
|
||||||
|
@ -18,12 +20,19 @@ pub enum Error {
|
||||||
// for `FromStr` of `BuiltinSolver`
|
// for `FromStr` of `BuiltinSolver`
|
||||||
#[error("Unknown builtin solver")]
|
#[error("Unknown builtin solver")]
|
||||||
UnknownBuiltinSolver,
|
UnknownBuiltinSolver,
|
||||||
|
#[error("pattern matching error")]
|
||||||
|
Regex{
|
||||||
|
#[from]
|
||||||
|
source: regex::Error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
#[derive(Debug, Clone, Error)]
|
||||||
pub enum GameError {
|
pub enum GameError {
|
||||||
#[error("The guess has the wrong length")]
|
#[error("The guess has the wrong length ({0})")]
|
||||||
GuessHasWrongLength,
|
GuessHasWrongLength(usize),
|
||||||
#[error("The game is finished but a guess is being made")]
|
#[error("The game is finished but a guess is being made")]
|
||||||
TryingToPlayAFinishedGame,
|
TryingToPlayAFinishedGame,
|
||||||
|
#[error("Tried to guess a word that is not in the wordlist ({0})")]
|
||||||
|
WordNotInWordlist(Word),
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,11 +78,14 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
||||||
/// 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) -> GameResult<GuessResponse> {
|
||||||
if guess.len() != self.length {
|
if guess.len() != self.length {
|
||||||
return Err(GameError::GuessHasWrongLength);
|
return Err(GameError::GuessHasWrongLength(guess.len()));
|
||||||
}
|
}
|
||||||
if self.finished || self.step > self.max_steps {
|
if self.finished || self.step > self.max_steps {
|
||||||
return Err(GameError::TryingToPlayAFinishedGame);
|
return Err(GameError::TryingToPlayAFinishedGame);
|
||||||
}
|
}
|
||||||
|
if self.wordlist.get_word(&guess).is_none() {
|
||||||
|
return Err(GameError::WordNotInWordlist(guess));
|
||||||
|
}
|
||||||
self.step += 1;
|
self.step += 1;
|
||||||
|
|
||||||
let mut compare_solution = self.solution.0.clone();
|
let mut compare_solution = self.solution.0.clone();
|
||||||
|
@ -101,7 +104,8 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
||||||
evaluation.push((c, status));
|
evaluation.push((c, status));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = GuessResponse::new(guess, evaluation, &self);
|
let response = GuessResponse::new(guess, evaluation, self);
|
||||||
|
self.responses.push(response.clone());
|
||||||
self.finished = response.finished();
|
self.finished = response.finished();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -128,6 +132,13 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
||||||
pub fn responses(&self) -> &Vec<GuessResponse> {
|
pub fn responses(&self) -> &Vec<GuessResponse> {
|
||||||
&self.responses
|
&self.responses
|
||||||
}
|
}
|
||||||
|
pub fn last_response(&self) -> Option<&GuessResponse> {
|
||||||
|
self.responses().last()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wordlist(&self) -> &WL {
|
||||||
|
self.wordlist
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build and Configure a [`Game`]
|
/// Build and Configure a [`Game`]
|
||||||
|
|
|
@ -62,6 +62,14 @@ impl GuessResponse {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn evaluation(&self) -> &[(char, Status)] {
|
||||||
|
&self.evaluation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guess(&self) -> &str {
|
||||||
|
&self.guess
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for GuessResponse {
|
impl Display for GuessResponse {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use libpt::log::info;
|
||||||
use crate::wlist::word::Word;
|
use crate::wlist::word::Word;
|
||||||
use crate::wlist::WordList;
|
use crate::wlist::WordList;
|
||||||
|
|
||||||
use super::{AnyBuiltinSolver, Solver};
|
use super::{AnyBuiltinSolver, Solver, Status};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NaiveSolver<'wl, WL> {
|
pub struct NaiveSolver<'wl, WL> {
|
||||||
|
@ -16,7 +16,21 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
Ok(Self { wl: wordlist })
|
Ok(Self { wl: wordlist })
|
||||||
}
|
}
|
||||||
fn guess_for(&self, game: &crate::game::Game<WL>) -> Word {
|
fn guess_for(&self, game: &crate::game::Game<WL>) -> Word {
|
||||||
self.wl.rand_word().0
|
// HACK: hardcoded length
|
||||||
|
let mut buf: Word = Word::from(".....");
|
||||||
|
let response = game.last_response();
|
||||||
|
if response.is_some() {
|
||||||
|
for (idx, p) in response.unwrap().evaluation().iter().enumerate() {
|
||||||
|
if p.1 == Status::Matched {
|
||||||
|
buf.replace_range(idx..idx + 1, &p.0.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game.wordlist()
|
||||||
|
.get_words_matching(buf)
|
||||||
|
.expect("the solution does not exist in the wordlist")[0]
|
||||||
|
.0
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl super::WordList for BuiltinWList {
|
||||||
&self.words
|
&self.words
|
||||||
}
|
}
|
||||||
fn get_word(&self, word: &Word) -> Option<super::WordData> {
|
fn get_word(&self, word: &Word) -> Option<super::WordData> {
|
||||||
self.words.get(&word)
|
self.words.get(word)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
use libpt::log::debug;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::RangeBounds;
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
|
@ -14,13 +17,16 @@ pub type AnyWordlist = Box<dyn WordList>;
|
||||||
|
|
||||||
pub trait WordList: Clone + std::fmt::Debug + Default {
|
pub trait WordList: Clone + std::fmt::Debug + Default {
|
||||||
fn solutions(&self) -> ManyWordDatas {
|
fn solutions(&self) -> ManyWordDatas {
|
||||||
let wmap = self.wordmap();
|
let wmap = self.wordmap().clone();
|
||||||
let threshold = wmap.threshold();
|
let threshold = wmap.threshold();
|
||||||
wmap.iter().filter(|i| *i.1 > threshold).collect()
|
wmap.iter()
|
||||||
|
.filter(|i| *i.1 > threshold)
|
||||||
|
.map(|p| (p.0.clone(), *p.1))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
fn rand_solution(&self) -> WordData {
|
fn rand_solution(&self) -> WordData {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let sol = *self.solutions().iter().choose(&mut rng).unwrap();
|
let sol = self.solutions().iter().choose(&mut rng).unwrap().clone();
|
||||||
(sol.0.to_owned(), sol.1.to_owned())
|
(sol.0.to_owned(), sol.1.to_owned())
|
||||||
}
|
}
|
||||||
fn rand_word(&self) -> WordData {
|
fn rand_word(&self) -> WordData {
|
||||||
|
@ -62,4 +68,28 @@ pub trait WordList: Clone + std::fmt::Debug + Default {
|
||||||
let n: f64 = cmap.keys().len() as f64;
|
let n: f64 = cmap.keys().len() as f64;
|
||||||
cmap.into_iter().map(|p| (p.0, p.1 as f64 / n)).collect()
|
cmap.into_iter().map(|p| (p.0, p.1 as f64 / n)).collect()
|
||||||
}
|
}
|
||||||
|
fn raw_wordlist(&self) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
for w in self.wordmap().keys() {
|
||||||
|
buf += &w;
|
||||||
|
buf += "\n";
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
fn get_words_matching(&self, pattern: String) -> WResult<ManyWordDatas> {
|
||||||
|
let pattern = Regex::new(&pattern)?;
|
||||||
|
let hay = self.raw_wordlist();
|
||||||
|
let keys = pattern.captures_iter(&hay);
|
||||||
|
let mut buf = ManyWordDatas::new();
|
||||||
|
for k in keys {
|
||||||
|
debug!("match: {k:?}");
|
||||||
|
let w: WordData = self.wordmap().get(&k[0]).unwrap();
|
||||||
|
buf.push(w)
|
||||||
|
}
|
||||||
|
// sort by frequency
|
||||||
|
buf.sort_by(|a, b| {
|
||||||
|
a.1.partial_cmp(&b.1).unwrap()
|
||||||
|
});
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::write;
|
use std::fmt::write;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use libpt::log::debug;
|
use libpt::log::debug;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
@ -11,7 +12,7 @@ pub type Frequency = f64;
|
||||||
pub type Word = String;
|
pub type Word = String;
|
||||||
pub type WordData = (Word, Frequency);
|
pub type WordData = (Word, Frequency);
|
||||||
pub type ManyWords<'a> = Vec<&'a Word>;
|
pub type ManyWords<'a> = Vec<&'a Word>;
|
||||||
pub type ManyWordDatas<'a> = Vec<(&'a Word, &'a Frequency)>;
|
pub type ManyWordDatas = Vec<(Word, Frequency)>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -66,9 +67,9 @@ impl WordMap {
|
||||||
pub fn inner(&self) -> &HashMap<Word, Frequency> {
|
pub fn inner(&self) -> &HashMap<Word, Frequency> {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
pub fn get(&self, word: &Word) -> Option<WordData> {
|
pub fn get<I: std::fmt::Display>(&self, word: I) -> Option<WordData> {
|
||||||
match self.inner.get(word) {
|
match self.inner.get(&word.to_string()) {
|
||||||
Some(f) => Some((word.clone(), *f)),
|
Some(f) => Some((word.to_string(), *f)),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue