Compare commits

...

5 Commits

Author SHA1 Message Date
189a5aef58 fix 2025-04-28 01:18:46 -04:00
c54f3a7493 elo test 2025-04-28 01:17:28 -04:00
c3c07fcb28 probabilistic progress 2025-04-28 01:12:15 -04:00
2586b43294 fix mvs 2025-04-24 13:20:07 -04:00
14254e44ba elo: fix display 2 2025-04-24 13:12:02 -04:00
4 changed files with 68 additions and 68 deletions

View File

@@ -5,7 +5,7 @@ use crate::{
logic::{ChildrenEvalMethod, FutureMoveConfig}, logic::{ChildrenEvalMethod, FutureMoveConfig},
repr::{Board, Piece, Winner}, repr::{Board, Piece, Winner},
}; };
use indicatif::{ParallelProgressIterator, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use skillratings::{ use skillratings::{
@@ -119,14 +119,17 @@ pub fn run() {
) )
}) })
.collect(); .collect();
if true {
vec.push(( vec.push((
"RandomAgent".to_string(), "RandomAgent".to_string(),
Box::new(move |piece| Box::new(RandomAgent::new(piece))), Box::new(move |piece| Box::new(RandomAgent::new(piece))),
)); ));
}
let mut arena = PlayerArena::new(vec); let mut arena = PlayerArena::new(vec);
arena.prop_arena(1000); arena.prop_arena(100);
println!("{}", arena); println!("{}", arena);
} }
@@ -172,7 +175,10 @@ impl PlayerArena {
.map(|&(i, j)| { .map(|&(i, j)| {
( (
(i, j), (i, j),
Self::create_agents(&self.players[i].1, &self.players[j].1), (
(self.players[i].1)(Piece::Black),
(self.players[j].1)(Piece::White),
),
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -202,12 +208,6 @@ impl PlayerArena {
created_pairs created_pairs
.into_par_iter() .into_par_iter()
.progress_with_style({
ProgressStyle::with_template(
"[{elapsed_precise}] {pos:>7}/{len:7} ETA: {eta}",
)
.expect("invalid ProgressStyle")
})
.map(|((i, j), (p1, p2))| (i, j, Self::play_two_inner(p1, p2))) .map(|((i, j), (p1, p2))| (i, j, Self::play_two_inner(p1, p2)))
.for_each(|(i, j, o)| { .for_each(|(i, j, o)| {
sender.send((i, j, o)).expect("Failed to send result"); sender.send((i, j, o)).expect("Failed to send result");
@@ -220,15 +220,23 @@ impl PlayerArena {
// Process results on main thread as they arrive // Process results on main thread as they arrive
let mut received_num = 0; let mut received_num = 0;
let p = ProgressBar::new(num as u64).with_style(
ProgressStyle::with_template("[{elapsed_precise}] {pos:>7}/{len:7} ETA: {eta}")
.expect("invalid ProgressStyle"),
);
while let Ok((i, j, o)) = receiver.recv() { while let Ok((i, j, o)) = receiver.recv() {
self.process_outcome(i, j, &o); self.process_outcome(i, j, &o);
received_num += 1;
if received_num > 0 {
term.clear_last_lines(self.players.len()) term.clear_last_lines(self.players.len())
.expect("unable to clear prev lines"); .expect("unable to clear prev lines");
}
term.write_str(format!("{}", self).as_str()) term.write_str(format!("{}", self).as_str())
.expect("unable to write leaderboard"); .expect("unable to write leaderboard");
received_num += 1;
p.inc(1);
// break if all pairs were recieved // break if all pairs were recieved
if received_num == num { if received_num == num {
break; break;
@@ -265,19 +273,13 @@ impl PlayerArena {
self.players[player2].2 = np2; self.players[player2].2 = np2;
} }
fn create_agents(
player_1_fn: &AgentMaker,
player_2_fn: &AgentMaker,
) -> (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 { fn play_two_inner(player_1: Box<dyn Agent>, player_2: Box<dyn Agent>) -> Outcomes {
let result = GameInner::new( let result = GameInner::new(
player_1, player_1,
player_2, player_2,
false, false,
Board::random(rand::random_range(1..=15)), // Board::random(rand::random_range(20..=30)),
Board::STARTING_POSITION,
) )
.expect("unable to create game") .expect("unable to create game")
.loop_until_result(); .loop_until_result();

View File

@@ -315,23 +315,14 @@ impl FutureMoves {
// get best (for the adversary) enemy play // get best (for the adversary) enemy play
// this assumes the adversary is playing optimally // this assumes the adversary is playing optimally
children_values children_values.into_iter().map(|x| x.value).min()
.into_iter()
.min_by_key(|x| x.value)
.map(|x| x.value)
} else { } else {
children_values children_values.into_iter().map(|x| x.value).max()
.into_iter()
.max_by_key(|x| x.value)
.map(|x| x.value)
} }
.unwrap_or(0); .unwrap_or(0);
// we use `depth` and divided `self_value` by it, idk if this is worth it
// we should really setup some sort of ELO rating for each commit, playing them against
// each other or something, could be cool to benchmark these more subjective things, not
// just performance (cycles/time wise)
self.arena[idx].value.value = self.arena[idx].value.value =
self.arena[idx].self_value as i32 + child_value; self.arena[idx].self_value.value + child_value;
} }
ChildrenEvalMethod::MinMaxProb => { ChildrenEvalMethod::MinMaxProb => {
let child_value = if self.arena[idx].color == self.agent_color { let child_value = if self.arena[idx].color == self.agent_color {
@@ -344,16 +335,12 @@ impl FutureMoves {
} }
.cloned() .cloned()
.unwrap_or(Default::default()); .unwrap_or(Default::default());
// we use `depth` and divided `self_value` by it, idk if this is worth it
// we should really setup some sort of ELO rating for each commit, playing them against self.arena[idx].value = self.arena[idx].self_value;
// each other or something, could be cool to benchmark these more subjective things, not
// just performance (cycles/time wise)
self.arena[idx] self.arena[idx]
.value .value
.populate_self_from_children(&children_values); .populate_self_from_children(&children_values);
self.arena[idx].value.value += child_value.value;
self.arena[idx].value.value =
self.arena[idx].self_value as i32 + child_value.value;
} }
} }
} }

View File

@@ -29,7 +29,7 @@ pub struct Move {
pub value: MoveValueStats, pub value: MoveValueStats,
/// What is the inherit value of this move (not including children) /// What is the inherit value of this move (not including children)
pub self_value: i16, pub self_value: MoveValueStats,
/// Which color made a move on this move? /// Which color made a move on this move?
pub color: Piece, pub color: Piece,
@@ -58,28 +58,29 @@ impl Move {
value: Default::default(), value: Default::default(),
color, color,
is_trimmed: false, is_trimmed: false,
self_value: 0, self_value: Default::default(),
}; };
// set wins/losses values appropriately // set wins/losses values appropriately
match m.winner { match m.winner {
Winner::Player(piece) => { Winner::Player(piece) => {
if piece == agent_color { if piece == agent_color {
m.value.set_state(Some(MVSGameState::Win)); m.self_value.set_state(Some(MVSGameState::Win));
} else { } else {
m.value.set_state(Some(MVSGameState::Loss)); m.self_value.set_state(Some(MVSGameState::Loss));
} }
} }
Winner::Tie => { Winner::Tie => {
m.value.set_state(Some(MVSGameState::Tie)); m.self_value.set_state(Some(MVSGameState::Tie));
} }
Winner::None => {} Winner::None => {}
} }
if mvc.self_value_raw { if mvc.self_value_raw {
m.self_value = const { BoardValueMap::weighted() }.board_value(&board, agent_color); m.self_value.value =
const { BoardValueMap::weighted() }.board_value(&board, agent_color) as i32;
} else { } else {
m.self_value = m.compute_self_value(agent_color, &board, mvc); m.self_value.value = m.compute_self_value(agent_color, &board, mvc) as i32;
} }
m m
} }

View File

@@ -13,43 +13,39 @@ pub struct MoveValueStats {
state: Option<MVSGameState>, state: Option<MVSGameState>,
wins: u16, wins: u16,
losses: u16, losses: u16,
ties: u16,
pub value: i32, pub value: i32,
} }
impl MoveValueStats { impl MoveValueStats {
#[cfg(test)] #[cfg(test)]
pub const fn new_from_wins_losses(wins: u16, losses: u16) -> Self { pub fn new_from_wins_losses(wins: u16, losses: u16) -> Self {
Self { Self {
state: None,
wins, wins,
losses, losses,
value: 0, ..Default::default()
} }
} }
#[cfg(test)] #[cfg(test)]
pub const fn new_from_value(value: i32) -> Self { pub fn new_from_value(value: i32) -> Self {
Self { Self {
state: None,
wins: 0,
losses: 0,
value, value,
..Default::default()
} }
} }
#[cfg(test)] #[cfg(test)]
pub const fn new_from_state(state: Option<MVSGameState>) -> Self { pub fn new_from_state(state: Option<MVSGameState>) -> Self {
Self { Self {
state, state,
wins: 0, ..Default::default()
losses: 0,
value: 0,
} }
} }
fn chance_win(&self) -> Option<f32> { fn chance_win(&self) -> Option<f32> {
let sum = self.losses + self.wins; let sum = self.losses + self.wins + self.ties;
if sum == 0 { if 20 > sum {
return None; return None;
} }
Some(self.wins as f32 / sum as f32) Some(self.wins as f32 / sum as f32)
@@ -70,6 +66,11 @@ impl MoveValueStats {
.iter() .iter()
.filter(|x| x.state == Some(MVSGameState::Loss)) .filter(|x| x.state == Some(MVSGameState::Loss))
.count() as u16; .count() as u16;
self.ties = others.iter().map(|x| x.ties).sum::<u16>()
+ others
.iter()
.filter(|x| x.state == Some(MVSGameState::Tie))
.count() as u16;
} }
} }
@@ -86,11 +87,13 @@ impl Ord for MoveValueStats {
} }
let (s_cw, o_cw) = (self.chance_win(), other.chance_win()); let (s_cw, o_cw) = (self.chance_win(), other.chance_win());
if s_cw.is_some() | o_cw.is_some() {
if s_cw > o_cw { if s_cw > o_cw {
return Ordering::Greater; return Ordering::Greater;
} else if o_cw > s_cw { } else if o_cw > s_cw {
return Ordering::Less; return Ordering::Less;
} }
}
self.value.cmp(&other.value) self.value.cmp(&other.value)
} }
@@ -120,4 +123,11 @@ mod tests {
let two = MoveValueStats::new_from_state(Some(MVSGameState::Win)); let two = MoveValueStats::new_from_state(Some(MVSGameState::Win));
assert!(one < two); assert!(one < two);
} }
#[test]
fn two_prob_zero() {
let one = MoveValueStats::new_from_wins_losses(10, 0);
let two = MoveValueStats::new_from_wins_losses(0, 6);
assert!(one > two);
}
} }