Last active
September 27, 2019 15:31
-
-
Save seandewar/caf5b0d8a62a6ee8673b936ebd18a51b to your computer and use it in GitHub Desktop.
Code for a bot for Coders Strike Back in Gold Tier (codingame.com) written in Rust.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{io, option, vec, cmp}; | |
macro_rules! print_err { | |
($($arg:tt)*) => ( | |
{ | |
use std::io::Write; | |
writeln!(&mut ::std::io::stderr(), $($arg)*).ok(); | |
} | |
) | |
} | |
macro_rules! parse_input { | |
($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap()) | |
} | |
/** | |
* Useful constants | |
**/ | |
const CHECKPOINT_RADIUS: i32 = 600; | |
const POD_FORCEFIELD_RADIUS: i32 = 400; | |
const POD_MAX_TURN_PIVOT_DEGREES: i32 = 18; | |
const POD_BOOST_ACCELERATION: i32 = 650; | |
const POD_FRICTION: f64 = 0.85; | |
/** | |
* Math helpers | |
**/ | |
fn distance_sq(ax: i32, ay: i32, bx: i32, by: i32) -> i64 { | |
let (a, b) = ((bx - ax) as i64, (by - ay) as i64); | |
(a * a) + (b * b) | |
} | |
fn distance(ax: i32, ay: i32, bx: i32, by: i32) -> f64 { | |
(distance_sq(ax, ay, bx, by) as f64).sqrt() | |
} | |
fn angle_between(ax: i32, ay: i32, bx: i32, by: i32) -> f64 { | |
let mut theta = f64::atan2((by - ay) as f64, (bx - ax) as f64); | |
if theta < 0.0 { | |
theta += 2.0 * std::f64::consts::PI; | |
} | |
theta | |
} | |
/** | |
* AI code | |
**/ | |
#[derive(Copy, Clone)] | |
struct Checkpoint { | |
id: usize, | |
x: i32, | |
y: i32, | |
} | |
#[derive(Copy, Clone, Default)] | |
struct Pod { | |
x: i32, | |
y: i32, | |
vx: i32, | |
vy: i32, | |
angle: i32, | |
next_checkpoint_id: usize, | |
speed: f64, | |
} | |
impl Pod { | |
fn update(&mut self, x: i32, y: i32, vx: i32, vy: i32, angle: i32, next_checkpoint_id: usize) { | |
self.x = x; | |
self.y = y; | |
self.vx = vx; | |
self.vy = vy; | |
self.speed = ((self.vx * self.vx + self.vy * self.vy) as f64).sqrt(); | |
self.angle = angle; | |
self.next_checkpoint_id = next_checkpoint_id; | |
} | |
fn simulate_move(&mut self, pod_move: &PlayerMove) { | |
// get angle between pod and target point | |
let rtheta = angle_between(self.x, self.y, pod_move.target_x, pod_move.target_y).to_degrees(); | |
// get rotation amounts in both left and right direction | |
let right_turn = if self.angle as f64 <= rtheta { | |
rtheta - self.angle as f64 | |
} else { | |
360.0 - self.angle as f64 + rtheta | |
}; | |
let left_turn = if self.angle as f64 >= rtheta { | |
self.angle as f64 - rtheta | |
} else { | |
self.angle as f64 + 360.0 - rtheta | |
}; | |
// choose smallest rotation direction | |
let turn_angle = if right_turn < left_turn { right_turn } else { -left_turn } | |
.max(-POD_MAX_TURN_PIVOT_DEGREES as f64) | |
.min(POD_MAX_TURN_PIVOT_DEGREES as f64); | |
// rotate pods towards target point (0-360 deg) | |
self.angle = (self.angle as f64 + turn_angle).round() as i32 % 360; | |
// work out acceleration for this turn | |
let accel = match pod_move.move_type { | |
MoveType::Thrust(thrust) => thrust, | |
MoveType::Boost => POD_BOOST_ACCELERATION, | |
MoveType::Shield => 0, | |
}; | |
// accelerate pod in direction of rotation | |
// (make a new vx and vy variable that is floating point | |
// and we will round and cast to an integer value later) | |
let (vx, vy) = (self.vx as f64 + accel as f64 * f64::cos((self.angle as f64).to_radians()), | |
self.vy as f64 + accel as f64 * f64::sin((self.angle as f64).to_radians())); | |
// move the pod by adding the velocity to the pod's position | |
// (and round the result as an integer) | |
self.x = (self.x as f64 + vx).round() as i32; | |
self.y = (self.y as f64 + vy).round() as i32; | |
// apply friction and assign to pod's velocity (as truncated integer) | |
self.vx = (vx * POD_FRICTION) as i32; | |
self.vy = (vy * POD_FRICTION) as i32; | |
} | |
} | |
fn compute_race_pod_move(pod: &Pod, checkpoints: &Vec<Checkpoint>) -> PlayerMove { | |
// get the next checkpoint and the one after that (future checkpoint) | |
let next_checkpoint = &checkpoints[pod.next_checkpoint_id]; | |
let future_checkpoint = &checkpoints[(pod.next_checkpoint_id + 1) % checkpoints.len()]; | |
// simulate pod to compute optimal point to start turning towards | |
// the future checkpoint instead of the next checkpoint if we will | |
// still intersect with the next checkpoint | |
let mut simulated_pod = pod.clone(); | |
let mut target_checkpoint = next_checkpoint; | |
for i in 0..30 { | |
// compute a move for our simulated pod towards the future checkpoint | |
let simulated_pod_move = compute_move_towards_point(&simulated_pod, | |
future_checkpoint.x, | |
future_checkpoint.y, | |
&MoveType::Thrust(100)); | |
// simulate the move on the simulated pod | |
simulated_pod.simulate_move(&simulated_pod_move); | |
// test for collision with next checkpoint | |
if distance_sq(simulated_pod.x, simulated_pod.y, next_checkpoint.x, next_checkpoint.y) | |
<= (CHECKPOINT_RADIUS as i64) * (CHECKPOINT_RADIUS as i64) { | |
target_checkpoint = future_checkpoint; | |
break; | |
} | |
} | |
compute_move_towards_point(&pod, target_checkpoint.x, target_checkpoint.y, &MoveType::Thrust(100)) | |
} | |
fn compute_move_towards_point(pod: &Pod, x: i32, y: i32, move_type: &MoveType) -> PlayerMove { | |
// current velocity vector | |
let (vx, vy) = (pod.vx, pod.vy); | |
// vector towards point | |
let (nx, ny) = (x - pod.x, y - pod.y); | |
// steering velocity vector for this turn | |
let (sx, sy) = (nx - vx, ny - vy); | |
PlayerMove::new(pod.x + sx, pod.y + sy, move_type.clone()) | |
} | |
#[derive(Clone, Default)] | |
struct GameState { | |
laps: i32, | |
round: i32, | |
checkpoints: Vec<Checkpoint>, | |
friendly_pods: [Pod; 2], | |
enemy_pods: [Pod; 2], | |
} | |
#[derive(Copy, Clone)] | |
enum MoveType { | |
Thrust(i32), | |
Boost, | |
Shield, | |
} | |
impl Default for MoveType { | |
fn default() -> Self { | |
MoveType::Thrust(0) | |
} | |
} | |
#[derive(Copy, Clone, Default)] | |
struct PlayerMove { | |
target_x: i32, | |
target_y: i32, | |
move_type: MoveType, | |
} | |
impl PlayerMove { | |
fn new(target_x: i32, target_y: i32, move_type: MoveType) -> Self { | |
PlayerMove { | |
target_x: target_x, | |
target_y: target_y, | |
move_type: move_type, | |
} | |
} | |
} | |
fn execute_move(player_move: &PlayerMove) { | |
let move_str = match player_move.move_type { | |
MoveType::Thrust(thrust) => thrust.to_string(), | |
MoveType::Boost => String::from("BOOST"), | |
MoveType::Shield => String::from("SHIELD"), | |
}; | |
println!("{} {} {}", player_move.target_x, player_move.target_y, move_str); | |
} | |
fn parse_init_input() -> GameState { | |
let mut game_state = GameState::default(); | |
// laps | |
let mut input_line = String::new(); | |
io::stdin().read_line(&mut input_line).unwrap(); | |
game_state.laps = parse_input!(input_line, i32); | |
// checkpointCount | |
let mut input_line = String::new(); | |
io::stdin().read_line(&mut input_line).unwrap(); | |
let num_checkpoints = parse_input!(input_line, usize); | |
// checkpointX checkpointY | |
for i in 0..num_checkpoints { | |
let mut input_line = String::new(); | |
io::stdin().read_line(&mut input_line).unwrap(); | |
let inputs = input_line.split(" ").collect::<Vec<_>>(); | |
game_state.checkpoints.push(Checkpoint {id: i, | |
x: parse_input!(inputs[0], i32), | |
y: parse_input!(inputs[1], i32),}); | |
} | |
game_state | |
} | |
fn parse_pod_input(pod: &mut Pod) { | |
// x y vx vy angle nextCheckPointId | |
let mut input_line = String::new(); | |
io::stdin().read_line(&mut input_line).unwrap(); | |
let inputs = input_line.split(" ").collect::<Vec<_>>(); | |
let x = parse_input!(inputs[0], i32); | |
let y = parse_input!(inputs[1], i32); | |
let vx = parse_input!(inputs[2], i32); | |
let vy = parse_input!(inputs[3], i32); | |
let angle = parse_input!(inputs[4], i32); | |
let next_checkpoint_id = parse_input!(inputs[5], usize); | |
pod.update(x, y, vx, vy, angle, next_checkpoint_id); | |
} | |
fn parse_turn_input(game_state: &mut GameState) { | |
// 2 friendly pods | |
parse_pod_input(&mut game_state.friendly_pods[0]); | |
parse_pod_input(&mut game_state.friendly_pods[1]); | |
// 2 enemy pods | |
parse_pod_input(&mut game_state.enemy_pods[0]); | |
parse_pod_input(&mut game_state.enemy_pods[1]); | |
game_state.round += 1; | |
} | |
fn main() { | |
// init game state | |
let mut game_state = parse_init_input(); | |
// game loop | |
let mut loop_i = -1; | |
loop { | |
loop_i += 1; | |
parse_turn_input(&mut game_state); | |
// TODO move stupidly for now | |
execute_move(&compute_race_pod_move(&game_state.friendly_pods[0], &game_state.checkpoints)); | |
execute_move(&compute_race_pod_move(&game_state.friendly_pods[1], &game_state.checkpoints)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment