solo mode (not verry good)
This commit is contained in:
parent
23be6f1a18
commit
6123ca177e
@ -186,7 +186,11 @@ pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Opt
|
|||||||
|
|
||||||
while start.elapsed() < game_info.calculation_time.get() {
|
while start.elapsed() < game_info.calculation_time.get() {
|
||||||
let mut board = board.clone();
|
let mut board = board.clone();
|
||||||
tree.monte_carlo_step(&mut board);
|
if game.ruleset.name == "solo" {
|
||||||
|
tree.monte_carlo_step_solo(&mut board);
|
||||||
|
} else {
|
||||||
|
tree.monte_carlo_step(&mut board);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let actions = tree.child_statistics.entry(game_info.my_token).or_default();
|
let actions = tree.child_statistics.entry(game_info.my_token).or_default();
|
||||||
@ -299,4 +303,88 @@ impl Node {
|
|||||||
}
|
}
|
||||||
winner
|
winner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs one monte carlo simulation step for a solo game
|
||||||
|
///
|
||||||
|
/// Returns the lengths before death
|
||||||
|
fn monte_carlo_step_solo(
|
||||||
|
&mut self,
|
||||||
|
board: &mut simulation::Board,
|
||||||
|
) -> BTreeMap<SnakeToken, usize> {
|
||||||
|
let lengths = if self.statistic.played == 0 {
|
||||||
|
// We didn't simulate a game for this node yet. Do that
|
||||||
|
let mut lengths: BTreeMap<_, _> = board
|
||||||
|
.snakes()
|
||||||
|
.filter_map(|snake| Some((snake, board.snake_length(snake)?)))
|
||||||
|
.collect();
|
||||||
|
board.simulate_until(|board| {
|
||||||
|
for snake in board.snakes() {
|
||||||
|
if let Some(length) = board.snake_length(snake) {
|
||||||
|
lengths.insert(snake, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
board.alive_snakes() == 0
|
||||||
|
});
|
||||||
|
lengths
|
||||||
|
} else {
|
||||||
|
// select a node to simulate
|
||||||
|
let possible_actions = board.possible_actions();
|
||||||
|
|
||||||
|
let actions = possible_actions
|
||||||
|
.iter()
|
||||||
|
.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
|
||||||
|
/ board.spaces() 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();
|
||||||
|
|
||||||
|
board.simulate_actions(&actions);
|
||||||
|
let lengths = self
|
||||||
|
.childs
|
||||||
|
.entry(actions.clone())
|
||||||
|
.or_default()
|
||||||
|
.monte_carlo_step_solo(board);
|
||||||
|
|
||||||
|
// update child statistics
|
||||||
|
for (token, action) in &actions {
|
||||||
|
let entry = self
|
||||||
|
.child_statistics
|
||||||
|
.entry(*token)
|
||||||
|
.or_default()
|
||||||
|
.entry(*action)
|
||||||
|
.or_default();
|
||||||
|
entry.played += 1;
|
||||||
|
if let Some(length) = lengths.get(token) {
|
||||||
|
entry.won += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lengths
|
||||||
|
};
|
||||||
|
self.statistic.played += 1;
|
||||||
|
for (token, length) in &lengths {
|
||||||
|
self.statistic
|
||||||
|
.won
|
||||||
|
.entry(*token)
|
||||||
|
.and_modify(|won| *won += length)
|
||||||
|
.or_insert(*length);
|
||||||
|
}
|
||||||
|
lengths
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,11 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
pub const fn spaces(&self) -> usize {
|
||||||
|
self.height as usize * self.width as usize
|
||||||
|
}
|
||||||
|
|
||||||
pub fn alive_snakes(&self) -> usize {
|
pub fn alive_snakes(&self) -> usize {
|
||||||
self.snakes.len()
|
self.snakes.len()
|
||||||
}
|
}
|
||||||
@ -89,6 +94,10 @@ impl Board {
|
|||||||
self.snakes.keys().copied()
|
self.snakes.keys().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn snake_length(&self, token: SnakeToken) -> Option<usize> {
|
||||||
|
self.snakes.get(&token).map(|snake| snake.body.len())
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -158,7 +167,7 @@ impl Board {
|
|||||||
self.turn += 1;
|
self.turn += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate_until(&mut self, exit: impl Fn(&Self) -> bool) {
|
pub fn simulate_until(&mut self, mut exit: impl FnMut(&Self) -> bool) {
|
||||||
while !exit(self) {
|
while !exit(self) {
|
||||||
let actions = self
|
let actions = self
|
||||||
.possible_actions()
|
.possible_actions()
|
||||||
|
Loading…
Reference in New Issue
Block a user