separate into packages & start on actual game

This commit is contained in:
ashastral 2022-02-27 12:40:36 -08:00
parent 76ff849a8c
commit 45289ac8ab
20 changed files with 482 additions and 72 deletions

249
Cargo.lock generated
View file

@ -2,34 +2,88 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "js-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "memory_units"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
[[package]]
name = "optimle"
version = "0.1.0"
dependencies = [
"rand",
"rand_chacha",
"wordlelike",
]
[[package]]
@ -38,6 +92,24 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -68,8 +140,183 @@ dependencies = [
"getrandom",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c8d417d87eefa0087e62e3c75ad086be39433449e2961add9a5d9ce5acc2f1"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e560d44db5e73b69a9757a15512fe7e1ef93ed2061c928871a4025798293dd"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "web-optimle"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"optimle",
"wasm-bindgen",
"wasm-bindgen-test",
"wee_alloc",
]
[[package]]
name = "web-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wee_alloc"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"memory_units",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "wordlelike"
version = "0.1.0"
dependencies = [
"rand",
]

View file

@ -1,9 +1,6 @@
[package]
name = "optimle"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
[workspace]
members = [
"wordlelike",
"optimle",
"web-optimle",
]

13
optimle/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "optimle"
version = "0.1.0"
authors = ["ashastral"]
edition = "2018"
[lib]
crate-type = ["rlib"]
[dependencies]
rand = "0.8.5"
rand_chacha = "0.3.1"
wordlelike = { path = "../wordlelike" }

90
optimle/src/lib.rs Normal file
View file

@ -0,0 +1,90 @@
use rand_chacha::ChaCha8Rng;
use rand_chacha::rand_core::SeedableRng;
use rand::seq::SliceRandom;
use wordlelike::constraints;
use wordlelike::constraints::Constraints;
use wordlelike::evaluator;
use wordlelike::evaluator::Evaluator;
use wordlelike::game;
use wordlelike::guess::*;
use wordlelike::guesser;
pub struct Puzzle<const N: usize> {
pub history: GuessHistory<N>,
}
impl<const N: usize> Puzzle<N> {
pub fn new(dictionary: &Dictionary<N>, seed: u64) -> Self {
let mut rng: ChaCha8Rng = ChaCha8Rng::seed_from_u64(seed);
let mut history: Option<GuessHistory<N>> = None;
loop {
let initial_secret = dictionary.choose(&mut rng).unwrap();
let constraints = &mut constraints::Total::new();
let mut game = game::Game {
evaluator: &mut evaluator::SecretEvaluator::new(*initial_secret),
guesser: &mut guesser::RandomConstraintGuesser::new(dictionary, constraints, &mut rng),
};
let mut game_history = Vec::new();
loop {
let result = game.play_round();
if result.correct() { break; }
game.push(result);
game_history.push(result);
if game_history.len() > 2 {
if result.matches
.iter()
.filter(|&m| *m == LetterMatch::HERE)
.count() < 3 {
continue;
}
let possible_words = game.guesser.get_possible_words();
if Puzzle::interesting(dictionary, &game_history, possible_words) {
history = Some(game_history);
break;
}
}
}
if history.is_some() { break; }
}
Self {
history: history.unwrap(),
}
}
fn interesting(dictionary: &Dictionary<N>, history: &GuessHistory<N>, possible_words: Dictionary<N>) -> bool {
let count = possible_words.len();
if count < 3 || count > 6 {
return false;
}
let base_constraints = &mut constraints::Total::new();
let constraints = &mut constraints::Total::new();
let mut evaluator = evaluator::AdversarialEvaluator::new(dictionary);
for &result in history {
base_constraints.push(result);
evaluator.push(result);
}
let mut solutions = Vec::new();
for &word in dictionary {
constraints.clone_from(base_constraints);
let matches = evaluator.evaluate(word);
constraints.push(GuessResult { word, matches });
let remaining_words = possible_words
.iter()
.filter(|&word| constraints.check(*word))
.count();
if remaining_words == 1 {
solutions.push(word);
}
}
let solution_count = solutions.len();
let interesting = solution_count > 0 && solution_count <= 100;
if interesting {
println!("Could be: {:?}", possible_words);
}
interesting
}
}

10
optimle/src/main.rs Normal file
View file

@ -0,0 +1,10 @@
use optimle::Puzzle;
use wordlelike::guess::*;
fn main() {
let common_words: Vec<Word<5>> = wordlelike::load::load_words(include_str!("../../wordlelike/src/words/common.txt"));
let puzzle = Puzzle::new(&common_words, 0);
for result in puzzle.history {
println!("{}", result);
}
}

View file

@ -1,52 +0,0 @@
use std::convert::TryInto;
#[macro_export]
macro_rules! word {
( $x:expr ) => {
{
$x.chars()
.collect::<Vec<char>>()
.as_slice()
.try_into()
.unwrap()
}
}
}
mod guess;
use guess::*;
mod constraints;
mod game;
mod guesser;
mod evaluator;
fn play<const N: usize>(dictionary: &Dictionary<N>) {
let mut constraints = constraints::Total::new();
let mut game = game::Game {
evaluator: &mut evaluator::AdversarialEvaluator::new(dictionary),
guesser: &mut guesser::RandomConstraintGuesser::new(dictionary, &mut constraints),
};
loop {
let result = game.next();
println!("{}", result);
if result.correct() {
return
}
}
}
fn load_words<const N: usize>(contents: &'static str) -> Vec<Word<N>> {
contents
.split("\n")
.map(|line| word!(line))
.collect::<Vec<_>>()
}
fn main() {
let common_words: Vec<Word<5>> = load_words(include_str!("words/common.txt"));
loop {
play(&common_words);
}
}

23
web-optimle/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "web-optimle"
version = "0.1.0"
authors = ["ashastral"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
console_error_panic_hook = { version = "0.1.6", optional = true }
wee_alloc = { version = "0.4.5", optional = true }
optimle = { path = "../optimle" }
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
[profie.release]
opt-level = "s"

0
web-optimle/src/lib.rs Normal file
View file

11
wordlelike/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "wordlelike"
version = "0.1.0"
authors = ["ashastral"]
edition = "2018"
[lib]
crate-type = ["rlib"]
[dependencies]
rand = "0.8.5"

View file

@ -1,4 +1,3 @@
use std::convert::TryInto;
use std::collections::HashSet;
use crate::guess::*;
@ -14,6 +13,7 @@ pub struct SecretEvaluator<const N: usize> {
secret: Word<N>,
}
/// Evaluates guesses according to the provided secret word.
impl<const N: usize> SecretEvaluator<N> {
pub fn new(secret: Word<N>) -> Self {
Self {
@ -28,6 +28,9 @@ impl<const N: usize> Evaluator<N> for SecretEvaluator<N> {
}
}
/// Evaluates guesses "adversarially", that is, in a way that maximizes the
/// remaining search space after each guess.
pub struct AdversarialEvaluator<const N: usize> {
possible_words: Dictionary<N>,
constraints: constraints::Total<N>,
@ -84,6 +87,7 @@ impl<const N: usize> Evaluator<N> for AdversarialEvaluator<N> {
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn it_evaluates_adversarially() {

View file

@ -8,12 +8,15 @@ pub struct Game<'a, const N: usize> {
}
impl<'a, const N: usize> Game<'a, N> {
pub fn next(&mut self) -> GuessResult<N> {
pub fn play_round(&mut self) -> GuessResult<N> {
let word = self.guesser.guess();
let matches = self.evaluator.evaluate(word);
let result = GuessResult { word, matches };
self.guesser.push(result);
self.evaluator.push(result);
result
}
pub fn push(&mut self, result: GuessResult<N>) {
self.guesser.push(result);
self.evaluator.push(result);
}
}

View file

@ -1,5 +1,3 @@
use std::convert::TryInto;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum LetterMatch {
NOWHERE,
@ -77,6 +75,7 @@ pub type Dictionary<const N: usize> = Vec<Word<N>>;
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn it_matches_correct_guess() {

View file

@ -1,35 +1,39 @@
use rand::seq::SliceRandom;
use rand::RngCore;
use crate::guess::*;
use crate::constraints;
pub trait Guesser<const N: usize> {
fn push(&mut self, _result: GuessResult<N>) {}
fn guess(&self) -> Word<N>;
fn guess(&mut self) -> Word<N>;
fn get_possible_words(&self) -> Dictionary<N>;
}
pub struct RandomConstraintGuesser<'a, const N: usize> {
possible_words: Dictionary<N>,
history: GuessHistory<N>,
constraints: &'a mut dyn constraints::Constraints<N>,
rng: &'a mut dyn RngCore,
}
impl<'a, const N: usize> RandomConstraintGuesser<'a, N> {
pub fn new(
dictionary: &Dictionary<N>,
constraints: &'a mut dyn constraints::Constraints<N>,
rng: &'a mut dyn RngCore,
) -> Self {
Self {
possible_words: dictionary.to_vec(),
history: GuessHistory::new(),
constraints,
rng,
}
}
}
impl<'a, const N: usize> Guesser<N> for RandomConstraintGuesser<'a, N> {
fn guess(&self) -> Word<N> {
*self.possible_words.choose(&mut rand::thread_rng()).unwrap()
fn guess(&mut self) -> Word<N> {
*self.possible_words.choose(&mut self.rng).unwrap()
}
fn push(&mut self, result: GuessResult<N>) {
@ -38,4 +42,8 @@ impl<'a, const N: usize> Guesser<N> for RandomConstraintGuesser<'a, N> {
let constraints = &self.constraints;
self.possible_words.retain(|&word| constraints.check(word));
}
fn get_possible_words(&self) -> Dictionary<N> {
return self.possible_words.to_vec();
}
}

8
wordlelike/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
#[macro_use]
pub mod macros;
pub mod guess;
pub mod constraints;
pub mod game;
pub mod guesser;
pub mod evaluator;
pub mod load;

11
wordlelike/src/load.rs Normal file
View file

@ -0,0 +1,11 @@
use std::convert::TryInto;
use crate::guess::*;
pub fn load_words<const N: usize>(contents: &'static str) -> Vec<Word<N>> {
contents
.split("\n")
.map(|line| word!(line))
.collect::<Vec<_>>()
}

12
wordlelike/src/macros.rs Normal file
View file

@ -0,0 +1,12 @@
#[macro_export]
macro_rules! word {
( $x:expr ) => {
{
$x.chars()
.collect::<Vec<char>>()
.as_slice()
.try_into()
.unwrap()
}
}
}

26
wordlelike/src/main.rs Normal file
View file

@ -0,0 +1,26 @@
use wordlelike::*;
use wordlelike::guess::*;
fn play<const N: usize>(dictionary: &Dictionary<N>) {
let mut constraints = constraints::Total::new();
let mut rng = rand::thread_rng();
let mut game = game::Game {
evaluator: &mut evaluator::AdversarialEvaluator::new(dictionary),
guesser: &mut guesser::RandomConstraintGuesser::new(dictionary, &mut constraints, &mut rng),
};
loop {
let result = game.play_round();
println!("{}", result);
if result.correct() {
return
}
game.push(result);
}
}
fn main() {
let common_words: Vec<Word<5>> = load::load_words(include_str!("words/common.txt"));
loop {
play(&common_words);
}
}