init
This commit is contained in:
commit
b130df0391
16 changed files with 339925 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
192
Cargo.lock
generated
Normal file
192
Cargo.lock
generated
Normal file
|
@ -0,0 +1,192 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[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.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "turbotusmors"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "turbotusmors"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
default-run = "proxy"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
rayon = "1.10.0"
|
||||
|
||||
[[bin]]
|
||||
name = "proxy"
|
||||
path = "src/proxy.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "simulate"
|
||||
path = "src/simulate.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "bench"
|
||||
path = "src/bench.rs"
|
3
build.rs
Normal file
3
build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn main() {
|
||||
//
|
||||
}
|
20276
data/francais.txt
Normal file
20276
data/francais.txt
Normal file
File diff suppressed because it is too large
Load diff
318885
data/gutenberg.txt
Normal file
318885
data/gutenberg.txt
Normal file
File diff suppressed because it is too large
Load diff
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
hard_tabs = true
|
||||
max_width = 120
|
35
src/bench.rs
Normal file
35
src/bench.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
#![allow(dead_code, unused)]
|
||||
|
||||
// use core::iter::repeat;
|
||||
use game::{simulation::Simulation, Game};
|
||||
use rand::seq::SliceRandom;
|
||||
use rayon::iter::repeat;
|
||||
use rayon::prelude::*;
|
||||
use solve::grouping::Grouping;
|
||||
use std::env::args;
|
||||
|
||||
mod dictionnary;
|
||||
mod game;
|
||||
mod solve;
|
||||
|
||||
fn main() {
|
||||
let count = args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse().expect("Number expected."))
|
||||
.unwrap_or(100);
|
||||
// let dict = dictionnary::francais(7);
|
||||
let dict = dictionnary::gutenberg(7);
|
||||
(0..count).into_par_iter().for_each(|_| {
|
||||
let target = dict.choose(&mut rand::thread_rng()).unwrap().to_string();
|
||||
dbg!(&target);
|
||||
let solver = Grouping::new(dict.clone());
|
||||
let mut game = Simulation::new(target.clone());
|
||||
let result = game.play_all(solver, Some(20));
|
||||
match result {
|
||||
Ok((_, tries)) => println!("succ,{target},{tries}"),
|
||||
Err(reason) => println!("fail,{target},{reason}"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// fail,suedois,Reached limit.
|
30
src/dictionnary.rs
Normal file
30
src/dictionnary.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
|
||||
pub fn load(path: impl AsRef<Path>, width: usize) -> Vec<String> {
|
||||
let content = fs::read_to_string(path).expect("Can read file.");
|
||||
content
|
||||
.lines()
|
||||
.filter(|w| w.len() == width)
|
||||
.map(|l| l.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn francais(width: usize) -> Vec<String> {
|
||||
let raw = include_str!("../data/francais.txt");
|
||||
raw.lines()
|
||||
.filter(|w| w.len() == width)
|
||||
.map(|l| l.to_string())
|
||||
.collect()
|
||||
}
|
||||
pub fn gutenberg(width: usize) -> Vec<String> {
|
||||
let raw = include_str!("../data/gutenberg.txt");
|
||||
let mut res = raw
|
||||
.lines()
|
||||
.filter(|w| w.len() == width)
|
||||
.map(|l| l.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
res.shuffle(&mut thread_rng());
|
||||
res
|
||||
}
|
103
src/game/mod.rs
Normal file
103
src/game/mod.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use std::ops::Not;
|
||||
|
||||
use crate::solve::Solver;
|
||||
|
||||
pub mod proxy;
|
||||
pub mod simulation;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InfoKind {
|
||||
There,
|
||||
Elsewhere,
|
||||
Abscent,
|
||||
}
|
||||
|
||||
impl InfoKind {
|
||||
pub fn to_printable(self) -> char {
|
||||
match self {
|
||||
InfoKind::Abscent => '.',
|
||||
InfoKind::Elsewhere => '-',
|
||||
InfoKind::There => '+',
|
||||
}
|
||||
}
|
||||
pub fn from_printable(printable: char) -> Option<Self> {
|
||||
Some(match printable {
|
||||
'.' => InfoKind::Abscent,
|
||||
'-' => InfoKind::Elsewhere,
|
||||
'+' => InfoKind::There,
|
||||
_ => None?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Info {
|
||||
pub kind: InfoKind,
|
||||
pub character: char,
|
||||
}
|
||||
|
||||
impl Info {
|
||||
pub fn new(kind: InfoKind, character: char) -> Self {
|
||||
Self { kind, character }
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new(InfoKind::Abscent, '#')
|
||||
}
|
||||
|
||||
pub fn as_printable(infos: impl Iterator<Item = Self>) -> Vec<char> {
|
||||
infos.map(|i| i.kind.to_printable()).collect()
|
||||
}
|
||||
|
||||
pub fn from_printable(
|
||||
printables: impl Iterator<Item = char> + Clone,
|
||||
characters: impl Iterator<Item = char>,
|
||||
) -> Option<Vec<Self>> {
|
||||
let result = printables.map(InfoKind::from_printable);
|
||||
let success = result.clone().any(|o| o.is_none()).not();
|
||||
success.then(|| result.zip(characters).map(|(r, c)| Self::new(r.unwrap(), c)).collect())
|
||||
}
|
||||
|
||||
pub fn from_init(characters: impl Iterator<Item = char>) -> Vec<Self> {
|
||||
characters
|
||||
.map(|c| match c {
|
||||
'.' => Self::new(InfoKind::Abscent, '#'),
|
||||
_ => Self::new(InfoKind::There, c),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn to_word(infos: impl Iterator<Item = Self>) -> String {
|
||||
infos.map(|i| i.character).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Game {
|
||||
fn length(&mut self) -> usize;
|
||||
fn guess(&mut self, word: &str) -> Result<(), Vec<Info>>;
|
||||
|
||||
fn play_all(&mut self, mut solver: impl Solver, mut limit: Option<usize>) -> Result<(String, usize), String> {
|
||||
for try_ in 0.. {
|
||||
if let Some(limit) = limit {
|
||||
if limit == try_ {
|
||||
return Err("Reached limit.".into());
|
||||
}
|
||||
}
|
||||
let guess = solver.guess();
|
||||
match guess {
|
||||
None => return Err("".into()),
|
||||
Some(guess) => match self.guess(dbg!(&guess)) {
|
||||
Ok(()) => return Ok((guess, try_)),
|
||||
Err(infos) => {
|
||||
let printable = Info::as_printable(infos.iter().cloned())
|
||||
.into_iter()
|
||||
.collect::<String>();
|
||||
dbg!(printable);
|
||||
solver.learn(infos);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
43
src/game/proxy.rs
Normal file
43
src/game/proxy.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use super::{Game, Info};
|
||||
|
||||
pub struct Proxy {
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
pub fn init() -> (Self, Vec<Info>) {
|
||||
println!("Infos.");
|
||||
let infos = prompt();
|
||||
let infos = Info::from_init(infos.chars());
|
||||
let length = infos.len();
|
||||
(Self { length }, infos)
|
||||
}
|
||||
}
|
||||
|
||||
impl Game for Proxy {
|
||||
fn guess(&mut self, word: &str) -> Result<(), Vec<Info>> {
|
||||
println!("Guessing");
|
||||
println!(" {word}");
|
||||
loop {
|
||||
let result = prompt();
|
||||
let Some(infos) = Info::from_printable(result.chars(), word.chars()) else {
|
||||
continue;
|
||||
};
|
||||
return Err(infos);
|
||||
}
|
||||
}
|
||||
|
||||
fn length(&mut self) -> usize {
|
||||
self.length
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt() -> String {
|
||||
print!("> ");
|
||||
io::stdout().flush().ok();
|
||||
let mut line = String::new();
|
||||
io::stdin().read_line(&mut line).unwrap();
|
||||
line.trim_end_matches("\n").to_string()
|
||||
}
|
80
src/game/simulation.rs
Normal file
80
src/game/simulation.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use rayon::prelude::*;
|
||||
|
||||
use crate::game::Info;
|
||||
|
||||
use super::{Game, InfoKind};
|
||||
|
||||
pub struct Simulation {
|
||||
target: String,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new(target: impl ToString) -> Self {
|
||||
let target = target.to_string();
|
||||
Self { target }
|
||||
}
|
||||
}
|
||||
|
||||
impl Game for Simulation {
|
||||
fn guess(&mut self, word: &str) -> Result<(), Vec<Info>> {
|
||||
if word == self.target {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(wordle(&self.target, word))
|
||||
}
|
||||
}
|
||||
|
||||
fn length(&mut self) -> usize {
|
||||
self.target.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wordle(target: &str, word: &str) -> Vec<Info> {
|
||||
let mut buffs = wordle_buffs_for(target);
|
||||
wordle_inner(target, word, &mut buffs);
|
||||
buffs.2
|
||||
}
|
||||
|
||||
pub fn wordle_buffs_for(target: &str) -> (Vec<Option<InfoKind>>, Vec<Option<char>>, Vec<Info>) {
|
||||
let infos = target.chars().map(|_| None).collect();
|
||||
let letters = target.chars().map(Some).collect();
|
||||
let result = target.chars().map(|_| Info::empty()).collect();
|
||||
(infos, letters, result)
|
||||
}
|
||||
|
||||
pub fn wordle_inner(target: &str, word: &str, buffs: &mut (Vec<Option<InfoKind>>, Vec<Option<char>>, Vec<Info>)) {
|
||||
fn to_info(letter: &mut Option<char>, info: &mut Option<InfoKind>, kind: InfoKind) {
|
||||
*letter = None;
|
||||
info.insert(kind);
|
||||
}
|
||||
|
||||
let (infos, letters, result) = buffs;
|
||||
let d = drop;
|
||||
infos.iter_mut().for_each(|i| d(i.take()));
|
||||
let d = drop;
|
||||
letters.iter_mut().zip(target.chars()).for_each(|(l, c)| d(l.insert(c)));
|
||||
|
||||
word.chars()
|
||||
.zip(letters.iter_mut())
|
||||
.zip(infos.iter_mut())
|
||||
.filter_map(|((w, l), i)| l.is_some_and(|t| w == t).then_some((l, i)))
|
||||
.for_each(|(r, i)| to_info(r, i, InfoKind::There));
|
||||
|
||||
word.chars()
|
||||
.zip(infos.iter_mut())
|
||||
.filter(|(r, i)| i.is_none())
|
||||
.for_each(|(l, i)| {
|
||||
let r = letters.iter_mut().find(|e| e.as_ref().is_some_and(|e| *e == l));
|
||||
if let Some(r) = r {
|
||||
to_info(r, i, InfoKind::Elsewhere);
|
||||
}
|
||||
});
|
||||
|
||||
infos
|
||||
.iter()
|
||||
.map(|i| i.unwrap_or(InfoKind::Abscent))
|
||||
.zip(word.chars())
|
||||
.map(|(i, c)| Info::new(i, c))
|
||||
.zip(result.iter_mut())
|
||||
.for_each(|(i, r)| *r = i);
|
||||
}
|
20
src/proxy.rs
Normal file
20
src/proxy.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
#![allow(dead_code, unused)]
|
||||
|
||||
use game::{proxy::Proxy, Game};
|
||||
use solve::{
|
||||
grouping::{matches, Grouping},
|
||||
Solver,
|
||||
};
|
||||
|
||||
mod dictionnary;
|
||||
mod game;
|
||||
mod solve;
|
||||
|
||||
pub fn main() {
|
||||
let (mut game, infos) = Proxy::init();
|
||||
let dict = dictionnary::gutenberg(infos.len());
|
||||
let mut solver = Grouping::new(dict.into_iter().filter(|w| matches(w, &infos)));
|
||||
solver.learn(infos);
|
||||
let result = game.play_all(solver, Some(5));
|
||||
println!("{result:?}");
|
||||
}
|
23
src/simulate.rs
Normal file
23
src/simulate.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
#![allow(dead_code, unused)]
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use game::{simulation::Simulation, Game};
|
||||
use rand::seq::SliceRandom;
|
||||
use solve::grouping::Grouping;
|
||||
|
||||
mod dictionnary;
|
||||
mod game;
|
||||
mod solve;
|
||||
|
||||
fn main() {
|
||||
let first_arg = args().nth(1);
|
||||
let dict = dictionnary::gutenberg(first_arg.clone().map(|w| w.len()).unwrap_or(7));
|
||||
let random_word = || dict.choose(&mut rand::thread_rng()).unwrap().to_string();
|
||||
let target = first_arg.unwrap_or_else(random_word);
|
||||
dbg!(&target);
|
||||
let solver = Grouping::new(dict);
|
||||
let mut game = Simulation::new(target);
|
||||
let result = game.play_all(solver, Some(20));
|
||||
println!("{result:?}");
|
||||
}
|
203
src/solve/grouping.rs
Normal file
203
src/solve/grouping.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use std::{cmp::Ordering, collections::HashSet};
|
||||
|
||||
use rand::{
|
||||
seq::{IteratorRandom, SliceRandom},
|
||||
thread_rng,
|
||||
};
|
||||
use rayon::iter::{ParallelBridge, ParallelIterator};
|
||||
|
||||
use crate::{
|
||||
dictionnary::{self, gutenberg},
|
||||
game::{
|
||||
simulation::{wordle, wordle_buffs_for, wordle_inner},
|
||||
Info, InfoKind,
|
||||
},
|
||||
};
|
||||
|
||||
use super::Solver;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Grouping {
|
||||
dict: Vec<String>,
|
||||
candidates: Vec<String>,
|
||||
}
|
||||
|
||||
impl Grouping {
|
||||
pub fn new<S: ToString>(dict: impl IntoIterator<Item = S>) -> Self {
|
||||
let dict = dict.into_iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
let candidates = dict.clone();
|
||||
Self { dict, candidates }
|
||||
}
|
||||
|
||||
/// Gives a score to the word (lowest is best).
|
||||
fn rank_word(&self, word: &str) -> usize {
|
||||
let mut groups = [0; 3];
|
||||
let mut buffs = wordle_buffs_for(word);
|
||||
for target in &self.candidates {
|
||||
wordle_inner(target, word, &mut buffs);
|
||||
let infos = &buffs.2;
|
||||
for i in infos {
|
||||
match i.kind {
|
||||
InfoKind::Abscent => groups[0] += 1,
|
||||
InfoKind::Elsewhere => groups[1] += 1,
|
||||
InfoKind::There => groups[2] += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
let min = groups.iter().min().unwrap();
|
||||
let max = groups.iter().max().unwrap();
|
||||
(max - min)
|
||||
}
|
||||
}
|
||||
|
||||
impl Solver for Grouping {
|
||||
fn guess(&mut self) -> Option<String> {
|
||||
if self.candidates.len() == 1 {
|
||||
return self.candidates.first().cloned();
|
||||
}
|
||||
|
||||
self.dict
|
||||
.iter()
|
||||
.take(10_000)
|
||||
.par_bridge()
|
||||
.map(|word| (word, self.rank_word(word)))
|
||||
.min_by_key(|(_, score)| *score)
|
||||
.map(|(w, _)| w.to_string())
|
||||
.or_else(|| self.candidates.first().cloned())
|
||||
}
|
||||
|
||||
fn learn(&mut self, infos: Vec<Info>) {
|
||||
let word = Info::to_word(infos.clone().into_iter());
|
||||
if let Some(index) = self
|
||||
.dict
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, w)| (*w == *word).then_some(i))
|
||||
{
|
||||
self.dict.remove(index);
|
||||
}
|
||||
self.candidates = self.candidates.drain(..).filter(|w| matches(w, &infos)).collect();
|
||||
dbg!(&self.candidates);
|
||||
dbg!(self.candidates.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(word: &str, infos: &[Info]) -> bool {
|
||||
let results = word
|
||||
.chars()
|
||||
.zip(infos)
|
||||
.map(|(w, info)| match (w, info.kind, info.character) {
|
||||
(w, InfoKind::There, c) if w == c => (None, None),
|
||||
(_, InfoKind::There, _) => (Some(false), None),
|
||||
(w, InfoKind::Elsewhere, c) if w == c => (Some(false), None),
|
||||
(w, _, _) => (None, Some((info, w))),
|
||||
});
|
||||
if let Some(conclusion) = results.clone().filter_map(|(c, _)| c).next() {
|
||||
return conclusion;
|
||||
}
|
||||
|
||||
let remaining = results.filter_map(|(_, maybe)| maybe);
|
||||
let mut remaining_letters = remaining.clone().map(|(_, w)| Some(w)).collect::<Vec<_>>();
|
||||
let remaining_info = remaining.map(|(i, _)| i);
|
||||
for info in remaining_info {
|
||||
let found = remaining_letters
|
||||
.iter_mut()
|
||||
.find(|l| l.is_some_and(|l| l == info.character));
|
||||
match (info.kind, found) {
|
||||
(InfoKind::Abscent, Some(_)) => return false,
|
||||
(InfoKind::Abscent, _) => continue,
|
||||
(InfoKind::Elsewhere, None) => return false,
|
||||
(InfoKind::Elsewhere, Some(found)) => drop(found.take()),
|
||||
(InfoKind::There, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_misc() {
|
||||
// let grouping = Grouping::new(["cocon", "coton"]);
|
||||
// assert!(grouping.rank_word("coton") > grouping.rank_word("abaca"));
|
||||
|
||||
let grouping = Grouping::new(["abregee", "amorcee", "marquee", "assuree", "separee"]);
|
||||
dbg!(grouping.rank_word("volitif"));
|
||||
dbg!(grouping.rank_word("amorcee"));
|
||||
|
||||
let mut gut = gutenberg(7);
|
||||
dbg!(gut.len());
|
||||
|
||||
let mut grouping = Grouping::new(gut.into_iter().filter(|w| w.starts_with('j')));
|
||||
grouping.learn(vec![
|
||||
Info {
|
||||
kind: InfoKind::There,
|
||||
character: 'j',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Elsewhere,
|
||||
character: 'u',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Abscent,
|
||||
character: 'v',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Elsewhere,
|
||||
character: 'e',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Abscent,
|
||||
character: 'n',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Abscent,
|
||||
character: 'a',
|
||||
},
|
||||
Info {
|
||||
kind: InfoKind::Elsewhere,
|
||||
character: 't',
|
||||
},
|
||||
]);
|
||||
dbg!(grouping.rank_word("jouxtee"));
|
||||
dbg!(grouping.rank_word("jutions"));
|
||||
dbg!(grouping.rank_word("jaspine"));
|
||||
dbg!(grouping.guess());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches() {
|
||||
let cases = [
|
||||
("gargouille", "gargouille", "+....-...-", false),
|
||||
("gargouille", "guetteuses", "+....-...-", true),
|
||||
("grognement", "grognement", "+....+.-.-", false),
|
||||
("grognement", "guetteuses", "+....+.-.-", true),
|
||||
("gesticuler", "gesticuler", "+--+..+.+.", false),
|
||||
("gesticuler", "guetteuses", "+--+..+.+.", true),
|
||||
("guetteuses", "guetteuses", "++++++++++", true),
|
||||
("inoculeras", "inoculeras", "+-.-+.+-..", false),
|
||||
("inoculeras", "imprudence", "+-.-+.+-..", true),
|
||||
("implosions", "implosions", "+++.....-.", false),
|
||||
("implosions", "imprudence", "+++.....-.", true),
|
||||
("imprudents", "imprudents", "++++++++..", false),
|
||||
("imprudents", "imprudence", "++++++++..", true),
|
||||
("imprudence", "imprudence", "++++++++++", true),
|
||||
("cocon", "coton", "++.++", true),
|
||||
("coton", "coton", "+++++", true),
|
||||
];
|
||||
|
||||
for (tried, word, infos, matches_) in cases {
|
||||
let infos_ = Info::from_printable(infos.chars(), tried.chars()).expect("Info deserialized correctly.");
|
||||
assert_eq!(
|
||||
matches(word, &infos_),
|
||||
matches_,
|
||||
"Asserts that matching '{}' against {} ({}) returns {}.\n{}\n{}\n{}",
|
||||
word,
|
||||
infos,
|
||||
tried,
|
||||
matches_,
|
||||
word,
|
||||
infos,
|
||||
tried,
|
||||
);
|
||||
}
|
||||
}
|
8
src/solve/mod.rs
Normal file
8
src/solve/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use crate::game::Info;
|
||||
|
||||
pub trait Solver {
|
||||
fn learn(&mut self, infos: Vec<Info>);
|
||||
fn guess(&mut self) -> Option<String>;
|
||||
}
|
||||
|
||||
pub mod grouping;
|
Loading…
Add table
Add a link
Reference in a new issue