use crate::agent_collector::collect_agents;
use crate::configuration::Configuration;
use crate::constraints::Constraints;
use crate::game_interface::{Game, GameFactory};
use crate::logger::init_logger;
use crate::match_runner::{run_match, MatchSettings, RunnerResult};
use crate::tournament_scheduler::TournamentScheduler;
use crate::tournament_strategy::TournamentStrategy;
use std::collections::HashMap;
use std::fmt::Display;
use std::sync::mpsc::Sender;
use std::sync::{mpsc, Arc, Mutex};
use tracing::{info, instrument, trace};
pub struct Evaluator<G: Game, F>
where
F: GameFactory<G>,
{
factory: F,
constraints: Constraints,
config: Configuration,
_ff: std::marker::PhantomData<G>,
}
impl<G: Game + Send + 'static, F: GameFactory<G>> Evaluator<G, F> {
#[instrument(skip_all)]
pub fn new(factory: F, config: Configuration, constraints: Constraints) -> Evaluator<G, F> {
if config.log {
init_logger();
}
trace!(?config, ?constraints);
Evaluator {
factory,
config,
constraints,
_ff: std::marker::PhantomData,
}
}
pub fn evaluate<T: TournamentStrategy<G::Score>>(
&self,
directory: impl AsRef<std::path::Path>,
mut tournament: T,
) -> anyhow::Result<HashMap<String, T::FinalScore>>
where
T::FinalScore: 'static,
{
Self::setup_panic_hook(self.config.verbose);
if self.config.verbose {
disable_line_wrap();
}
let agents = collect_agents(directory.as_ref(), self.config)?;
info!(?agents);
tournament.add_agents(agents);
let mut scheduler = TournamentScheduler::new(self.constraints.clone(), tournament);
let (tx_result, rx_result) = mpsc::channel();
let running = Arc::new(Mutex::new(vec![]));
self.launch_initial_matches(&mut scheduler, &tx_result, &running);
while !scheduler.is_finished() {
let result = rx_result.recv().unwrap();
for new_match in scheduler.on_result(result) {
self.launch_match(new_match, tx_result.clone(), &running);
}
}
if self.config.verbose {
enable_line_wrap();
}
Ok(Self::collect_final_scores(&scheduler))
}
fn setup_panic_hook(verbose: bool) {
let orig_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if verbose {
enable_line_wrap();
}
orig_hook(panic_info);
std::process::exit(1);
}));
}
fn launch_initial_matches<T: TournamentStrategy<G::Score>>(
&self,
scheduler: &mut TournamentScheduler<T, G::Score>,
tx_result: &Sender<RunnerResult<G::Score>>,
running: &Arc<Mutex<Vec<MatchSettings>>>,
) {
for m in scheduler.advance() {
self.launch_match(m, tx_result.clone(), running);
}
}
fn collect_final_scores<T: TournamentStrategy<G::Score>>(
scheduler: &TournamentScheduler<T, G::Score>,
) -> HashMap<String, T::FinalScore> {
scheduler
.final_scores()
.into_iter()
.map(|(agent, score)| (agent.name.clone(), score))
.collect::<HashMap<_, _>>()
}
fn launch_match(
&self,
match_settings: MatchSettings,
tx_result: Sender<RunnerResult<G::Score>>,
running: &Arc<Mutex<Vec<MatchSettings>>>,
) {
let game = self.factory.new_game();
let mutex = running.clone();
let mut guard = mutex.lock().expect("poisoned");
guard.push(match_settings.clone());
if self.config.verbose {
print_running_matches(&guard);
}
drop(guard);
let config = self.config;
std::thread::spawn(move || {
let result = run_match(match_settings.clone(), config, game);
if config.verbose {
print_runner_result(&match_settings, &result);
}
Self::remove_running_match(&mutex, &match_settings);
tx_result.send(result).unwrap();
});
}
fn remove_running_match(mutex: &Mutex<Vec<MatchSettings>>, running: &MatchSettings) {
let mut guard = mutex.lock().expect("poisoned");
let pos = guard
.iter()
.position(|s| s == running)
.expect("error: got result of a match that was not started (or got result twice)");
guard.remove(pos);
}
}
fn print_runner_result<S: Display + PartialOrd>(
match_settings: &MatchSettings,
result: &RunnerResult<S>,
) {
let mut ordered_scores = Vec::new();
for player in &match_settings.ordered_player {
let name = &player.name;
let res = result
.results
.iter()
.find_map(|a| if &a.0.name == name { Some(&a.1) } else { None })
.expect("agent not found");
ordered_scores.push(res);
}
let ordered_scores = ordered_scores
.into_iter()
.map(|f| format!("{f}"))
.collect::<Vec<_>>()
.join("-");
println!(
"\x1b[2K\x1b[32m{match_settings}: \x1b[39m{ordered_scores} \x1b[31m{}\x1b[39m\x1b[0G",
result.errors
);
}
fn print_running_matches(running: &[MatchSettings]) {
print!(
"\x1b[2K\x1b[32mRunning...:\x1b[39m {}\x1b[0G",
running
.iter()
.map(MatchSettings::to_string)
.collect::<Vec<_>>()
.join(", ")
);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
fn disable_line_wrap() {
print!("\x1b[?7l");
}
fn enable_line_wrap() {
print!("\x1b[?7h");
}