Compare commits
5 Commits
3ad74c90b7
...
189a5aef58
| Author | SHA1 | Date | |
|---|---|---|---|
|
189a5aef58
|
|||
|
c54f3a7493
|
|||
|
c3c07fcb28
|
|||
|
2586b43294
|
|||
|
14254e44ba
|
38
src/elo.rs
38
src/elo.rs
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user