From 1dd1f3da25e42f51a9277f36f1bb2b08903c47e0 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Sun, 2 Mar 2025 22:08:12 -0500 Subject: [PATCH] initial elo rating stuff --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/complexagent.rs | 2 - src/elo.rs | 101 ++++++++++++++++++++++++++++++++++++++ src/game_inner.rs | 5 +- src/lib.rs | 1 + src/logic/future_moves.rs | 12 ++++- src/main.rs | 5 ++ 8 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/elo.rs diff --git a/Cargo.lock b/Cargo.lock index f367ffc..de666c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,6 +438,7 @@ dependencies = [ "nohash-hasher", "num", "rand", + "skillratings", "static_assertions", ] @@ -640,6 +641,12 @@ dependencies = [ "serde", ] +[[package]] +name = "skillratings" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c8196a815d27d6dbd2439058a2cbf6597a549a68ca6368611df240df7c2987" + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 923c6c6..ab62fbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ indicatif = "0.17" nohash-hasher = "0.2" num = "0.4" rand = "0.9" +skillratings = "0.27.1" static_assertions = "1.1" [dev-dependencies] diff --git a/src/complexagent.rs b/src/complexagent.rs index 1bc50b4..345ab2a 100644 --- a/src/complexagent.rs +++ b/src/complexagent.rs @@ -23,8 +23,6 @@ impl Agent for ComplexAgent { fn next_move(&mut self, board: &Board) -> Option { self.future_moves.update_from_board(board); - println!("# of moves stored: {}", self.future_moves.arena_len()); - self.future_moves .best_move() .expect("FutureMoves has no move?") diff --git a/src/elo.rs b/src/elo.rs new file mode 100644 index 0000000..cc5f652 --- /dev/null +++ b/src/elo.rs @@ -0,0 +1,101 @@ +use crate::{ + agent::{Agent, RandomAgent}, + complexagent::ComplexAgent, + game_inner::GameInner, + logic::FutureMoveConfig, + repr::{Piece, Winner}, +}; +use skillratings::{ + elo::{elo, EloConfig, EloRating}, + Outcomes, Rating, +}; + +pub fn run() { + let mut arena = PlayerArena::new(vec![ + ( + "RandomAgent".into(), + Box::new(|piece| Box::new(RandomAgent::new(piece))), + ), + ( + "ComplexAgentBasic".into(), + Box::new(|piece| { + Box::new(ComplexAgent::new( + piece, + FutureMoveConfig { + max_depth: 20, + min_arena_depth: 14, + top_k_children: 2, + up_to_minus: 10, + max_arena_size: 100_000, + do_not_prune: false, + print: false, + }, + )) + }), + ), + ]); + for _ in 0..10 { + arena.play_two(0, 1); + } + + println!("{}", arena); +} + +pub struct PlayerArena { + players: Vec<(String, Box Box>, EloRating)>, +} + +impl std::fmt::Display for PlayerArena { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut players_i: Vec = (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 Box>)>) -> Self { + Self { + players: players + .into_iter() + .zip([EloRating::new()].into_iter().cycle()) + .map(|((a, b), c)| (a, b, c)) + .collect(), + } + } + + fn play_two(&mut self, player1: usize, player2: usize) { + let result = GameInner::new( + self.players[player1].1(Piece::Black), + self.players[player2].1(Piece::White), + false, + ) + .loop_until_result(); + let outcome = 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"), + }; + 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; + } +} diff --git a/src/game_inner.rs b/src/game_inner.rs index 04fcb12..cf7d624 100644 --- a/src/game_inner.rs +++ b/src/game_inner.rs @@ -72,7 +72,10 @@ impl GameInner { } } } else { - println!("Player {} did not make a move!", player_i); + if self.do_print { + println!("Player {} did not make a move!", player_i); + } + // players are able to skip a move if they have no valid moves to make if self.board.possible_moves(player_color).next().is_some() { if self.do_print { diff --git a/src/lib.rs b/src/lib.rs index 544b748..2d62ee7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod agent; mod complexagent; +mod elo; mod game; mod game_inner; pub mod logic; diff --git a/src/logic/future_moves.rs b/src/logic/future_moves.rs index b923a39..7066499 100644 --- a/src/logic/future_moves.rs +++ b/src/logic/future_moves.rs @@ -41,6 +41,8 @@ pub struct FutureMoveConfig { pub max_arena_size: usize, pub do_not_prune: bool, + + pub print: bool, } impl FutureMoves { @@ -92,7 +94,7 @@ impl FutureMoves { } for _ in self.current_depth..self.config.max_depth { - let pstyle_inner = if cfg!(test) { + let pstyle_inner = if cfg!(test) || !self.config.print { "" } else { &format!( @@ -273,7 +275,9 @@ impl FutureMoves { self.set_root_idx_raw(curr_board_idx); false } else { - println!("regenerating arena from board"); + if self.config.print { + println!("regenerating arena from board"); + } self.set_root_from_board(*board); true } @@ -304,6 +308,9 @@ impl FutureMoves { self.refocus_tree(); self.extend_layers(); + if self.config.print { + println!("# of moves stored: {}", self.arena_len()); + } self.compute_values(0..self.arena.len()); // check arena's consistancy @@ -467,6 +474,7 @@ mod tests { up_to_minus: 0, max_arena_size: 100, do_not_prune: true, + print: false, }; #[test] diff --git a/src/main.rs b/src/main.rs index 4c9825e..ddc2c35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ +use elo::run; use game::Game; use logic::FutureMoveConfig; use repr::Piece; mod agent; mod complexagent; +mod elo; mod game; mod game_inner; mod logic; @@ -11,6 +13,8 @@ pub mod repr; // TODO! make this agent configuration a config option via `clap-rs` fn main() { + run(); + return; let player1 = complexagent::ComplexAgent::new( Piece::Black, FutureMoveConfig { @@ -20,6 +24,7 @@ fn main() { up_to_minus: 10, max_arena_size: 50_000_000, do_not_prune: false, + print: true, }, ); // let player2 = complexagent::ComplexAgent::new(