elo: add live leaderboard

This commit is contained in:
Simon Gardling 2025-03-05 10:25:38 -05:00
parent 222afb29c1
commit 875c1737e5
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
3 changed files with 60 additions and 16 deletions

10
Cargo.lock generated
View File

@ -172,6 +172,15 @@ dependencies = [
"itertools", "itertools",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.6"
@ -415,6 +424,7 @@ dependencies = [
"arrayvec", "arrayvec",
"const_fn", "const_fn",
"criterion", "criterion",
"crossbeam-channel",
"either", "either",
"indicatif", "indicatif",
"nohash-hasher", "nohash-hasher",

View File

@ -24,6 +24,7 @@ debug = true
[dependencies] [dependencies]
arrayvec = "0.7" arrayvec = "0.7"
const_fn = "0.4" const_fn = "0.4"
crossbeam-channel = "0.5.14"
either = "1.13" either = "1.13"
indicatif = { version = "0.17", features = [ "rayon" ] } indicatif = { version = "0.17", features = [ "rayon" ] }
nohash-hasher = "0.2" nohash-hasher = "0.2"

View File

@ -118,6 +118,7 @@ impl PlayerArena {
players: players players: players
.into_iter() .into_iter()
.zip([EloRating::new()].into_iter().cycle()) .zip([EloRating::new()].into_iter().cycle())
// flatten tuple
.map(|((a, b), c)| (a, b, c)) .map(|((a, b), c)| (a, b, c))
.collect(), .collect(),
} }
@ -137,21 +138,53 @@ impl PlayerArena {
// shuffle for consistency // shuffle for consistency
created_pairs.shuffle(&mut rand::rng()); created_pairs.shuffle(&mut rand::rng());
// after the agents are created, we can multithread the games being played let num = created_pairs.len();
created_pairs
.into_par_iter() let (sender, receiver) = crossbeam_channel::unbounded();
.progress_with_style(
ProgressStyle::with_template("[{elapsed_precise}] {pos:>7}/{len:7} ETA: {eta}") // Spawn parallel processing in a dedicated thread
.expect("invalid ProgressStyle"), let processing_thread = {
) let sender = sender.clone();
.map(|((i, j), (p1, p2))| (i, j, Self::play_two_inner(p1, p2))) std::thread::spawn(move || {
// TODO! is there some way in rayon to allow `self.process_outcome` to be run on the main thread created_pairs
// as soon as Self::play_two_inner completes? This would allow maybe a live leaderboard to be displayed .into_par_iter()
// while players are playing .progress_with_style(
.collect::<Vec<_>>() ProgressStyle::with_template(
// collect and process the outcomes of all the games "[{elapsed_precise}] {pos:>7}/{len:7} ETA: {eta}",
.into_iter() )
.for_each(|(i, j, o)| self.process_outcome(i, j, &o)); .expect("invalid ProgressStyle"),
)
.map(|((i, j), (p1, p2))| (i, j, Self::play_two_inner(p1, p2)))
.for_each(|(i, j, o)| {
sender.send((i, j, o)).expect("Failed to send result");
});
})
};
// Immediately drop our copy of the sender so the channel closes properly
drop(sender);
// Process results on main thread as they arrive
let mut received_num = 0;
while let Ok((i, j, o)) = receiver.recv() {
self.process_outcome(i, j, &o);
received_num += 1;
// print the leaderboard every 5 steps
if received_num % 5 == 0 {
println!("{}", self);
}
// break if all pairs were recieved
if received_num == num {
break;
}
}
// Ensure parallel thread completes
processing_thread
.join()
.expect("Processing thread panicked");
} }
fn prop_arena(&mut self, n: usize) { fn prop_arena(&mut self, n: usize) {
@ -191,7 +224,7 @@ impl PlayerArena {
player_1, player_1,
player_2, player_2,
false, false,
Board::random(rand::random_range(3..8)), Board::random(rand::random_range(3..=7)),
) )
.loop_until_result(); .loop_until_result();