did stuff

This commit is contained in:
ashastral 2022-02-25 23:51:46 -08:00
parent f597fb751b
commit 0023a73b83
5 changed files with 13294 additions and 2 deletions

68
Cargo.lock generated
View file

@ -2,6 +2,74 @@
# It is not intended for manual editing.
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]]
name = "optimle"
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"

View file

@ -6,3 +6,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"

View file

@ -1,3 +1,254 @@
fn main() {
println!("Hello, world!");
use std::collections::HashMap;
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

File diff suppressed because it is too large Load diff

10657
src/words/uncommon.txt Normal file

File diff suppressed because it is too large Load diff