variable simulation time
This commit is contained in:
parent
c2d242453e
commit
23be6f1a18
@ -11,9 +11,14 @@
|
||||
// For more info see docs.battlesnake.com
|
||||
|
||||
use core::f64;
|
||||
use std::collections::BTreeMap;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
@ -101,41 +106,92 @@ pub fn info() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct GameInfo {
|
||||
calculation_time: Cell<Duration>,
|
||||
token_mapping: Arc<BTreeMap<String, SnakeToken>>,
|
||||
my_token: SnakeToken,
|
||||
}
|
||||
|
||||
static GAME_INFOS: LazyLock<Mutex<HashMap<(String, String), GameInfo>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
// start is called when your Battlesnake begins a game
|
||||
pub fn start(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) {
|
||||
pub fn start(game: &Game, _turn: i32, board: &Board, you: &Battlesnake) {
|
||||
info!("GAME START");
|
||||
let token_mapping = Arc::new(SnakeToken::from_board(board));
|
||||
let my_token = token_mapping[&you.id];
|
||||
let Ok(mut game_infos) = GAME_INFOS.lock() else {
|
||||
error!("unable to lock game infos");
|
||||
return;
|
||||
};
|
||||
game_infos.insert(
|
||||
(game.id.clone(), you.id.clone()),
|
||||
GameInfo {
|
||||
calculation_time: Cell::new(Duration::from_millis(50)),
|
||||
token_mapping,
|
||||
my_token,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// end is called when your Battlesnake finishes a game
|
||||
pub fn end(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) {
|
||||
pub fn end(game: &Game, _turn: i32, _board: &Board, you: &Battlesnake) {
|
||||
info!("GAME OVER");
|
||||
let Ok(mut game_infos) = GAME_INFOS.lock() else {
|
||||
error!("unable to lock game infos");
|
||||
return;
|
||||
};
|
||||
game_infos.remove(&(game.id.clone(), you.id.clone()));
|
||||
}
|
||||
|
||||
// move is called on every turn and returns your next move
|
||||
// Valid moves are "up", "down", "left", or "right"
|
||||
// See https://docs.battlesnake.com/api/example-move for available data
|
||||
pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Option<Action> {
|
||||
let token_map = SnakeToken::from_board(board);
|
||||
let start = Instant::now();
|
||||
let game_info = GAME_INFOS
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|guard| guard.get(&(game.id.clone(), you.id.clone())).cloned())
|
||||
.unwrap_or_else(|| {
|
||||
let token_mapping = Arc::new(SnakeToken::from_board(board));
|
||||
let my_token = token_mapping[&you.id];
|
||||
GameInfo {
|
||||
calculation_time: Cell::new(Duration::from_millis(50)),
|
||||
token_mapping,
|
||||
my_token,
|
||||
}
|
||||
});
|
||||
|
||||
// do some latency compensation
|
||||
game_info.calculation_time.set(
|
||||
game_info.calculation_time.get()
|
||||
+ Duration::from_millis(
|
||||
u64::from(
|
||||
game.timeout * 3 / 4 - you.latency.parse().unwrap_or(game.timeout * 3 / 4),
|
||||
) / 3,
|
||||
),
|
||||
);
|
||||
|
||||
let board = simulation::Board::from_game_board(
|
||||
board,
|
||||
&token_map,
|
||||
&game_info.token_mapping,
|
||||
turn,
|
||||
game.ruleset.settings.food_spawn_chance,
|
||||
game.ruleset.settings.minimum_food,
|
||||
);
|
||||
|
||||
let my_token = token_map[&you.id];
|
||||
|
||||
let mut tree = Node::default();
|
||||
|
||||
for _ in 0..300 {
|
||||
while start.elapsed() < game_info.calculation_time.get() {
|
||||
let mut board = board.clone();
|
||||
tree.monte_carlo_step(&mut board);
|
||||
}
|
||||
|
||||
let actions = tree.child_statistics.entry(my_token).or_default();
|
||||
let actions = tree.child_statistics.entry(game_info.my_token).or_default();
|
||||
|
||||
info!("actions: {actions:?}");
|
||||
info!("actions {}: {actions:?}", you.name);
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let chosen = actions
|
||||
@ -143,7 +199,11 @@ pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Opt
|
||||
.max_by_key(|(_, stat)| OrderedFloat(stat.won as f64 / stat.played as f64))
|
||||
.map(|(direction, _)| *direction)?;
|
||||
|
||||
info!("DIRECTION {}: {:?}", turn, chosen);
|
||||
info!(
|
||||
"DIRECTION {turn}: {chosen:?} after {}ms ({})",
|
||||
start.elapsed().as_millis(),
|
||||
you.name,
|
||||
);
|
||||
Some(Action {
|
||||
r#move: chosen,
|
||||
shout: None,
|
||||
@ -186,28 +246,23 @@ impl Node {
|
||||
|
||||
let actions = possible_actions
|
||||
.iter()
|
||||
.map(|(token, actions)| {
|
||||
.filter_map(|(token, actions)| {
|
||||
let statistics = self.child_statistics.entry(*token).or_default();
|
||||
let selected = actions
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by_key(|direction| {
|
||||
let statistics = statistics.entry(*direction).or_default();
|
||||
if statistics.played == 0 {
|
||||
return OrderedFloat(f64::INFINITY);
|
||||
}
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let exploitation = statistics.won as f64 / statistics.played as f64;
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let exploration = f64::consts::SQRT_2
|
||||
* f64::sqrt(
|
||||
f64::ln(self.statistic.played as f64)
|
||||
/ statistics.played as f64,
|
||||
);
|
||||
OrderedFloat(exploitation + exploration)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
(*token, selected)
|
||||
let selected = actions.iter().copied().max_by_key(|direction| {
|
||||
let statistics = statistics.entry(*direction).or_default();
|
||||
if statistics.played == 0 {
|
||||
return OrderedFloat(f64::INFINITY);
|
||||
}
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let exploitation = statistics.won as f64 / statistics.played as f64;
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let exploration = f64::consts::SQRT_2
|
||||
* f64::sqrt(
|
||||
f64::ln(self.statistic.played as f64) / statistics.played as f64,
|
||||
);
|
||||
OrderedFloat(exploitation + exploration)
|
||||
})?;
|
||||
Some((*token, selected))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -19,23 +19,11 @@ const MAX_HEALTH: i32 = 100;
|
||||
// See https://docs.battlesnake.com/api
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Clone,
|
||||
Copy,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Sequence,
|
||||
Default,
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize, Serialize, Sequence,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Direction {
|
||||
/// Move left (-x)
|
||||
#[default]
|
||||
Left,
|
||||
/// Move up (+y)
|
||||
Up,
|
||||
|
@ -92,7 +92,7 @@ impl Board {
|
||||
pub fn simulate_actions(&mut self, actions: &BTreeMap<SnakeToken, Direction>) {
|
||||
// move snakes
|
||||
for (token, snake) in &mut self.snakes {
|
||||
snake.perform_action(actions.get(token).copied().unwrap_or_default());
|
||||
snake.perform_action(actions.get(token).copied().unwrap_or(Direction::Up));
|
||||
}
|
||||
|
||||
// feed snakes
|
||||
@ -170,7 +170,7 @@ impl Board {
|
||||
.iter()
|
||||
.choose(&mut rand::thread_rng())
|
||||
.copied()
|
||||
.unwrap_or_default(),
|
||||
.unwrap_or(Direction::Up),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
Loading…
Reference in New Issue
Block a user