-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplanner.rs
143 lines (134 loc) · 4.88 KB
/
planner.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::thread;
use std::time::{Duration, Instant};
use crate::game_message::{Cannon, Constants, Id, Tick};
use crate::game_random::GameRandom;
use crate::game_search_state::{Action, GameSearchState};
use crate::mcts::{MCTS, MCTSOptions};
use crate::search::SearchState;
use crate::simulate::{run_server_tick, EventInfo, GameState};
const MCTS_OPTIONS: MCTSOptions = MCTSOptions {
exploration_multiplier: 1.0,
random_action_prob: 0.05,
uncertainty_d: 10.0,
print_every_n_rounds: Some(800),
reset_after_n_nodes: Some(500000),
};
const MCTS_BUDGET: Duration = Duration::from_millis(750);
// Maximum number of threads to spawn. If unset, defaults to num available
// *physical* cores - 1.
// Note that the current MCTS tree is copied this amount of times, so pick
// thresholds accordingly to remain under the desired RAM budget.
const MAX_THREADS: Option<usize> = None;
/// Events at the time where the client would see them (i.e. the tick after
/// they happened). Note that we move forward the meteors in EventInfos by one
/// tick to match what the client would see in the GameMessage JSON (post tick
/// update).
#[derive(Clone, Copy, Debug)]
pub struct Event {
pub tick: Tick,
pub info: EventInfo
}
pub struct Planner<'a> {
pub game_state: GameState,
pub search_state: GameSearchState<'a>,
cannon: &'a Cannon,
constants: &'a Constants,
random: GameRandom,
mcts: MCTS<GameSearchState<'a>>,
}
impl<'a> Planner<'a> {
pub fn new(first_id: Id, cannon: &'a Cannon, constants: &'a Constants,
random: GameRandom) -> Self {
let game_state = GameState::new(first_id);
let search_state = GameSearchState::new(
game_state.clone(), &constants, &cannon,
random.clone());
let mcts = MCTS::new(search_state.clone(), MCTS_OPTIONS);
Self { game_state, search_state, cannon, constants, random, mcts }
}
pub fn is_done(&self) -> bool {
self.game_state.is_done()
}
pub fn next_action(&mut self) -> Vec<Event> {
let mut events = Vec::new();
let mut best_seen_score = self.mcts.best_seen_score;
self.mcts = self.run_mcts();
if self.mcts.best_seen_score > best_seen_score {
best_seen_score = self.mcts.best_seen_score;
println!("New best: {}", best_seen_score);
}
for event in run_server_tick(
&mut self.game_state, &mut self.random, &self.constants) {
events.push(Event {
tick: self.game_state.tick,
info: post_update_event_info(event)
});
}
if self.game_state.is_done() {
return events;
}
let best_action = self.mcts.pick_best_action().expect("No action returned");
self.search_state.apply_action(&best_action);
match best_action {
Action::Hold { .. } => {},
Action::Shoot { aim, target_id, .. } => {
events.push(Event {
info: self.game_state.shoot(
&self.cannon, &self.constants, &aim, target_id).unwrap(),
tick: self.game_state.tick });
},
}
events
}
fn run_mcts(&self) -> MCTS<GameSearchState<'a>> {
let parallelism = match MAX_THREADS {
Some(threads) => threads,
None => {
let num_cores = num_cpus::get_physical();
assert!(num_cores > 0);
num_cores - 1
},
};
thread::scope(|s| {
let mut handles = vec![];
let start = Instant::now();
for _ in 0..parallelism {
let mut mcts = self.mcts.clone();
handles.push(s.spawn(move || {
mcts.run_with_budget(&start, MCTS_BUDGET);
mcts
}));
}
let mut best_mcts: Option<MCTS<GameSearchState>> = None;
for h in handles {
let mcts = h.join().unwrap();
if best_mcts.as_ref()
.map_or(true, |m| mcts.best_seen_score > m.best_seen_score) {
best_mcts = Some(mcts);
}
}
best_mcts.unwrap()
})
}
}
/// Update event info position information to be after the tick update, to
/// match the positions that the client will receive (easier to verify that
/// the values are equal and that the simulation worked).
fn post_update_event_info(info: EventInfo) -> EventInfo {
match info {
EventInfo::MeteorSpawn { id, pos, vel, typ } => EventInfo::MeteorSpawn {
id,
pos: pos.add(&vel),
vel,
typ,
},
EventInfo::MeteorSplit { id, parent_id, pos, vel, typ } => EventInfo::MeteorSplit {
id,
parent_id,
pos: pos.add(&vel),
vel,
typ,
},
_ => info,
}
}