extern crate sdl2; extern crate std; extern crate rand; use super::player; use super::constants; pub struct EBullet { x: i32, y: i32, radius: u32, real_x: f64, real_y: f64, d_x: f64, d_y: f64, } enum EnemyStatus { Accel, Travel, Decel, } pub struct Enemy { pub x: f64, pub y: f64, v_x: f64, v_y: f64, a_x: f64, a_y: f64, target_x: f64, target_y: f64, radius: u32, pub health: i64, } const STARTING_HEALTH: i64 = 100; impl EBullet { pub fn new(x: i32, d_x: f64, y: i32, d_y: f64) -> EBullet { EBullet { x: x, real_x: x as f64, y: y, real_y: y as f64, radius: 10, d_x: d_x, d_y: d_y } } pub fn act(&mut self) { self.real_x += self.d_x; self.real_y += self.d_y; self.x = self.real_x.round() as i32; self.y = self.real_y.round() as i32; } } pub struct PBullet { b: EBullet, } impl PBullet { pub fn new(x: i32, d_x: f64, y: i32, d_y: f64) -> PBullet { PBullet { b: EBullet::new(x, d_x, y, d_y) } } pub fn act(&mut self) { self.b.act() } pub fn delete(&mut self) { self.b.x = -10; } } fn random_bullet(enemy: &Enemy) -> EBullet { let multiplier = 2. + (STARTING_HEALTH as i64 - enemy.health) as f64 / STARTING_HEALTH as f64 * 2.; let theta: f64 = rand::random::()*2.*std::f64::consts::PI/3. + std::f64::consts::PI/6.; let dx = f64::cos(theta) * multiplier; let dy = f64::sin(theta) * multiplier; EBullet::new(enemy.x.round() as i32, dx, enemy.y.round() as i32, dy) } fn random_position() -> (f64, f64) { (rand::random::() * (constants::X_DIM - 200) as f64 + 100., rand::random::() * 100.) } fn shorten(x: f64, y: f64, maxlen: f64) -> (f64, f64) { let len = f64::sqrt(x*x + y*y); if len > maxlen { (x / len / 2., y / len / 2.) } else { (x, y) } } impl Enemy { pub fn new() -> Enemy { let (x, y) = random_position(); Enemy { x: 200., y: 40., v_x: 0., v_y: 0., a_x: 0., a_y: 0., radius: 30, health: STARTING_HEALTH, target_x: x, target_y: y } } pub fn act(&mut self, count: u64) -> Vec { let dx = self.target_x - self.x as f64; let dy = self.target_y - self.y as f64; let vec_x = self.target_x - self.x; let vec_y = self.target_y - self.y; let (x, y) = shorten(vec_x, vec_y, 0.25); self.a_x = x; self.a_y = y; /* Except that if we're really close, we actually should just slow down */ if f64::sqrt(dx*dx + dy*dy) < 100. { let (x, y) = shorten(-self.v_x, -self.v_y, 0.25); self.a_x = x; self.a_y = y; /* And, if we're close and we managed to slow down, we'll pick a new target */ if f64::sqrt(self.v_x*self.v_x + self.v_y*self.v_y) < 0.1 { let (x, y) = random_position(); self.target_x = x; self.target_y = y; } } /* Close enough */ /* If we're too fast, don't go faster */ if f64::sqrt(self.v_x*self.v_x + self.v_y*self.v_y) > 10. { if self.v_x.signum() == self.a_x.signum() { self.a_x = 0.; } if self.v_y.signum() == self.a_y.signum() { self.a_y = 0.; } } self.v_x += self.a_x; self.x += self.v_x; self.v_y += self.a_y; self.y += self.v_y; if count % 20 == 0 { let num_bullets; if self.health >= 80 { num_bullets = 4; } else if self.health >= 50 { num_bullets = 5; } else if self.health >= 30 { num_bullets = 6; } else if self.health >= 20 { num_bullets = 7; } else { num_bullets = 8; } let mut result = Vec::new(); for _ in 0..num_bullets { result.push(random_bullet(&*self)); } result } else { Vec::new() } } pub fn health(&self) -> i64 { self.health } } pub struct State { pub player: player::Player, pub enemies: Vec, pub bullets: Vec, pub player_bullets: Vec, pub count: u64, } impl State { pub fn new() -> State { State { player: player::Player::new(), enemies: Vec::new(), bullets: Vec::new(), count: 0, player_bullets: Vec::new() } } } fn dist(x1: i32, y1: i32, x2: i32, y2: i32) -> f64 { let xdist = x1 - x2; let ydist = y1 - y2; f64::sqrt((xdist*xdist + ydist*ydist) as f64) } pub trait Positioned { fn x(&self) -> i32; fn y(&self) -> i32; fn radius(&self) -> u32; fn contains(&self, x: i32, y: i32) -> bool { let d = dist(self.x(), self.y(), x, y); d <= self.radius() as f64 } fn overlaps(&self, other: &T) -> bool { let dist = dist(self.x(), self.y(), other.x(), other.y()); dist < (self.radius() + other.radius()) as f64 } fn outside(&self) -> bool { let x = self.x(); let y = self.y(); x < 0 || x as u32 > constants::X_DIM || y < 0 || y as u32 > constants::Y_DIM } } impl Positioned for EBullet { fn x(&self) -> i32 { self.x } fn y(&self) -> i32 { self.y } fn radius(&self) -> u32 { self.radius } } impl Positioned for PBullet { fn x(&self) -> i32 { self.b.x() } fn y(&self) -> i32 { self.b.y() } fn radius(&self) -> u32 { self.b.radius() } } impl Positioned for Enemy { fn x(&self) -> i32 { self.x.round() as i32 } fn y(&self) -> i32 { self.y.round() as i32 } fn radius(&self) -> u32 { self.radius } } pub trait Drawable : Positioned { fn color(&self) -> sdl2::pixels::Color; fn draw(&self, canvas: &mut sdl2::render::WindowCanvas) { let x = self.x(); let y = self.y(); let r = self.radius(); let r = r as i32; let x_min = x - r; let x_max = x + r; let y_min = y - r; let y_max = y + r; canvas.set_draw_color(self.color()); for x in x_min..x_max + 1 { for y in y_min..y_max + 1 { if self.contains(x, y) { canvas.fill_rect(sdl2::rect::Rect::new(x, y, 1, 1)).unwrap(); } } } } } impl Drawable for Enemy { fn color(&self) -> sdl2::pixels::Color { let red_scale = self.health as f64 / STARTING_HEALTH as f64 * 255.; sdl2::pixels::Color::RGB(red_scale as u8, 255 - red_scale as u8, 0) } } impl Drawable for EBullet { fn color(&self) -> sdl2::pixels::Color { sdl2::pixels::Color::RGB(0, 0, 255) } } impl Drawable for PBullet { fn color(&self) -> sdl2::pixels::Color { sdl2::pixels::Color::RGB(230, 255, 230) } }