use std::sync::{Arc, Mutex};
use indicatif::ParallelProgressIterator;
use rand::seq::index::sample;
use rayon::prelude::*;
use crate::{
perf::Perf,
strategy::{AttemptsKey, Puzzle, Strategy, Word},
words::ANSWERS,
WordleError,
};
#[derive(Debug)]
pub struct Harness {
strategies: Vec<Box<dyn Strategy>>,
verbose: bool,
num_guesses: Option<usize>,
}
impl Default for Harness {
fn default() -> Self {
Harness {
strategies: Vec::new(),
verbose: false,
num_guesses: Some(100),
}
}
}
impl Harness {
pub fn new() -> Self {
Self::default()
}
pub fn verbose(self) -> Self {
Harness {
verbose: true,
..self
}
}
pub fn quiet(self) -> Self {
Harness {
verbose: false,
..self
}
}
pub fn add_strategy(self, strat: Box<dyn Strategy>) -> Self {
let mut strategies = self.strategies;
strategies.push(strat);
Harness { strategies, ..self }
}
pub fn add_strategies(self, strats: Vec<Box<dyn Strategy>>) -> Self {
let mut strategies = self.strategies;
strategies.extend(strats);
Harness { strategies, ..self }
}
pub fn test_all(self) -> Self {
Harness {
num_guesses: None,
..self
}
}
pub fn test_num(self, n: usize) -> Self {
Harness {
num_guesses: Some(n.clamp(0, ANSWERS.len())),
..self
}
}
pub fn debug_run(&self, words: Option<&[Word]>) -> Result<Vec<Perf>, WordleError> {
use std::panic::{self, AssertUnwindSafe};
let mut perfs = Vec::new();
for strat in &self.strategies {
perfs.push(Perf::new(strat.as_ref()))
}
let words = match words {
Some(w) => Vec::from(w),
None => ANSWERS
.iter()
.map(|&i| Word::from_index(i))
.collect::<Result<Vec<_>, _>>()
.unwrap(),
};
for word in words.iter() {
for (i, strategy) in self.strategies.iter().enumerate() {
let key = AttemptsKey::new(strategy.hardmode());
let res = {
let wrapper = AssertUnwindSafe(strategy);
panic::catch_unwind(|| {
let mut puzzle = Puzzle::new(*word);
let attempts = (*wrapper).solve(&mut puzzle, key);
(puzzle, attempts)
})
}
.map_or_else(
|_| {
println!("strategy {strategy} panicked on puzzle {word}");
println!("------------");
None
},
Some,
);
if let Some((puzzle, solution)) = res {
perfs[i].tries.push((*word, solution));
if puzzle.poisoned {
return Err(WordleError::StrategyCheated(format!("{}", strategy)));
}
}
}
}
Ok(perfs)
}
pub fn run(&self) -> Result<Vec<Perf>, WordleError> {
let perfs = Arc::new(Mutex::new(Vec::new()));
{
let mut perfs = perfs.lock().unwrap();
for strat in &self.strategies {
perfs.push(Perf::new(strat.as_ref()))
}
}
let mut rng = rand::thread_rng();
if let Some(n) = self.num_guesses {
if self.verbose {
sample(&mut rng, ANSWERS.len(), n)
.iter()
.par_bridge()
.progress_count(n as u64)
.map(|i| self.run_inner(ANSWERS[i], perfs.clone()))
.collect::<Result<(), WordleError>>()?;
} else {
sample(&mut rng, ANSWERS.len(), n)
.iter()
.par_bridge()
.map(|i| self.run_inner(ANSWERS[i], perfs.clone()))
.collect::<Result<(), WordleError>>()?;
}
} else {
if self.verbose {
(0..ANSWERS.len())
.into_par_iter()
.progress()
.map(|i| self.run_inner(ANSWERS[i], perfs.clone()))
.collect::<Result<(), WordleError>>()?;
} else {
(0..ANSWERS.len())
.into_par_iter()
.map(|i| self.run_inner(ANSWERS[i], perfs.clone()))
.collect::<Result<(), WordleError>>()?;
}
}
Ok(Arc::try_unwrap(perfs).unwrap().into_inner().unwrap())
}
fn run_inner(&self, index: usize, perfs: Arc<Mutex<Vec<Perf>>>) -> Result<(), WordleError> {
let word = Word::from_index(index).unwrap();
let mut puzzle = Puzzle::new(word);
for (i, strategy) in self.strategies.iter().enumerate() {
let key = AttemptsKey::new(strategy.hardmode());
let solution = strategy.solve(&mut puzzle, key);
{
let mut perfs = perfs.lock().unwrap();
perfs[i].tries.push((word, solution));
}
if puzzle.poisoned {
return Err(WordleError::StrategyCheated(format!("{}", strategy)));
}
}
Ok(())
}
pub fn run_and_summarize(&self) -> Result<Vec<Perf>, WordleError> {
let perfs = self.run()?;
for perf in &perfs {
println!("{}", perf);
}
Ok(perfs)
}
}