Skip to content

Instantly share code, notes, and snippets.

@seandewar
Last active September 27, 2019 15:31
Show Gist options
  • Save seandewar/caf5b0d8a62a6ee8673b936ebd18a51b to your computer and use it in GitHub Desktop.
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.
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