From e5600fe038fd55561952441bce8d345d92f0cd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=A4nner?= Date: Fri, 25 Apr 2025 19:55:42 +0200 Subject: [PATCH] use faster rng --- battlesnake/benches/simulation.rs | 11 +++---- battlesnake/src/main.rs | 32 +++++++++----------- battlesnake/src/types/simulation.rs | 45 +++++++++++++++++------------ 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/battlesnake/benches/simulation.rs b/battlesnake/benches/simulation.rs index 1b5c2d4..abbe015 100644 --- a/battlesnake/benches/simulation.rs +++ b/battlesnake/benches/simulation.rs @@ -1,15 +1,16 @@ use std::sync::{ - atomic::{AtomicU32, Ordering}, Arc, + atomic::{AtomicU32, Ordering}, }; -use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; +use criterion::{Bencher, BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; use battlesnake::types::{ + Coord, simulation::Board, wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings}, - Coord, }; +use rand::{SeedableRng, rngs::SmallRng}; fn create_start_snake(coord: Coord) -> Battlesnake { let id: Arc = format!("{coord:?}").into(); @@ -74,7 +75,7 @@ fn standard(c: &mut Criterion) { let benchmark = |b: &mut Bencher, board: &Board| { b.iter(|| { let mut board = board.clone(); - let turn = board.simulate_random(|board| { + let turn = board.simulate_random(&mut SmallRng::from_os_rng(), |board| { if board.num_snakes() <= 1 { Some(board.turn()) } else { @@ -151,7 +152,7 @@ fn constrictor(c: &mut Criterion) { let benchmark = |b: &mut Bencher, board: &Board| { b.iter(|| { let mut board = board.clone(); - let turn = board.simulate_random(|board| { + let turn = board.simulate_random(&mut SmallRng::from_os_rng(), |board| { if board.num_snakes() <= 1 { Some(board.turn()) } else { diff --git a/battlesnake/src/main.rs b/battlesnake/src/main.rs index 6a1eb53..96c41c3 100644 --- a/battlesnake/src/main.rs +++ b/battlesnake/src/main.rs @@ -13,7 +13,7 @@ use battlesnake::types::{ }; use float_ord::FloatOrd; use log::{debug, error, info, trace, warn}; -use rand::{prelude::*, rng}; +use rand::prelude::*; use serde::Serialize; use tokio::{ net::TcpListener, @@ -89,6 +89,7 @@ async fn get_move(request: Json) -> response::Json { info!("valid actions: {actions:?}"); tokio::task::spawn_blocking(move || { + let mut rng = SmallRng::from_os_rng(); if start.elapsed() > Duration::from_millis(10) { error!( "The calculation started late ({}ms)", @@ -129,23 +130,22 @@ async fn get_move(request: Json) -> response::Json { .map(|id| MctsManager::new(u8::try_from(id).unwrap())) .collect(); let c = f32::sqrt(2.0); + let mut mcts_actions = Vec::new(); while start.elapsed() < timeout * 4 / 5 { let mut board = board.clone(); while end_condition(&board).is_none() { - let actions: Vec<_> = mcts_managers - .iter_mut() - .filter_map(|mcts_manager| { - mcts_manager - .next_action(&board, c) - .map(|action| (mcts_manager.snake, action)) - }) - .collect(); - board.next_turn(&actions); - if actions.is_empty() { + mcts_actions.clear(); + mcts_actions.extend(mcts_managers.iter_mut().filter_map(|mcts_manager| { + mcts_manager + .next_action(&board, c, &mut rng) + .map(|action| (mcts_manager.snake, action)) + })); + board.next_turn(&mcts_actions, &mut rng); + if mcts_actions.is_empty() { break; } } - board.simulate_random(end_condition); + board.simulate_random(&mut rng, end_condition); for mcts_manager in &mut mcts_managers { let id = mcts_manager.snake; let score = score_fn(&board, id); @@ -273,7 +273,7 @@ impl MctsManager { self.expanded = false; } - fn next_action(&mut self, board: &Board, c: f32) -> Option { + fn next_action(&mut self, board: &Board, c: f32, rng: &mut impl RngCore) -> Option { if self.expanded { return None; } @@ -294,11 +294,7 @@ impl MctsManager { .collect(); trace!("got actions: {ucts:?}"); if ucts.iter().any(|(_, uct)| uct.is_none()) { - let action = ucts - .iter() - .filter(|(_, uct)| uct.is_none()) - .choose(&mut rng())? - .0; + let action = ucts.iter().filter(|(_, uct)| uct.is_none()).choose(rng)?.0; self.expanded = true; current.next[usize::from(action)].replace(ActionInfo::new()); self.actions.push(action); diff --git a/battlesnake/src/types/simulation.rs b/battlesnake/src/types/simulation.rs index 3cb11c4..d1b1eed 100644 --- a/battlesnake/src/types/simulation.rs +++ b/battlesnake/src/types/simulation.rs @@ -8,7 +8,7 @@ use std::{ use bitvec::prelude::*; use log::{error, warn}; -use rand::{prelude::*, rng}; +use rand::prelude::*; use super::{Coord, Direction, wire::Request}; @@ -256,43 +256,50 @@ impl Board { } #[must_use] - pub fn random_action(&self, id: u8) -> Direction { + pub fn random_action(&self, id: u8, rng: &mut impl RngCore) -> Direction { let Some(index) = self.id_to_index(id) else { return Direction::Up; }; self.valid_actions_index(index) - .choose(&mut rng()) + .choose(rng) .unwrap_or(Direction::Up) } - pub fn random_actions(&self) -> impl Iterator + use<'_> { + pub fn random_actions<'a, R: RngCore>( + &self, + rng: &'a mut R, + ) -> impl Iterator + use<'_, 'a, R> { (0..self.snakes.len()).map(|index| { ( self.snakes[index].id, self.valid_actions_index(index) - .choose(&mut rng()) + .choose(rng) .unwrap_or(Direction::Up), ) }) } - pub fn simulate_random(&mut self, stop: impl Fn(&Self) -> Option) -> T { + pub fn simulate_random( + &mut self, + rng: &mut impl RngCore, + stop: impl Fn(&Self) -> Option, + ) -> T { loop { if let Some(score) = stop(self) { break score; } - self.next_turn(&[]); + self.next_turn(&[], rng); } } - pub fn next_turn(&mut self, actions: &[(u8, Direction)]) { - self.move_standard(actions); + pub fn next_turn(&mut self, actions: &[(u8, Direction)], rng: &mut impl RngCore) { + self.move_standard(actions, rng); self.starvation_standard(); self.hazard_damage_standard(); self.feed_snakes_standard(); self.eliminate_snake_standard(); self.update_free_map(); - self.spawn_food(); + self.spawn_food(rng); self.turn += 1; } @@ -306,13 +313,13 @@ impl Board { .filter(move |direction| self.is_free(head.wrapping_apply(*direction))) } - fn move_standard(&mut self, actions: &[(u8, Direction)]) { + fn move_standard(&mut self, actions: &[(u8, Direction)], rng: &mut impl RngCore) { for i in 0..self.snakes.len() { let snake = &self.snakes[i]; let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else( || { self.valid_actions_index(i) - .choose(&mut rng()) + .choose(rng) .unwrap_or(Direction::Up) }, |(_, action)| *action, @@ -445,15 +452,15 @@ impl Board { } } - fn spawn_food(&mut self) { - let food_needed = self.check_food_needing_placement(); + fn spawn_food(&mut self, rng: &mut impl RngCore) { + let food_needed = self.check_food_needing_placement(rng); if food_needed > 0 { - self.place_food_randomly(food_needed); + self.place_food_randomly(food_needed, rng); } } - fn check_food_needing_placement(&self) -> u16 { + fn check_food_needing_placement(&self, rng: &mut impl RngCore) -> u16 { let min_food = self.min_food; let food_spawn_chance = self.food_spawn_chance; let num_current_food = u16::try_from(self.food.count_ones()).unwrap_or(u16::MAX); @@ -461,14 +468,14 @@ impl Board { if num_current_food < min_food { return min_food - num_current_food; } - if food_spawn_chance > 0 && (100 - rng().random_range(0..100)) < food_spawn_chance { + if food_spawn_chance > 0 && (100 - rng.random_range(0..100)) < food_spawn_chance { return 1; } 0 } - fn place_food_randomly(&mut self, amount: u16) { + fn place_food_randomly(&mut self, amount: u16, rng: &mut impl RngCore) { let tails: Vec<_> = self .snakes .iter() @@ -495,7 +502,7 @@ impl Board { .filter(|i| !tails.contains(i)) .filter(|i| !possible_moves.contains(i)); - for food_spot in unoccupied_points.choose_multiple(&mut rng(), usize::from(amount)) { + for food_spot in unoccupied_points.choose_multiple(rng, usize::from(amount)) { self.food.set(food_spot, true); } }