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