Files
othello/src/elo.rs

176 lines
5.0 KiB
Rust

use crate::{
agent::{Agent, RandomAgent},
complexagent::ComplexAgent,
game_inner::GameInner,
logic::FutureMoveConfig,
repr::{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: 5_000_000,
do_not_prune: false,
print: false,
};
let mut arena = PlayerArena::new(vec![
(
"RandomAgent".into(),
Box::new(|piece| Box::new(RandomAgent::new(piece))),
),
(
"ComplexAgentD5".into(),
Box::new(|piece| {
Box::new(ComplexAgent::new(
piece,
FutureMoveConfig {
max_depth: 5,
..FMV_BASE
},
))
}),
),
(
"ComplexAgentD6".into(),
Box::new(|piece| {
Box::new(ComplexAgent::new(
piece,
FutureMoveConfig {
max_depth: 6,
..FMV_BASE
},
))
}),
),
(
"ComplexAgentD7".into(),
Box::new(|piece| {
Box::new(ComplexAgent::new(
piece,
FutureMoveConfig {
max_depth: 7,
..FMV_BASE
},
))
}),
),
]);
arena.play(
&(0..arena.players.len())
.zip([0].into_iter().cycle())
.filter(|(i, j)| i != j)
.collect::<Vec<_>>()
.repeat(20),
);
println!("{}", arena);
}
pub struct PlayerArena {
/// Name, Creator, 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
.into_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) {
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<_>>(),
);
}
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: &Box<dyn Fn(Piece) -> Box<dyn Agent>>,
player_2_fn: &Box<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).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"),
}
}
}