othello/src/elo.rs
2025-03-04 14:59:00 -05:00

163 lines
4.8 KiB
Rust

use crate::{
agent::Agent,
complexagent::ComplexAgent,
game_inner::GameInner,
logic::{ChildrenEvalMethod, FutureMoveConfig},
repr::{Board, Piece, Winner},
};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use skillratings::{
elo::{elo, EloConfig, EloRating},
Outcomes, Rating,
};
pub fn run() {
const FMV_BASE: FutureMoveConfig = FutureMoveConfig {
max_depth: 20,
min_arena_depth: 14,
top_k_children: 2,
up_to_minus: 10,
max_arena_size: usize::MAX,
do_not_prune: true,
print: false,
children_eval_method: ChildrenEvalMethod::Max,
};
let vec: Vec<(String, Box<dyn Fn(Piece) -> Box<dyn Agent>>)> = (1..=6)
.flat_map(|d| {
[
ChildrenEvalMethod::Average,
// ChildrenEvalMethod::Max,
// ChildrenEvalMethod::Min,
]
.into_iter()
.map(move |m| -> (String, Box<dyn Fn(Piece) -> Box<dyn Agent>>) {
(
format!("ComplexAgentD{}{:?}", d, m),
Box::new(move |piece| {
Box::new(ComplexAgent::new(
piece,
FutureMoveConfig {
max_depth: d,
children_eval_method: m,
..FMV_BASE
},
))
}),
)
})
})
.collect();
let mut arena = PlayerArena::new(vec);
arena.prop_arena(300);
println!("{}", arena);
}
pub struct PlayerArena {
/// Name, Creator Function, Elo
players: Vec<(String, Box<dyn Fn(Piece) -> Box<dyn Agent>>, EloRating)>,
}
impl std::fmt::Display for PlayerArena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut players_i: Vec<usize> = (0..self.players.len()).collect();
players_i.sort_by_key(|&i| -(self.players[i].2.rating() * 100.0) as i64);
for i in players_i {
writeln!(
f,
"({:.2}): {}",
self.players[i].2.rating(),
self.players[i].0
)?;
}
Ok(())
}
}
impl PlayerArena {
pub fn new(players: Vec<(String, Box<dyn Fn(Piece) -> Box<dyn Agent>>)>) -> Self {
Self {
players: players
.into_iter()
.zip([EloRating::new()].into_iter().cycle())
.map(|((a, b), c)| (a, b, c))
.collect(),
}
}
fn play(&mut self, pairs: &[(usize, usize)]) {
pairs
.iter()
.map(|&(i, j)| {
(
(i, j),
Self::create_agents(&self.players[i].1, &self.players[j].1),
)
})
.collect::<Vec<_>>()
// after the agents are created, we can multithread the games being played
.into_par_iter()
.map(|((i, j), (p1, p2))| (i, j, Self::play_two_inner(p1, p2)))
.collect::<Vec<_>>()
// collect and process the outcomes of all the games
.into_iter()
.for_each(|(i, j, o)| self.process_outcome(i, j, &o));
}
fn prop_arena(&mut self, n: usize) {
self.play(
&(0..self.players.len())
.flat_map(|i| {
(0..self.players.len())
.map(move |j| (i, j))
.filter(|(i, j)| i != j)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.repeat(n),
);
}
fn process_outcome(&mut self, player1: usize, player2: usize, outcome: &Outcomes) {
let (np1, np2) = elo(
&self.players[player1].2,
&self.players[player2].2,
outcome,
&EloConfig::new(),
);
self.players[player1].2 = np1;
self.players[player2].2 = np2;
}
fn create_agents(
player_1_fn: &dyn Fn(Piece) -> Box<dyn Agent>,
player_2_fn: &dyn Fn(Piece) -> Box<dyn Agent>,
) -> (Box<dyn Agent>, Box<dyn Agent>) {
(player_1_fn(Piece::Black), player_2_fn(Piece::White))
}
fn play_two_inner(player_1: Box<dyn Agent>, player_2: Box<dyn Agent>) -> Outcomes {
let result = GameInner::new(
player_1,
player_2,
false,
Board::random(rand::random_range(3..8)),
)
.loop_until_result();
match result {
Winner::Player(piece) => match piece {
Piece::Black => Outcomes::WIN,
Piece::White => Outcomes::LOSS,
},
Winner::Tie => Outcomes::DRAW,
Winner::None => panic!("somehow met None"),
}
}
}