From e63fbf74ba6f448c904d22b225dce3632b5cae43 Mon Sep 17 00:00:00 2001 From: Leonora Tindall Date: Mon, 3 Apr 2023 22:47:45 -0500 Subject: [PATCH] Accept multiple asteroids and shot-time input --- src/player.rs | 192 ++++++++++++++++++++++++++------------------------ 1 file changed, 100 insertions(+), 92 deletions(-) diff --git a/src/player.rs b/src/player.rs index e972eef..08223a0 100644 --- a/src/player.rs +++ b/src/player.rs @@ -7,6 +7,10 @@ use crate::{ nn::{ActivationFunc, NN}, HEIGHT, WIDTH, }; +const NUM_KEYS: usize = 4; +const INPUTS_PER_ASTEROID: usize = 4; +const NUM_ASTEROIDS: usize = 1; +const INPUTS_FOR_SHIP: usize = 2; #[derive(Default)] pub struct Player { pub pos: Vec2, @@ -16,11 +20,9 @@ pub struct Player { rot: f32, drag: f32, bullets: Vec, - asteroid: Option, + asteroids: Vec>, inputs: Vec, pub outputs: Vec, - // asteroid_data: Vec<(f32, f32, f32)>, - raycasts: Vec, last_shot: u32, shot_interval: u32, pub brain: Option, @@ -40,9 +42,15 @@ impl Player { Some(mut c) => { c.retain(|&x| x != 0); // Number of inputs - c.insert(0, 5); + c.insert( + 0, + (INPUTS_PER_ASTEROID * NUM_ASTEROIDS) + + INPUTS_FOR_SHIP + ); // Number of outputs - c.push(4); + c.push( + NUM_KEYS + ); Some(NN::new(c, mut_rate.unwrap(), activ.unwrap())) } _ => None, @@ -55,51 +63,17 @@ impl Player { shot_interval: 18, alive: true, shots: 4, - outputs: vec![0.; 4], - raycasts: vec![0.; 8], + // 4 outputs + outputs: vec![0.; NUM_KEYS], ..Default::default() } } pub fn check_player_collision(&mut self, asteroid: &Asteroid) -> bool { - // To give more near asteroids data: + // Save the asteroid to our asteroids + self.asteroids.push(Some(asteroid.clone())); - // self.asteroid_data.push(( - // ((asteroid.pos - self.pos).length() - asteroid.radius) / WIDTH, - // self.dir.angle_between(asteroid.pos - self.pos), - // (asteroid.vel - self.vel).length(), - // )); - - // Single asteroid data: - if self.asteroid.is_none() - || (asteroid.pos).distance_squared(self.pos) - < self - .asteroid - .as_ref() - .unwrap() - .pos - .distance_squared(self.pos) - { - self.asteroid = Some(asteroid.clone()); - } - - // Try raycasting below: - - // let v = asteroid.pos - self.pos; - // for i in 0..4 { - // let dir = Vec2::from_angle(PI / 4. * i as f32).rotate(self.dir); - // let cross = v.perp_dot(dir); - // let dot = v.dot(dir); - // if cross.abs() <= asteroid.radius { - // self.raycasts[if dot >= 0. { i } else { i + 4 }] = *partial_max( - // &self.raycasts[if dot >= 0. { i } else { i + 4 }], - // &(100. - // / (dot.abs() - (asteroid.radius * asteroid.radius - cross * cross).sqrt())), - // ) - // .unwrap(); - // } - // } if asteroid.check_collision(self.pos, 8.) || self.lifespan > 3600 && self.brain.is_some() { self.alive = false; return true; @@ -107,12 +81,28 @@ impl Player { false } + pub fn consider_asteroids(pos: Vec2, asteroids: &mut Vec>) { + // Consider the closest asteroids first + asteroids.sort_by_key(|ast| match ast { + None => i32::MAX, + Some(ast) => (dist_wrapping(ast.pos, pos, ast.radius) * 100.) as i32, + }); + // Cull if there are too may asteroids + *asteroids = asteroids.iter().cloned().take(NUM_ASTEROIDS).collect(); + // Insert if there are not enought asteroids + if asteroids.len() < NUM_ASTEROIDS { + for _ in 0..NUM_ASTEROIDS - asteroids.len() { + asteroids.push(None); + } + } + assert_eq!(asteroids.len(), NUM_ASTEROIDS); + } + pub fn check_bullet_collisions(&mut self, asteroid: &mut Asteroid) -> bool { for bullet in &mut self.bullets { if asteroid.check_collision(bullet.pos, 0.) { asteroid.alive = false; bullet.alive = false; - self.asteroid = None; return true; } } @@ -125,46 +115,47 @@ impl Player { self.acc = 0.; self.outputs = vec![0.; 4]; let mut keys = vec![false; 4]; - if let Some(ast) = self.asteroid.as_ref() { - self.inputs = vec![ - (ast.pos - self.pos).length() / HEIGHT, - self.dir.angle_between(ast.pos - self.pos), - (ast.vel - self.vel).x * 0.6, - (ast.vel - self.vel).y * 0.6, - self.rot / TAU as f32, - // self.vel.x / 8., - // self.vel.y / 8., - // self.rot / TAU as f32, - ]; - // self.inputs.append(self.raycasts.as_mut()); - - // self.asteroid_data - // .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - // self.asteroid_data.resize(1, (0., 0., 0.)); - // inputs.append( - // &mut self - // .asteroid_data - // .iter() - // .map(|(d, a, h)| vec![*d, *a, *h]) - // .flatten() - // .collect::>(), - // ); - - if let Some(brain) = &self.brain { - self.outputs = brain.feed_forward(&self.inputs); - keys = self - .outputs - .iter() - .map(|&x| { - x > if brain.activ_func == ActivationFunc::Sigmoid { - 0.85 - } else { - 0. - } - }) - .collect(); + self.inputs = vec![]; + // Insert all the asteroid data + Self::consider_asteroids(self.pos, &mut self.asteroids); + for ast in &self.asteroids { + if let Some(ast) = ast { + self.inputs.extend_from_slice(&[ + // Distance to asteroid + dist_wrapping(ast.pos, self.pos, ast.radius), + // Angle to asteroid + self.dir.angle_between(ast.pos - self.pos), + // Asteroid velocity x + (ast.vel - self.vel).x * 0.6, + // Asteroid velocity y + (ast.vel - self.vel).y * 0.6, + ]); + } else { + self.inputs.extend_from_slice(&[0., 0., 0., 0.]); } } + assert_eq!(self.inputs.len(), NUM_ASTEROIDS * INPUTS_PER_ASTEROID); + // Insert the ship data + self.inputs.push(self.rot / TAU as f32); + self.inputs.push( + (self.shot_interval as f32 - self.last_shot as f32).max(0.) / self.shot_interval as f32, + ); + // Run the brain + if let Some(brain) = &self.brain { + assert_eq!(self.inputs.len(), brain.config[0] - 1); + self.outputs = brain.feed_forward(&self.inputs); + keys = self + .outputs + .iter() + .map(|&x| { + x > if brain.activ_func == ActivationFunc::Sigmoid { + 0.85 + } else { + 0. + } + }) + .collect(); + } if keys[0] || self.brain.is_none() && is_key_down(KeyCode::Right) { // RIGHT self.rot = (self.rot + 0.1 + TAU as f32) % TAU as f32; @@ -205,9 +196,7 @@ impl Player { } self.bullets .retain(|b| b.alive && b.pos.x.abs() * 2. < WIDTH && b.pos.y.abs() * 2. < HEIGHT); - self.asteroid = None; - // self.asteroid_data.clear(); - // self.raycasts = vec![0.; 8]; + self.asteroids = vec![]; } pub fn draw(&self, color: Color, debug: bool) { @@ -226,13 +215,17 @@ impl Player { draw_triangle_lines(p6, p7, p8, 2., color); } if debug { - if let Some(ast) = self.asteroid.as_ref() { - draw_circle_lines(ast.pos.x, ast.pos.y, ast.radius, 1., RED); - // let p = self.pos - // + self.dir.rotate(Vec2::from_angle(self.asteroid_data[0].1)) - // * self.asteroid_data[0].0 - // * WIDTH; - draw_line(self.pos.x, self.pos.y, ast.pos.x, ast.pos.y, 1., RED); + let mut debug_asteroids = self.asteroids.clone(); + Self::consider_asteroids(self.pos, &mut debug_asteroids); + for asteroid in &debug_asteroids { + if let Some(ast) = asteroid { + draw_circle_lines(ast.pos.x, ast.pos.y, ast.radius, 1., RED); + // let p = self.pos + // + self.dir.rotate(Vec2::from_angle(self.asteroid_data[0].1)) + // * self.asteroid_data[0].0 + // * WIDTH; + draw_line(self.pos.x, self.pos.y, ast.pos.x, ast.pos.y, 1., RED); + } } // Draw raycasts @@ -276,3 +269,18 @@ impl Bullet { draw_circle(self.pos.x, self.pos.y, 2., Color::new(c.r, c.g, c.b, 0.9)); } } + +// Distance in a toroidal space: +// https://blog.demofox.org/2017/10/01/calculating-the-distance-between-points-in-wrap-around-toroidal-space/ +fn dist_wrapping(a: Vec2, b: Vec2, r: f32) -> f32 { + let mut dx = (a.x - b.x).abs(); + let mut dy = (a.y - b.y).abs(); + + if dx > (WIDTH as f32 / 2.) { + dx = WIDTH as f32 - dx; + } + if dy > (HEIGHT as f32 / 2.) { + dy = HEIGHT as f32 - dy; + } + ((dx * dx + dy * dy).sqrt() - r) / (WIDTH * WIDTH + HEIGHT * HEIGHT).sqrt() +}