generated from PlexSheep/rs-base
generics are crazy
cargo devel CI / cargo CI (push) Successful in 49s
Details
cargo devel CI / cargo CI (push) Successful in 49s
Details
This commit is contained in:
parent
9f908e3812
commit
880826dd85
|
@ -13,16 +13,21 @@ keywords = ["wordle", "benchmark"]
|
||||||
default-run = "wordlec"
|
default-run = "wordlec"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["game", "bench", "tui", "solvers"]
|
default = ["game", "bench", "tui", "solvers", "builtin_wlist", "serde"]
|
||||||
game = []
|
builtin_wlist = ["dep:serde_json", "serde"]
|
||||||
|
game = ["builtin_wlist"]
|
||||||
solvers = []
|
solvers = []
|
||||||
tui = ["game"]
|
tui = ["game"]
|
||||||
bench = []
|
bench = []
|
||||||
|
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"] }
|
||||||
libpt = "0.4.2"
|
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}
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "wordlec"
|
name = "wordlec"
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#![warn(missing_debug_implementations)]
|
#![warn(missing_debug_implementations)]
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use libpt::log::*;
|
use libpt::log::*;
|
||||||
|
use wordle_analyzer::game::Game;
|
||||||
|
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||||
use wordle_analyzer::{self, game};
|
use wordle_analyzer::{self, game};
|
||||||
|
|
||||||
#[derive(Parser, Clone, Debug)]
|
#[derive(Parser, Clone, Debug)]
|
||||||
|
@ -24,7 +26,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
Logger::build_mini(Some(Level::TRACE))?;
|
Logger::build_mini(Some(Level::TRACE))?;
|
||||||
debug!("dumping CLI: {:#?}", cli);
|
debug!("dumping CLI: {:#?}", cli);
|
||||||
|
|
||||||
let game = game::Game::builder()
|
let game: Game<BuiltinWList> = game::Game::builder()
|
||||||
.length(cli.length)
|
.length(cli.length)
|
||||||
.precompute(cli.precompute)
|
.precompute(cli.precompute)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
use crate::wlist::WordList;
|
||||||
pub struct Game {
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Game<WL>
|
||||||
|
where
|
||||||
|
WL: WordList,
|
||||||
|
{
|
||||||
length: usize,
|
length: usize,
|
||||||
precompute: bool,
|
precompute: bool,
|
||||||
max_steps: usize,
|
max_steps: usize,
|
||||||
step: usize,
|
step: usize,
|
||||||
solution: String,
|
solution: String,
|
||||||
|
wordlist: WL,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl<WL: WordList> Game<WL> {
|
||||||
/// get a new [`GameBuilder`]
|
/// get a new [`GameBuilder`]
|
||||||
pub fn builder() -> GameBuilder {
|
pub fn builder() -> GameBuilder<WL> {
|
||||||
GameBuilder::default()
|
GameBuilder::default()
|
||||||
}
|
}
|
||||||
/// Create a [Game] of wordle
|
/// Create a [Game] of wordle
|
||||||
|
@ -24,22 +30,16 @@ impl Game {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if .
|
/// This function will return an error if .
|
||||||
pub(crate) fn build(length: usize, precompute: bool, max_steps: usize) -> anyhow::Result<Self> {
|
pub(crate) fn build(length: usize, precompute: bool, max_steps: usize, wlist: WL) -> anyhow::Result<Self> {
|
||||||
let _game = Game {
|
let _game = Game {
|
||||||
length,
|
length,
|
||||||
precompute,
|
precompute,
|
||||||
max_steps,
|
max_steps,
|
||||||
step: 0,
|
step: 0,
|
||||||
solution: String::default(), // we actually set this later
|
solution: String::default(), // we actually set this later
|
||||||
|
wordlist: wlist
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: load wordlist of possible answers
|
|
||||||
// TODO: select one as a solution at random
|
|
||||||
// NOTE: The possible answers should be determined with a wordlist that has the
|
|
||||||
// frequencies/probabilities of the words. We then use a sigmoid function to determine if a
|
|
||||||
// word can be a solution based on that value. Only words above some threshold of
|
|
||||||
// commonness will be available as solutions then. Next, we choose one of the allowed words
|
|
||||||
// randomly.
|
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,16 +77,17 @@ impl Game {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct GameBuilder {
|
pub struct GameBuilder<WL: WordList> {
|
||||||
length: usize,
|
length: usize,
|
||||||
precompute: bool,
|
precompute: bool,
|
||||||
max_steps: usize,
|
max_steps: usize,
|
||||||
|
wordlist: WL
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameBuilder {
|
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> {
|
pub fn build(self) -> anyhow::Result<Game<WL>> {
|
||||||
let game: Game = Game::build(self.length, self.precompute, self.max_steps)?;
|
let game: Game<WL> = Game::build(self.length, self.precompute, self.max_steps, WL::default())?;
|
||||||
Ok(game)
|
Ok(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,12 +117,13 @@ impl GameBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameBuilder {
|
impl<WL: WordList> Default for GameBuilder<WL> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
length: super::DEFAULT_WORD_LENGTH,
|
length: super::DEFAULT_WORD_LENGTH,
|
||||||
precompute: false,
|
precompute: false,
|
||||||
max_steps: super::DEFAULT_MAX_STEPS,
|
max_steps: super::DEFAULT_MAX_STEPS,
|
||||||
|
wordlist: WL::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,3 +13,4 @@ pub mod bench;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
#[cfg(feature = "solvers")]
|
#[cfg(feature = "solvers")]
|
||||||
pub mod solvers;
|
pub mod solvers;
|
||||||
|
pub mod wlist;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use super::Word;
|
||||||
|
|
||||||
|
const RAW_WORDLIST_FILE: &str = include_str!("../../data/wordlists/en_US_3b1b_freq_map.json");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BuiltinWList {
|
||||||
|
words: super::WordMap
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::WordList for BuiltinWList {
|
||||||
|
fn solutions(&self) -> Vec<&Word> {
|
||||||
|
// PERF: this can be made faster if we were to use parallel iterators or chunking
|
||||||
|
self.words.keys().collect()
|
||||||
|
}
|
||||||
|
fn length_range(&self) -> impl std::ops::RangeBounds<usize> {
|
||||||
|
5..5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BuiltinWList {
|
||||||
|
fn default() -> Self {
|
||||||
|
let words: super::WordMap = serde_json::from_str(RAW_WORDLIST_FILE).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
words
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use rand::{prelude::*, seq::IteratorRandom};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
|
#[cfg(feature = "builtin_wlist")]
|
||||||
|
pub mod builtin;
|
||||||
|
pub mod word;
|
||||||
|
use word::*;
|
||||||
|
|
||||||
|
pub type AnyWordlist = Box<dyn WordList>;
|
||||||
|
|
||||||
|
pub trait WordList: Clone + std::fmt::Debug + Default {
|
||||||
|
// NOTE: The possible answers should be determined with a wordlist that has the
|
||||||
|
// frequencies/probabilities of the words. We then use a sigmoid function to determine if a
|
||||||
|
// word can be a solution based on that value. Only words above some threshold of
|
||||||
|
// commonness will be available as solutions then. Next, we choose one of the allowed words
|
||||||
|
// randomly.
|
||||||
|
// NOTE: must never return nothing
|
||||||
|
fn solutions(&self) -> Vec<&Word>;
|
||||||
|
fn rand_solution(&self) -> &Word {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
self.solutions().iter().choose(&mut rng).unwrap()
|
||||||
|
}
|
||||||
|
fn length_range(&self) -> impl RangeBounds<usize>;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// NOTE: We might need a different implementation for more precision
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Frequency {
|
||||||
|
inner: f64
|
||||||
|
}
|
||||||
|
// PERF: Hash for String is probably a bottleneck
|
||||||
|
pub type Word = String;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct WordMap {
|
||||||
|
inner: HashMap<Word,Frequency>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WordMap {
|
||||||
|
pub fn keys(&self) -> std::collections::hash_map::Keys<'_, String, Frequency> {
|
||||||
|
self.inner.keys()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue