Skip to content

Instantly share code, notes, and snippets.

@ronlobo
Last active February 12, 2020 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ronlobo/ba6039d22d6ee9092f5094c0aa32407d to your computer and use it in GitHub Desktop.
Save ronlobo/ba6039d22d6ee9092f5094c0aa32407d to your computer and use it in GitHub Desktop.
Genetic solution for Codingame Marslander v2 puzzle in Rust - heavily inspired by https://github.com/gzoritchak/marslander
pub mod env {
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
pub enum EnvOption {
PROD,
DEV,
}
pub const ENV: EnvOption = EnvOption::PROD;
pub fn is_prod() -> bool {
match ENV {
EnvOption::PROD => true,
EnvOption::DEV => false
}
}
}
pub mod cartesian {
use std::{ops, cmp};
use std::cmp::Ordering;
use crate::lander::SURFACE_N;
pub const SIZE_X: usize = 7000;
pub const SIZE_Y: usize = 3000;
pub type Radian = f32;
pub type Segment = (Point2, Point2);
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
pub struct Point2((f32, f32));
impl Point2 {
pub fn new(x: f32, y: f32) -> Self { Self((x, y)) }
pub fn x(&self) -> f32 { (self.0).0 }
pub fn y(&self) -> f32 { (self.0).1 }
}
impl ops::Add<Vector2> for Point2 {
type Output = Self;
fn add(self, vector: Vector2) -> Self::Output {
Self((self.x() + vector.dx(), self.y() + vector.dy()))
}
}
impl ops::Sub<Point2> for Point2 {
type Output = Vector2;
fn sub(self, other: Point2) -> Self::Output {
Vector2((self.x() - other.x(), self.y() - other.y()))
}
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Angle {
pub rad: Radian
}
impl Angle {
fn new(rad: Radian) -> Self { Self { rad } }
fn deg(&self) -> Radian { self.rad.to_degrees() }
}
impl ops::Mul<f32> for Angle {
type Output = Angle;
fn mul(self, mul: f32) -> Self::Output {
Angle::new(self.deg() * mul)
}
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Vector2(pub (f32, f32));
impl Vector2 {
pub fn dx(&self) -> f32 { (self.0).0 }
pub fn dy(&self) -> f32 { (self.0).1 }
}
impl ops::Add<Vector2> for Vector2 {
type Output = Vector2;
fn add(self, rhs: Vector2) -> Vector2 {
Vector2((self.dx() + rhs.dx(), self.dy() + rhs.dy()))
}
}
impl ops::Mul<f32> for Vector2 {
type Output = Vector2;
fn mul(self, times: f32) -> Self::Output {
Vector2((self.dx() * times, self.dy() * times))
}
}
impl Vector2 {
const PRECISION: u128 = 10; // TODO: figure out codingame rounding, huge discrepancy on last trajectory
fn length(&self) -> f32 { (self.dx() * self.dx() + self.dy() * self.dy()).sqrt() }
pub fn rotate(&self, angle: Angle) -> Self {
Self(
(
((self.dx() * angle.rad.cos() - self.dy() * angle.rad.sin()) * Self::PRECISION as f32).round() / Self::PRECISION as f32,
((self.dx() * angle.rad.sin() + self.dy() * angle.rad.cos()) * Self::PRECISION as f32).round() / Self::PRECISION as f32,
)
)
}
}
pub type LineValue = Vec<Point2>; // cap: SURFACE_N
#[derive(Clone, PartialOrd, PartialEq, Debug)]
pub struct Line(LineValue);
impl Line {
pub fn new(line_value: LineValue) -> Self {
assert!(
line_value.iter().count() > 1,
"At least two points required for line."
);
Line(line_value)
}
pub fn points(&self) -> &LineValue {
&self.0
}
pub fn land_zone(&self) -> (Point2, Point2) {
let points = self.points();
let segment = points
.windows(2)
.find(|win| win.get(0).unwrap().y().eq(&win.get(1).unwrap().y()))
.unwrap();
(segment[0], segment[1])
}
pub fn is_horizontal_at_x(&self, x: i32) -> bool {
let segment = self.get_segment_for(x);
let y1 = &segment.0.y();
let y2 = &segment.1.y();
let is_horizontal = y1.eq(y2);
is_horizontal
}
pub fn get_segment_for(&self, x: i32) -> Segment {
let points = self.points();
let segment = points
.windows(2)
.find(|win| x.max(0).min(SIZE_X as i32 - 1) <= win.get(1).unwrap().x() as i32)
.unwrap();
(segment[0], segment[1])
}
pub fn get_y_for_x(&self, x: f32) -> i32 {
let segment = self.get_segment_for(x as i32);
(segment.0.y() + (x - segment.0.x()) * (segment.1.y() - segment.0.y()) / (segment.1.x() - segment.0.x())) as i32
}
}
impl Default for Line {
fn default() -> Self {
Line(vec![Point2::default(); SURFACE_N])
}
}
impl cmp::PartialEq<Point2> for Line {
fn eq(&self, _point: &Point2) -> bool { unimplemented!() }
fn ne(&self, _point: &Point2) -> bool { unimplemented!() }
}
impl cmp::PartialOrd<Point2> for Line {
fn partial_cmp(&self, other: &Point2) -> Option<Ordering> {
let y_for_x = self.get_y_for_x(other.x());
if y_for_x >= 0 || y_for_x == 0 {
Some(Ordering::Greater)
} else {
None
}
}
fn gt(&self, point: &Point2) -> bool {
let y_for_x = self.get_y_for_x(point.x()) as f32;
let is_greater = y_for_x - point.y() >= 0.0_f32;
if is_greater {
return is_greater;
}
is_greater
}
}
}
pub mod physics {
use std::ops;
use crate::cartesian::*;
pub const GRAVITY_Y: f32 = -3.711;
pub const GRAVITY: Acceleration = acceleration(0.0, GRAVITY_Y);
pub const fn acceleration(dx: f32, dy: f32) -> Acceleration { Acceleration(Vector2((dx, dy))) }
pub type Seconds = f32;
pub type Position = Point2;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Acceleration(pub Vector2);
impl ops::Mul<Time> for Acceleration {
type Output = Speed;
fn mul(self, mul: Time) -> Self::Output {
Speed { direction: self.0 * mul.sec() }
}
}
impl ops::Add<Acceleration> for Acceleration {
type Output = Self;
fn add(self, other: Acceleration) -> Self::Output {
Acceleration(self.0 + other.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Time(pub Seconds);
impl Time {
pub fn sec(&self) -> Seconds { self.0 }
}
pub trait ToTime { fn sec(self) -> Time; }
impl ToTime for f32 {
fn sec(self) -> Time { Time(self) }
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Particle {
pub position: Position,
pub speed: Speed,
}
impl Particle {
pub fn accelerate(&self, acceleration: Acceleration, time: Time) -> Self {
Particle {
position: self.position + self.speed.direction * time.0 + acceleration.0 * time.0 * time.0 * 0.5,
speed: self.speed + acceleration * time,
}
}
}
impl Default for Time {
fn default() -> Self { Time(1.0_f32) }
}
impl ops::Mul<Time> for Time {
type Output = Self;
fn mul(self, other: Time) -> Self::Output {
Time(self.0 * other.0)
}
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Speed {
pub direction: Vector2,
}
impl ops::Add<Speed> for Speed {
type Output = Speed;
fn add(self, rhs: Speed) -> Self::Output {
Speed { direction: self.direction + rhs.direction }
}
}
}
pub mod genetic {
use crate::lander::*;
use rand::Rng;
use rand::prelude::ThreadRng;
use std::borrow::Borrow;
pub const ELITISM: bool = true;
pub const GENERATIONS_COUNT: usize = 10;
pub const POPULATION_SIZE: usize = 256;
pub const GENOME_SIZE: usize = 128;
const UNIFORM_RATE: f32 = 0.7;
const MUTATION_RATE: f32 = 0.05;
const SELECTION_RATIO: f32 = 0.7;
pub type Genome = [Gene; GENOME_SIZE];
pub type Population = Vec<LanderWithGenomeAndResult>; // cap: POPULATION_SIZE
pub type Generation = Vec<Population>; // cap: GENERATIONS_COUNT
pub type FnGeneratePopulation = dyn Fn() -> Box<dyn Fn(&Genome) -> Lander>; // todo: make trait
pub type FnCalcFitness = dyn Fn() -> Box<dyn Fn(&Lander) -> f32>;
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Gene(pub f32);
impl Gene {
pub fn as_int(&self, max: i32) -> i32 { (self.0 * (max + 1) as f32) as i32 }
}
pub trait GenomeAndResult {
type New;
type Result;
fn new(genome: Genome, gen_pop: &FnGeneratePopulation) -> Self::New;
fn genome(&self) -> Genome;
fn result(&self) -> &Self::Result;
}
#[derive(Clone)]
pub struct LanderWithGenomeAndResult {
genome: Genome,
result: Lander,
}
impl GenomeAndResult for LanderWithGenomeAndResult {
type New = Self;
type Result = Lander;
fn new(genome: Genome, gen_lander: &FnGeneratePopulation) -> Self::New {
LanderWithGenomeAndResult {
genome,
result: gen_lander()(&genome),
}
}
fn genome(&self) -> Genome { self.genome }
fn result(&self) -> &Self::Result { self.result.borrow() }
}
fn build_next_generation(
rng: &mut ThreadRng,
population: &Population,
gen_pop: &FnGeneratePopulation,
) -> Population {
let elitism_offset = if ELITISM { 1 } else { 0 };
let best_pop: &LanderWithGenomeAndResult = population.get(0).unwrap();
let mut new_pop: Population = Vec::with_capacity(POPULATION_SIZE);
if elitism_offset == 1 {
new_pop.push(best_pop.clone());
}
for _ in elitism_offset..(population.len()) {
let genome1 = select(&population, rng).genome();
let genome2 = select(&population, rng).genome();
let mut genome = crossover(&genome1, &genome2, rng);
mutate(rng, &mut genome);
new_pop.push(LanderWithGenomeAndResult::new(genome, gen_pop));
}
new_pop
}
pub fn find_best_population(gen_lander: &FnGeneratePopulation, calc_fitness: &FnCalcFitness) -> LanderWithGenomeAndResult
{
let mut rng = rand::thread_rng();
let mut population: Population = Vec::with_capacity(POPULATION_SIZE);
for _ in 0..POPULATION_SIZE {
let mut genome: [Gene; GENOME_SIZE] = [Gene::default(); GENOME_SIZE];
population.push(
LanderWithGenomeAndResult::new(
build_genome(&mut genome, &mut rng), gen_lander)
);
}
population.sort_by(
|a, b| {
(calc_fitness()(&b.result()) as i32).cmp(&(calc_fitness()(&a.result()) as i32))
});
let population: Population = [0; GENERATIONS_COUNT]
.iter()
.fold(population, |cur: Population, _| {
let mut next_pop = build_next_generation(&mut rng, &cur, gen_lander);
next_pop.sort_by(|a, b|
(calc_fitness()(&b.result()) as i32).cmp(&(calc_fitness()(&a.result()) as i32))
);
next_pop
});
population.first().unwrap().to_owned()
}
pub fn build_genome(buf: &mut Genome, rng: &mut ThreadRng) -> Genome {
for i in 0..buf.len() {
buf[i] = Gene(rng.gen::<f32>());
}
*buf
}
pub fn select(population: &Population, rng: &mut ThreadRng) -> LanderWithGenomeAndResult {
for i in 0..population.len() {
if rng.gen::<f32>() <= SELECTION_RATIO * (population.len() - i) as f32 / population.len() as f32 {
return population.get(i).unwrap().clone();
}
}
population.first().unwrap().clone()
}
pub fn crossover<'a, 'b>(genome1: &'a Genome, genome2: &'a Genome, rng: &'b mut ThreadRng) -> Genome {
return *if rng.gen::<f32>() <= UNIFORM_RATE {
genome1
} else {
genome2
};
}
pub fn mutate(rng: &mut ThreadRng, genome: &mut Genome) {
for i in 0..genome.len() {
if rng.gen::<f32>() <= MUTATION_RATE {
genome[i] = Gene(rng.gen::<f32>());
}
}
}
}
pub mod lander {
use crate::cartesian::*;
use crate::physics::*;
pub const SURFACE_N: usize = 29; // max 29 points to paint the mars surface
pub const VERTICAL_SPEED_TOLERANCE: f32 = 40.0;
pub const HORIZONTAL_SPEED_TOLERANCE: f32 = 20.0;
pub type Commands = Vec<ControlCmd>; // cap: GENOME_SIZE
pub type Trajectory = Vec<LanderState>; // cap: GENOME_SIZE
pub type ControlCmdBuilder = Box<dyn Fn(usize) -> ControlCmd>;
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct ControlCmd {
pub power: i32,
pub angle: i32,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum CrashResult {
Outside,
NoFuel,
Collision,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FlyState {
Flying,
Crashed(CrashResult),
Landed,
}
impl Default for FlyState {
fn default() -> Self {
FlyState::Flying
}
}
#[derive(Clone)]
pub struct Lander {
ground: Line,
init_lander_state: LanderState,
commands: Commands,
trajectory: Trajectory,
fly_state: FlyState,
}
impl Lander {
pub fn new(
ground: Line,
init_lander_state: LanderState,
commands: Commands,
) -> Self {
let (fly_state, trajectory) = Lander::compute_trajectory(
&ground,
&init_lander_state,
&commands,
);
Lander {
ground,
init_lander_state,
commands,
trajectory,
fly_state,
}
}
pub fn land_zone(&self) -> Segment { self.ground.land_zone() }
pub fn init_lander_state(&self) -> &LanderState { &self.init_lander_state }
pub fn commands(&self) -> &Commands { &self.commands }
pub fn trajectory(&self) -> &Trajectory { &self.trajectory }
pub fn fly_state(&self) -> &FlyState { &self.fly_state }
fn compute_trajectory(
ground: &Line,
init_lander_state: &LanderState,
commands: &Commands,
) -> (FlyState, Trajectory) {
let fly_state = FlyState::Flying;
let mut trajectory: Trajectory = Vec::with_capacity(commands.len() + 1);
let mut cur_state: LanderState = *init_lander_state;
trajectory.push(cur_state.clone());
for i in 1..commands.len() + 1 {
cur_state = *trajectory.get(i - 1).unwrap();
let next_state = cur_state.compute_next_state(
commands.get(i - 1).unwrap(),
1.0_f32.sec(),
);
trajectory.push(next_state);
let fly_state = Lander::evaluate_outside(&next_state);
if fly_state != FlyState::Flying {
return (fly_state, trajectory);
}
let fly_state = Lander::evaluate_no_fuel(&next_state);
if fly_state != FlyState::Flying {
return (fly_state, trajectory);
}
let fly_state = Lander::evaluate_hit_the_ground(&ground, &next_state);
if fly_state != FlyState::Flying {
return (fly_state, trajectory);
}
}
(fly_state, trajectory)
}
fn evaluate_outside(next_state: &LanderState) -> FlyState {
if next_state.position().x() as usize > SIZE_X - 1
|| next_state.position().x() < 0.0_f32
|| next_state.position().y() as usize > SIZE_Y - 1 {
FlyState::Crashed(CrashResult::Outside)
} else {
FlyState::Flying
}
}
fn evaluate_no_fuel(next_state: &LanderState) -> FlyState {
if next_state.fuel <= 0 {
FlyState::Crashed(CrashResult::NoFuel)
} else {
FlyState::Flying
}
}
fn evaluate_hit_the_ground(ground: &Line, next_state: &LanderState) -> FlyState {
return if *ground > next_state.position() {
return if Lander::is_landed(&ground, &next_state) {
FlyState::Landed
} else {
FlyState::Crashed(CrashResult::Collision)
};
} else {
FlyState::Flying
};
}
fn is_landed(ground: &Line, lander_state: &LanderState) -> bool {
Self::is_centered(lander_state)
&& Self::is_within_horizontal_speed(lander_state)
&& Self::is_within_vertical_speed(lander_state)
&& Self::is_on_land_zone(ground, lander_state)
}
fn is_centered(lander_state: &LanderState) -> bool { lander_state.angle == 0 }
fn is_within_horizontal_speed(lander_state: &LanderState) -> bool {
lander_state.speed().direction.dy() > -40.0_f32
}
fn is_within_vertical_speed(lander_state: &LanderState) -> bool {
lander_state.speed().direction.dx().abs() <= 20.0_f32
}
fn is_on_land_zone(ground: &Line, lander_state: &LanderState) -> bool {
ground.is_horizontal_at_x(lander_state.position().x() as i32)
}
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct LanderState {
pub fuel: i32,
pub power: i32,
pub angle: i32,
pub particle: Particle,
}
impl LanderState {
pub fn compute_next_state(&self, cmd: &ControlCmd, time: Time) -> LanderState {
let angle = cmd.angle;
let power = cmd.power;
let vector = (Vector2((0.0, 1.0)) * power as f32).rotate(Angle { rad: (angle as f32).to_radians() });
let fuel = self.fuel - power;
let thrust = Acceleration(vector);
let acceleration = GRAVITY + thrust;
let particle = self.particle.accelerate(acceleration, time);
LanderState {
fuel,
power,
angle,
particle,
}
}
pub fn position(&self) -> Position { self.particle.position }
pub fn speed(&self) -> Speed {
self.particle.speed
}
}
}
pub mod game {
use crate::lander::{SURFACE_N, LanderState};
use crate::cartesian::{Line, LineValue, Point2, Vector2};
use crate::physics::{Particle, Speed};
use crate::env::is_prod;
use std::{io, mem};
use std::sync::{Arc, Mutex, Once};
macro_rules! parse_input {
($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
}
type GameLevel = [Option<&'static str>; SURFACE_N + 2];
const RUN_LEVEL: usize = 0;
const GAME_LEVELS: [GameLevel; 2] = [
[
Some("6"),
Some("0 100"),
Some("1000 500"),
Some("1500 100"),
Some("3000 100"),
Some("5000 1500"),
Some("6999 1000"),
Some("2500 2500 0 0 500 0 0"),
None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None
],
[
Some("7"),
Some("0 100"),
Some("1000 500"),
Some("1500 1500"),
Some("3000 1000"),
Some("4000 150"),
Some("5500 150"),
Some("6999 800"),
Some("2500 2700 0 0 550 0 0"),
None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None,
]
];
#[derive(Clone)]
pub struct GameStateSingleton {
pub inner: Arc<Mutex<GameState>>,
}
pub fn game_state_singleton() -> &'static GameStateSingleton {
static mut SINGLETON: *const GameStateSingleton = 0 as *const GameStateSingleton;
static ONCE: Once = Once::new();
unsafe {
ONCE.call_once(|| {
let singleton = GameStateSingleton {
inner: Arc::new(Mutex::new(GameState::build())),
};
SINGLETON = mem::transmute(Box::new(singleton));
});
&*SINGLETON
}
}
#[derive(Clone, Debug)]
pub struct GameState {
pub ground: Line,
pub init_lander_state: LanderState,
}
impl GameState {
const fn new(
ground: Line,
init_lander_state: LanderState,
) -> Self {
Self { ground, init_lander_state }
}
pub fn build() -> Self {
let mut input_line = String::new();
let mut line_value: LineValue = Vec::with_capacity(SURFACE_N);
if !is_prod() {
input_line = GAME_LEVELS
.get(RUN_LEVEL)
.unwrap()
.get(0)
.unwrap()
.unwrap()
.to_string();
} else {
io::stdin().read_line(&mut input_line).unwrap();
};
let surface_n = parse_input!(input_line, usize);
for i in 0..surface_n {
let mut input_line = String::new();
if !is_prod() {
input_line = GAME_LEVELS
.get(RUN_LEVEL)
.unwrap()
.get(i + 1)
.unwrap()
.unwrap()
.to_string();
} else {
io::stdin().read_line(&mut input_line).unwrap();
}
let inputs = input_line.split(" ").collect::<Vec<_>>();
let land_x = parse_input!(inputs.get(0).unwrap(), f32); // X coordinate of a surface point. (0 to 6999)
let land_y = parse_input!(inputs.get(1).unwrap(), f32); // Y coordinate of a surface point. By linking all the points together in a sequential fashion, you form the surface of Mars.
line_value.push(Point2::new(land_x, land_y));
}
let mut input_line = String::new();
if !is_prod() {
input_line = GAME_LEVELS
.get(RUN_LEVEL)
.unwrap()
.get(surface_n + 1)
.unwrap()
.unwrap()
.to_string();
} else {
io::stdin().read_line(&mut input_line).unwrap();
}
let inputs = input_line.split(" ").collect::<Vec<_>>();
let x = parse_input!(inputs.get(0).unwrap(), f32);
let y = parse_input!(inputs.get(1).unwrap(), f32);
let dx = parse_input!(inputs.get(2).unwrap(), f32); // the horizontal speed (in m/s), can be negative.
let dy = parse_input!(inputs.get(3).unwrap(), f32); // the vertical speed (in m/s), can be negative.
let fuel = parse_input!(inputs.get(4).unwrap(), i32); // the quantity of remaining fuel in liters.
let angle = parse_input!(inputs.get(5).unwrap(), i32); // the rotation angle in degrees (-90 to 90).
let power = parse_input!(inputs.get(6).unwrap(), i32); // the thrust power (0 to 4).
let init_lander_state = LanderState {
fuel,
power,
angle,
particle: Particle {
position: Point2::new(x, y),
speed: Speed {
direction: Vector2((dx, dy))
},
},
};
let ground = Line::new(line_value);
if !is_prod() {
eprintln!(
"[{}]",
ground
.points()
.iter()
.fold(
String::new(),
|acc, num|
acc + format!("x: {}, y: {}", num.x(), num.y()).as_ref() + ", ",
)
);
}
Self::new(ground, init_lander_state)
}
}
}
use rand::prelude::ThreadRng;
use {genetic::*, lander::*, game::*};
use std::time::Instant;
const COMMAND_SIZE: usize = 9;
const AVAILABLE_COMMANDS: [(i32, i32); COMMAND_SIZE] = [(-15, -1), (-15, 0), (-15, 1), (0, -1), (0, 0), (0, 1), (15, -1), (15, 0), (15, 1)];
fn main() {
let timer = Instant::now();
let best_population: LanderWithGenomeAndResult = find_best_population(
&|| Box::new(|genome: &Genome| lander_from_genome(&genome)),
&|| Box::new(|lander: &Lander| calculate_lander_fitness(&lander)),
) as LanderWithGenomeAndResult;
let duration = timer.elapsed();
let best_population_result = best_population.result();
let trajectory = best_population_result.trajectory();
eprintln!("Time elapsed: {} ms", duration.as_millis());
eprintln!("Lander state: {:?}", best_population_result.fly_state());
eprintln!("Lander fuel: {:?}", trajectory.last().unwrap().fuel);
eprintln!("Lander pos: ({:?})", trajectory.last().unwrap().particle.position);
eprintln!("Lander speed: ({:?})", trajectory.last().unwrap().speed());
eprintln!("Fitness: {}", calculate_lander_fitness(&best_population.result()));
eprintln!("Trajectory Len: {}", trajectory.len());
eprintln!(
"angle {} speed {} X {} Y {}",
trajectory.get(0).unwrap().angle as i32,
trajectory.get(0).unwrap().power as i32,
trajectory.get(0).unwrap().position().x() as i32,
trajectory.get(0).unwrap().position().y() as i32,
);
eprintln!(
"X={}m, Y={}m, HSpeed={}m/s VSpeed={}m/s",
trajectory.last().unwrap().position().x() as i32,
trajectory.last().unwrap().position().y() as i32,
trajectory.last().unwrap().speed().direction.dx() as i32,
trajectory.last().unwrap().speed().direction.dy() as i32,
);
eprintln!(
"Fuel={}l, Angle={}˚, Power={}m/s*s",
trajectory.last().unwrap().fuel as i32,
trajectory.last().unwrap().angle as i32,
trajectory.last().unwrap().power as i32,
);
for i in 0..trajectory.len() - 1 {
let command = best_population_result.commands().get(i).unwrap();
println!(
"{} {}",
command.angle as i32,
command.power as i32
);
}
}
fn lander_from_genome(genome: &Genome) -> Lander {
let mut commands = Vec::with_capacity(genome.len());
let mut prev_power = 0_i32;
let mut prev_angle = 0_i32;
for i in 0..commands.capacity() {
let tmp_command = AVAILABLE_COMMANDS.get(((genome.get(i).unwrap().as_int(COMMAND_SIZE as i32 - 1) + 1) % COMMAND_SIZE as i32) as usize).unwrap();
let tmp_angle: i32 = prev_angle + tmp_command.0;
let tmp_power: i32 = prev_power + tmp_command.1;
let power = tmp_power.min(4).max(0);
let angle = tmp_angle.min(90).max(-90);
commands.push(ControlCmd { power, angle });
prev_angle = tmp_angle;
prev_power = tmp_power;
}
let singleton = game_state_singleton();
let game_state = singleton.inner.lock().unwrap();
Lander::new(game_state.ground.to_owned(), game_state.init_lander_state.to_owned(), commands)
}
fn calculate_lander_fitness(lander: &Lander) -> f32 {
let last_trajectory = *lander.trajectory().last().unwrap();
let last_y_pos = last_trajectory.position().y();
let last_x_pos = last_trajectory.position().x();
let last_x_neg = -1.0 * last_x_pos;
let land_zone = lander.land_zone();
let land_zone_y = land_zone.0.y();
let land_x_left = land_zone.0.x();
let land_x_right = land_zone.1.x();
let speed_x = last_trajectory.speed().direction.dx();
let speed_y = last_trajectory.speed().direction.dy();
let distance_x = if last_x_pos < land_x_left {
last_x_pos - land_x_left
} else if last_x_pos > land_x_right {
last_x_neg + land_x_right
} else {
0.0
};
let distance_y = (land_zone_y - last_y_pos).min(0.0);
let distance_fitness = distance_y + distance_x;
let is_on_land_zone = distance_y == 0.0 && distance_x == 0.0;
let vertical_speed_fitness = (-1.0 * speed_x.abs()).min(-1.0 * VERTICAL_SPEED_TOLERANCE) + VERTICAL_SPEED_TOLERANCE;
let horizontal_speed_fitness = (-1.0 * speed_y.abs()).min(-1.0 * HORIZONTAL_SPEED_TOLERANCE) + HORIZONTAL_SPEED_TOLERANCE;
let angle_fitness = -1.0 * last_trajectory.angle.abs() as f32;
return match lander.fly_state() {
FlyState::Landed => {
lander.trajectory().iter().last().unwrap().fuel as f32
}
FlyState::Flying => {
vertical_speed_fitness + horizontal_speed_fitness + distance_fitness + angle_fitness
}
FlyState::Crashed(_) => {
if is_on_land_zone {
vertical_speed_fitness + horizontal_speed_fitness + angle_fitness
} else {
vertical_speed_fitness + horizontal_speed_fitness + distance_fitness + angle_fitness
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment