did stuff
This commit is contained in:
parent
f597fb751b
commit
0023a73b83
5 changed files with 13294 additions and 2 deletions
68
Cargo.lock
generated
68
Cargo.lock
generated
|
@ -2,6 +2,74 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "optimle"
|
name = "optimle"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
|
@ -6,3 +6,4 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.8.5"
|
255
src/main.rs
255
src/main.rs
|
@ -1,3 +1,254 @@
|
||||||
fn main() {
|
use std::collections::HashMap;
|
||||||
println!("Hello, world!");
|
use std::convert::TryInto;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum LetterMatch {
|
||||||
|
NOWHERE,
|
||||||
|
ELSEWHERE,
|
||||||
|
HERE,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Word<const N: usize> = [char; N];
|
||||||
|
type Matches<const N: usize> = [LetterMatch; N];
|
||||||
|
|
||||||
|
fn guess<const N: usize>(word: &Word<N>, secret: &Word<N>) -> Matches<N> {
|
||||||
|
let mut result = [LetterMatch::NOWHERE; N];
|
||||||
|
let mut matched_secret_letters = [false; N];
|
||||||
|
|
||||||
|
// Find exact matches
|
||||||
|
for a in 0..N {
|
||||||
|
if word[a] == secret[a] {
|
||||||
|
result[a] = LetterMatch::HERE;
|
||||||
|
matched_secret_letters[a] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find wrong-position matches
|
||||||
|
for a in 0..N {
|
||||||
|
for b in 0..N {
|
||||||
|
if a == b || matched_secret_letters[b] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if word[a] == secret[b] {
|
||||||
|
result[a] = LetterMatch::ELSEWHERE;
|
||||||
|
matched_secret_letters[b] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct GuessResult<const N: usize> {
|
||||||
|
word: Word<N>,
|
||||||
|
matches: Matches<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> GuessResult<N> {
|
||||||
|
fn correct(&self) -> bool {
|
||||||
|
for a in 0..N {
|
||||||
|
if self.matches[a] != LetterMatch::HERE {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> std::fmt::Display for GuessResult<N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for a in 0..N {
|
||||||
|
result.push(match self.matches[a] {
|
||||||
|
LetterMatch::ELSEWHERE => format!("({})", self.word[a]),
|
||||||
|
LetterMatch::HERE => format!("[{}]", self.word[a]),
|
||||||
|
LetterMatch::NOWHERE => format!(" {} ", self.word[a]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
write!(f, "{}", result.join(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuessHistory<const N: usize> = Vec<GuessResult<N>>;
|
||||||
|
type Dictionary<const N: usize> = Vec<Word<N>>;
|
||||||
|
|
||||||
|
trait Evaluator<const N: usize> {
|
||||||
|
fn push(&mut self, _result: GuessResult<N>) {}
|
||||||
|
fn evaluate(&self, word: Word<N>) -> Matches<N>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Guesser<const N: usize> {
|
||||||
|
fn push(&mut self, _result: GuessResult<N>) {}
|
||||||
|
fn guess(&self) -> Word<N>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SecretEvaluator<const N: usize> {
|
||||||
|
secret: Word<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> SecretEvaluator<N> {
|
||||||
|
pub fn new(secret: Word<N>) -> Self {
|
||||||
|
Self {
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Evaluator<N> for SecretEvaluator<N> {
|
||||||
|
fn evaluate(&self, word: Word<N>) -> [LetterMatch; N] {
|
||||||
|
guess(&word, &self.secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct LetterConstraints<const N: usize> {
|
||||||
|
letter_minimums: HashMap<char, usize>,
|
||||||
|
letter_maximums: HashMap<char, usize>,
|
||||||
|
empty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LetterConstraints<N> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
letter_minimums: HashMap::new(),
|
||||||
|
letter_maximums: HashMap::new(),
|
||||||
|
empty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&self, word: Word<N>) -> bool {
|
||||||
|
if self.empty { return true; }
|
||||||
|
|
||||||
|
let mut letter_counts = HashMap::new();
|
||||||
|
for a in 0..N {
|
||||||
|
*letter_counts.entry(word[a]).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (letter, count) in letter_counts {
|
||||||
|
if count < *self.letter_minimums.get(&letter).unwrap_or(&0)
|
||||||
|
|| count > *self.letter_maximums.get(&letter).unwrap_or(&N) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LetterConstraints<N> {
|
||||||
|
fn push(&mut self, result: GuessResult<N>) {
|
||||||
|
self.empty = false;
|
||||||
|
|
||||||
|
let mut letter_matches = HashMap::new();
|
||||||
|
for a in 0..N {
|
||||||
|
letter_matches
|
||||||
|
.entry(result.word[a])
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(result.matches[a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (letter, matches) in letter_matches {
|
||||||
|
let nowheres = matches.iter().filter(|&m| *m == LetterMatch::NOWHERE).count();
|
||||||
|
let somewheres = matches.len() - nowheres;
|
||||||
|
self.letter_minimums.insert(
|
||||||
|
letter,
|
||||||
|
std::cmp::max(
|
||||||
|
*self.letter_minimums.get(&letter).unwrap_or(&0),
|
||||||
|
somewheres,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if nowheres > 0 {
|
||||||
|
self.letter_maximums.insert(
|
||||||
|
letter,
|
||||||
|
std::cmp::min(
|
||||||
|
*self.letter_maximums.get(&letter).unwrap_or(&N),
|
||||||
|
somewheres,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct RandomEasyGuesser<const N: usize> {
|
||||||
|
dictionary: Dictionary<N>,
|
||||||
|
history: GuessHistory<N>,
|
||||||
|
constraints: LetterConstraints<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> RandomEasyGuesser<N> {
|
||||||
|
pub fn new(dictionary: Dictionary<N>) -> Self {
|
||||||
|
Self {
|
||||||
|
dictionary,
|
||||||
|
history: GuessHistory::new(),
|
||||||
|
constraints: LetterConstraints::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Guesser<N> for RandomEasyGuesser<N> {
|
||||||
|
fn guess(&self) -> Word<N> {
|
||||||
|
*self.dictionary.choose(&mut rand::thread_rng()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, result: GuessResult<N>) {
|
||||||
|
self.history.push(result);
|
||||||
|
self.constraints.push(result);
|
||||||
|
let constraints = &self.constraints;
|
||||||
|
self.dictionary.retain(|&word| constraints.check(word));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Game<'a, const N: usize> {
|
||||||
|
guesser: &'a mut dyn Guesser<N>,
|
||||||
|
evaluator: &'a mut dyn Evaluator<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, const N: usize> Game<'a, N> {
|
||||||
|
fn next(&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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn play<const N: usize>(dictionary: Dictionary<N>) {
|
||||||
|
let secret = *dictionary.choose(&mut rand::thread_rng()).unwrap();
|
||||||
|
let mut game = Game {
|
||||||
|
evaluator: &mut SecretEvaluator::new(secret),
|
||||||
|
guesser: &mut RandomEasyGuesser::new(dictionary),
|
||||||
|
};
|
||||||
|
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|
|
||||||
|
line.chars()
|
||||||
|
.collect::<Vec<char>>()
|
||||||
|
.as_slice()
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let common_words: Vec<Word<5>> = load_words(include_str!("words/common.txt"));
|
||||||
|
play(common_words);
|
||||||
}
|
}
|
||||||
|
|
2315
src/words/common.txt
Normal file
2315
src/words/common.txt
Normal file
File diff suppressed because it is too large
Load diff
10657
src/words/uncommon.txt
Normal file
10657
src/words/uncommon.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue