use faster rng
All checks were successful
Build / build (push) Successful in 1m54s

This commit is contained in:
Max Känner 2025-04-25 19:55:42 +02:00
parent 15d90357ec
commit e5600fe038
3 changed files with 46 additions and 42 deletions

View File

@ -1,15 +1,16 @@
use std::sync::{ use std::sync::{
atomic::{AtomicU32, Ordering},
Arc, 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::{ use battlesnake::types::{
Coord,
simulation::Board, simulation::Board,
wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings}, wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings},
Coord,
}; };
use rand::{SeedableRng, rngs::SmallRng};
fn create_start_snake(coord: Coord) -> Battlesnake { fn create_start_snake(coord: Coord) -> Battlesnake {
let id: Arc<str> = format!("{coord:?}").into(); let id: Arc<str> = format!("{coord:?}").into();
@ -74,7 +75,7 @@ fn standard(c: &mut Criterion) {
let benchmark = |b: &mut Bencher, board: &Board| { let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| { b.iter(|| {
let mut board = board.clone(); 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 { if board.num_snakes() <= 1 {
Some(board.turn()) Some(board.turn())
} else { } else {
@ -151,7 +152,7 @@ fn constrictor(c: &mut Criterion) {
let benchmark = |b: &mut Bencher, board: &Board| { let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| { b.iter(|| {
let mut board = board.clone(); 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 { if board.num_snakes() <= 1 {
Some(board.turn()) Some(board.turn())
} else { } else {

View File

@ -13,7 +13,7 @@ use battlesnake::types::{
}; };
use float_ord::FloatOrd; use float_ord::FloatOrd;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use rand::{prelude::*, rng}; use rand::prelude::*;
use serde::Serialize; use serde::Serialize;
use tokio::{ use tokio::{
net::TcpListener, net::TcpListener,
@ -89,6 +89,7 @@ async fn get_move(request: Json<Request>) -> response::Json<Response> {
info!("valid actions: {actions:?}"); info!("valid actions: {actions:?}");
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut rng = SmallRng::from_os_rng();
if start.elapsed() > Duration::from_millis(10) { if start.elapsed() > Duration::from_millis(10) {
error!( error!(
"The calculation started late ({}ms)", "The calculation started late ({}ms)",
@ -129,23 +130,22 @@ async fn get_move(request: Json<Request>) -> response::Json<Response> {
.map(|id| MctsManager::new(u8::try_from(id).unwrap())) .map(|id| MctsManager::new(u8::try_from(id).unwrap()))
.collect(); .collect();
let c = f32::sqrt(2.0); let c = f32::sqrt(2.0);
let mut mcts_actions = Vec::new();
while start.elapsed() < timeout * 4 / 5 { while start.elapsed() < timeout * 4 / 5 {
let mut board = board.clone(); let mut board = board.clone();
while end_condition(&board).is_none() { while end_condition(&board).is_none() {
let actions: Vec<_> = mcts_managers mcts_actions.clear();
.iter_mut() mcts_actions.extend(mcts_managers.iter_mut().filter_map(|mcts_manager| {
.filter_map(|mcts_manager| { mcts_manager
mcts_manager .next_action(&board, c, &mut rng)
.next_action(&board, c) .map(|action| (mcts_manager.snake, action))
.map(|action| (mcts_manager.snake, action)) }));
}) board.next_turn(&mcts_actions, &mut rng);
.collect(); if mcts_actions.is_empty() {
board.next_turn(&actions);
if actions.is_empty() {
break; break;
} }
} }
board.simulate_random(end_condition); board.simulate_random(&mut rng, end_condition);
for mcts_manager in &mut mcts_managers { for mcts_manager in &mut mcts_managers {
let id = mcts_manager.snake; let id = mcts_manager.snake;
let score = score_fn(&board, id); let score = score_fn(&board, id);
@ -273,7 +273,7 @@ impl MctsManager {
self.expanded = false; self.expanded = false;
} }
fn next_action(&mut self, board: &Board, c: f32) -> Option<Direction> { fn next_action(&mut self, board: &Board, c: f32, rng: &mut impl RngCore) -> Option<Direction> {
if self.expanded { if self.expanded {
return None; return None;
} }
@ -294,11 +294,7 @@ impl MctsManager {
.collect(); .collect();
trace!("got actions: {ucts:?}"); trace!("got actions: {ucts:?}");
if ucts.iter().any(|(_, uct)| uct.is_none()) { if ucts.iter().any(|(_, uct)| uct.is_none()) {
let action = ucts let action = ucts.iter().filter(|(_, uct)| uct.is_none()).choose(rng)?.0;
.iter()
.filter(|(_, uct)| uct.is_none())
.choose(&mut rng())?
.0;
self.expanded = true; self.expanded = true;
current.next[usize::from(action)].replace(ActionInfo::new()); current.next[usize::from(action)].replace(ActionInfo::new());
self.actions.push(action); self.actions.push(action);

View File

@ -8,7 +8,7 @@ use std::{
use bitvec::prelude::*; use bitvec::prelude::*;
use log::{error, warn}; use log::{error, warn};
use rand::{prelude::*, rng}; use rand::prelude::*;
use super::{Coord, Direction, wire::Request}; use super::{Coord, Direction, wire::Request};
@ -256,43 +256,50 @@ impl Board {
} }
#[must_use] #[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 { let Some(index) = self.id_to_index(id) else {
return Direction::Up; return Direction::Up;
}; };
self.valid_actions_index(index) self.valid_actions_index(index)
.choose(&mut rng()) .choose(rng)
.unwrap_or(Direction::Up) .unwrap_or(Direction::Up)
} }
pub fn random_actions(&self) -> impl Iterator<Item = (u8, Direction)> + use<'_> { pub fn random_actions<'a, R: RngCore>(
&self,
rng: &'a mut R,
) -> impl Iterator<Item = (u8, Direction)> + use<'_, 'a, R> {
(0..self.snakes.len()).map(|index| { (0..self.snakes.len()).map(|index| {
( (
self.snakes[index].id, self.snakes[index].id,
self.valid_actions_index(index) self.valid_actions_index(index)
.choose(&mut rng()) .choose(rng)
.unwrap_or(Direction::Up), .unwrap_or(Direction::Up),
) )
}) })
} }
pub fn simulate_random<T>(&mut self, stop: impl Fn(&Self) -> Option<T>) -> T { pub fn simulate_random<T>(
&mut self,
rng: &mut impl RngCore,
stop: impl Fn(&Self) -> Option<T>,
) -> T {
loop { loop {
if let Some(score) = stop(self) { if let Some(score) = stop(self) {
break score; break score;
} }
self.next_turn(&[]); self.next_turn(&[], rng);
} }
} }
pub fn next_turn(&mut self, actions: &[(u8, Direction)]) { pub fn next_turn(&mut self, actions: &[(u8, Direction)], rng: &mut impl RngCore) {
self.move_standard(actions); self.move_standard(actions, rng);
self.starvation_standard(); self.starvation_standard();
self.hazard_damage_standard(); self.hazard_damage_standard();
self.feed_snakes_standard(); self.feed_snakes_standard();
self.eliminate_snake_standard(); self.eliminate_snake_standard();
self.update_free_map(); self.update_free_map();
self.spawn_food(); self.spawn_food(rng);
self.turn += 1; self.turn += 1;
} }
@ -306,13 +313,13 @@ impl Board {
.filter(move |direction| self.is_free(head.wrapping_apply(*direction))) .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() { for i in 0..self.snakes.len() {
let snake = &self.snakes[i]; let snake = &self.snakes[i];
let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else( let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else(
|| { || {
self.valid_actions_index(i) self.valid_actions_index(i)
.choose(&mut rng()) .choose(rng)
.unwrap_or(Direction::Up) .unwrap_or(Direction::Up)
}, },
|(_, action)| *action, |(_, action)| *action,
@ -445,15 +452,15 @@ impl Board {
} }
} }
fn spawn_food(&mut self) { fn spawn_food(&mut self, rng: &mut impl RngCore) {
let food_needed = self.check_food_needing_placement(); let food_needed = self.check_food_needing_placement(rng);
if food_needed > 0 { 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 min_food = self.min_food;
let food_spawn_chance = self.food_spawn_chance; let food_spawn_chance = self.food_spawn_chance;
let num_current_food = u16::try_from(self.food.count_ones()).unwrap_or(u16::MAX); 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 { if num_current_food < min_food {
return min_food - num_current_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; return 1;
} }
0 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 let tails: Vec<_> = self
.snakes .snakes
.iter() .iter()
@ -495,7 +502,7 @@ impl Board {
.filter(|i| !tails.contains(i)) .filter(|i| !tails.contains(i))
.filter(|i| !possible_moves.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); self.food.set(food_spot, true);
} }
} }