initial elo rating stuff

This commit is contained in:
2025-03-02 22:08:12 -05:00
parent f6c2ef753d
commit 1dd1f3da25
8 changed files with 129 additions and 5 deletions

7
Cargo.lock generated
View File

@@ -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"

View File

@@ -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]

View File

@@ -23,8 +23,6 @@ impl Agent for ComplexAgent {
fn next_move(&mut self, board: &Board) -> Option<CoordPair> {
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?")

101
src/elo.rs Normal file
View File

@@ -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<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_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;
}
}

View File

@@ -72,7 +72,10 @@ impl GameInner {
}
}
} else {
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 {

View File

@@ -1,5 +1,6 @@
mod agent;
mod complexagent;
mod elo;
mod game;
mod game_inner;
pub mod logic;

View File

@@ -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 {
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]

View File

@@ -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(