variable simulation time
This commit is contained in:
parent
c2d242453e
commit
23be6f1a18
@ -11,9 +11,14 @@
|
|||||||
// For more info see docs.battlesnake.com
|
// For more info see docs.battlesnake.com
|
||||||
|
|
||||||
use core::f64;
|
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 ordered_float::OrderedFloat;
|
||||||
use serde_json::{json, Value};
|
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
|
// 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");
|
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
|
// 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");
|
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
|
// move is called on every turn and returns your next move
|
||||||
// Valid moves are "up", "down", "left", or "right"
|
// Valid moves are "up", "down", "left", or "right"
|
||||||
// See https://docs.battlesnake.com/api/example-move for available data
|
// 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> {
|
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(
|
let board = simulation::Board::from_game_board(
|
||||||
board,
|
board,
|
||||||
&token_map,
|
&game_info.token_mapping,
|
||||||
turn,
|
turn,
|
||||||
game.ruleset.settings.food_spawn_chance,
|
game.ruleset.settings.food_spawn_chance,
|
||||||
game.ruleset.settings.minimum_food,
|
game.ruleset.settings.minimum_food,
|
||||||
);
|
);
|
||||||
|
|
||||||
let my_token = token_map[&you.id];
|
|
||||||
|
|
||||||
let mut tree = Node::default();
|
let mut tree = Node::default();
|
||||||
|
|
||||||
for _ in 0..300 {
|
while start.elapsed() < game_info.calculation_time.get() {
|
||||||
let mut board = board.clone();
|
let mut board = board.clone();
|
||||||
tree.monte_carlo_step(&mut board);
|
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)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
let chosen = actions
|
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))
|
.max_by_key(|(_, stat)| OrderedFloat(stat.won as f64 / stat.played as f64))
|
||||||
.map(|(direction, _)| *direction)?;
|
.map(|(direction, _)| *direction)?;
|
||||||
|
|
||||||
info!("DIRECTION {}: {:?}", turn, chosen);
|
info!(
|
||||||
|
"DIRECTION {turn}: {chosen:?} after {}ms ({})",
|
||||||
|
start.elapsed().as_millis(),
|
||||||
|
you.name,
|
||||||
|
);
|
||||||
Some(Action {
|
Some(Action {
|
||||||
r#move: chosen,
|
r#move: chosen,
|
||||||
shout: None,
|
shout: None,
|
||||||
@ -186,28 +246,23 @@ impl Node {
|
|||||||
|
|
||||||
let actions = possible_actions
|
let actions = possible_actions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(token, actions)| {
|
.filter_map(|(token, actions)| {
|
||||||
let statistics = self.child_statistics.entry(*token).or_default();
|
let statistics = self.child_statistics.entry(*token).or_default();
|
||||||
let selected = actions
|
let selected = actions.iter().copied().max_by_key(|direction| {
|
||||||
.iter()
|
let statistics = statistics.entry(*direction).or_default();
|
||||||
.copied()
|
if statistics.played == 0 {
|
||||||
.max_by_key(|direction| {
|
return OrderedFloat(f64::INFINITY);
|
||||||
let statistics = statistics.entry(*direction).or_default();
|
}
|
||||||
if statistics.played == 0 {
|
#[allow(clippy::cast_precision_loss)]
|
||||||
return OrderedFloat(f64::INFINITY);
|
let exploitation = statistics.won as f64 / statistics.played as f64;
|
||||||
}
|
#[allow(clippy::cast_precision_loss)]
|
||||||
#[allow(clippy::cast_precision_loss)]
|
let exploration = f64::consts::SQRT_2
|
||||||
let exploitation = statistics.won as f64 / statistics.played as f64;
|
* f64::sqrt(
|
||||||
#[allow(clippy::cast_precision_loss)]
|
f64::ln(self.statistic.played as f64) / statistics.played as f64,
|
||||||
let exploration = f64::consts::SQRT_2
|
);
|
||||||
* f64::sqrt(
|
OrderedFloat(exploitation + exploration)
|
||||||
f64::ln(self.statistic.played as f64)
|
})?;
|
||||||
/ statistics.played as f64,
|
Some((*token, selected))
|
||||||
);
|
|
||||||
OrderedFloat(exploitation + exploration)
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
(*token, selected)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -19,23 +19,11 @@ const MAX_HEALTH: i32 = 100;
|
|||||||
// See https://docs.battlesnake.com/api
|
// See https://docs.battlesnake.com/api
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize, Serialize, Sequence,
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
PartialOrd,
|
|
||||||
Ord,
|
|
||||||
Hash,
|
|
||||||
Clone,
|
|
||||||
Copy,
|
|
||||||
Deserialize,
|
|
||||||
Serialize,
|
|
||||||
Sequence,
|
|
||||||
Default,
|
|
||||||
)]
|
)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
/// Move left (-x)
|
/// Move left (-x)
|
||||||
#[default]
|
|
||||||
Left,
|
Left,
|
||||||
/// Move up (+y)
|
/// Move up (+y)
|
||||||
Up,
|
Up,
|
||||||
|
@ -92,7 +92,7 @@ impl Board {
|
|||||||
pub fn simulate_actions(&mut self, actions: &BTreeMap<SnakeToken, Direction>) {
|
pub fn simulate_actions(&mut self, actions: &BTreeMap<SnakeToken, Direction>) {
|
||||||
// move snakes
|
// move snakes
|
||||||
for (token, snake) in &mut self.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
|
// feed snakes
|
||||||
@ -170,7 +170,7 @@ impl Board {
|
|||||||
.iter()
|
.iter()
|
||||||
.choose(&mut rand::thread_rng())
|
.choose(&mut rand::thread_rng())
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default(),
|
.unwrap_or(Direction::Up),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
Loading…
Reference in New Issue
Block a user