From 5846773a84b629765b6a7236bb6363bc4e34ab94 Mon Sep 17 00:00:00 2001 From: Raymond Hogenson Date: Sun, 19 Aug 2018 00:45:11 -0400 Subject: Move some code around and improve abstraction This doesn't change the functionality of the game at all (ideally), but makes it easier to refactor and to add new enemies and new stages, which is really the end-goal of this whole thing. --- src/gates.rs | 185 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 24 +++--- src/player.rs | 4 +- src/state.rs | 246 ++++++++++++++++------------------------------------------ 4 files changed, 262 insertions(+), 197 deletions(-) create mode 100644 src/gates.rs diff --git a/src/gates.rs b/src/gates.rs new file mode 100644 index 0000000..c79297a --- /dev/null +++ b/src/gates.rs @@ -0,0 +1,185 @@ +extern crate rand; +extern crate std; + +use super::state; +use super::constants; + +pub struct Gates { + x: f64, + y: f64, + v_x: f64, + v_y: f64, + a_x: f64, + a_y: f64, + target_x: f64, + target_y: f64, + health: f64, +} + +pub struct GatesBullet { + real_x: f64, + real_y: f64, + d_x: f64, + d_y: f64, +} + +impl GatesBullet { + pub fn new(x: i32, d_x: f64, y: i32, d_y: f64) -> GatesBullet { + GatesBullet { real_x: x as f64, real_y: y as f64, d_x: d_x, d_y: d_y } + } +} + +impl state::Positioned for GatesBullet { + fn x(&self) -> i32 { + self.real_x.round() as i32 + } + fn y(&self) -> i32 { + self.real_y.round() as i32 + } + fn radius() -> u32 { + 10 + } +} + +impl state::Positioned for Gates { + fn x(&self) -> i32 { + self.x.round() as i32 + } + fn y(&self) -> i32 { + self.y.round() as i32 + } + fn radius() -> u32 { + 15 + } +} + +fn random_position() -> (f64, f64) { + (rand::random::() * (constants::X_DIM - 200) as f64 + 100., rand::random::() * 100.) +} + +impl state::StraightBullet for GatesBullet { + fn real_x(&self) -> f64 { + self.real_x + } + fn real_y(&self) -> f64 { + self.real_y + } + fn set_real_x(&mut self, real_x: f64) { + self.real_x = real_x; + } + fn set_real_y(&mut self, real_y: f64) { + self.real_y = real_y; + } + fn d_x(&self) -> f64 { + self.d_x + } + fn d_y(&self) -> f64 { + self.d_y + } +} + +fn random_bullet>(enemy: &T) -> GatesBullet { + let multiplier = 2. + 2.*(1. - enemy.health()); + 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; + GatesBullet::new(enemy.x(), dx, enemy.y(), dy) +} + +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 state::Enemy for Gates { + fn new() -> Gates { + Gates { x: 200., y: 40., v_x: 0., v_y: 0., a_x: 0., a_y: 0., target_x: 200., target_y: 40., health: 1. } + } + fn act(&mut self, count: u64) -> Vec { + let max_accel = 0.25; + let max_velocity = 5.; + let dx = self.target_x - self.x; + let dy = self.target_y - self.y; + /* If we've arrived (close and stopped) */ + if (dx*dx + dy*dy).sqrt() < max_velocity / max_accel && (self.v_x*self.v_x + self.v_y*self.v_y).sqrt() < 0.01 { + /* Then pick a new target */ + let (x, y) = random_position(); + self.target_x = x; + self.target_y = y; + } + + let dx = self.target_x - self.x; + let dy = self.target_y - self.y; + /* If we're close */ + if (dx*dx + dy*dy).sqrt() < max_velocity / max_accel { + /* Then slow down */ + let (x, y) = shorten(-self.v_x, -self.v_y, max_accel); + self.a_x = x; + self.a_y = y; + } else { + /* Otherwise, speed up */ + let (x, y) = shorten(dx, dy, max_accel); + self.a_x = x; + self.a_y = y; + } + + /* If we're going too fast */ + if (self.v_x*self.v_x + self.v_y*self.v_y).sqrt() > max_velocity { + /* Then don't go faster */ + 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 >= 0.8 { + num_bullets = 4; + } else if self.health >= 0.5 { + num_bullets = 5; + } else if self.health >= 0.3 { + num_bullets = 6; + } else if self.health >= 0.2 { + 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() + } + } + fn health(&self) -> f64 { + self.health + } + fn set_health(&mut self, health: f64) { + self.health = health; + } +} + +impl state::Drawable for GatesBullet { + fn center() -> (u32, u32) { + (20, 20) + } +} + +impl state::Drawable for Gates { + fn center() -> (u32, u32) { + (16, 23) + } +} diff --git a/src/main.rs b/src/main.rs index ca1bdd8..6e1322a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,11 @@ extern crate sdl2; mod state; mod player; mod constants; +mod gates; use state::Positioned; use state::Drawable; +use state::Bullet; enum Result { Win, @@ -20,15 +22,7 @@ struct Textures<'a> { ebullet: sdl2::render::Texture<'a>, } -fn spawn(count: u64) -> Vec { - if count == 0 { - vec![state::Enemy::new()] - } else { - Vec::new() - } -} - -fn frame(canvas: &mut sdl2::render::WindowCanvas, state: &mut state::State, events: &mut sdl2::event::EventPollIterator, textures: &Textures) -> Result { +fn frame, U: state::Bullet>(canvas: &mut sdl2::render::WindowCanvas, state: &mut state::State, events: &mut sdl2::event::EventPollIterator, textures: &Textures) -> Result { let mut new_bullets = Vec::new(); let count = state.count; for enemy in state.enemies.iter_mut() { @@ -51,16 +45,16 @@ fn frame(canvas: &mut sdl2::render::WindowCanvas, state: &mut state::State, even for bullet in state.player_bullets.iter_mut() { if enemy.overlaps(bullet) { bullet.delete(); - enemy.health -= 1; + let health = enemy.health(); + enemy.set_health(health - state::PBullet::damage()); } } } - state.enemies.retain(|enemy| state::Enemy::health(enemy) > 0); - state.enemies.append(&mut spawn(count)); + state.enemies.retain(|enemy| state::Enemy::health(enemy) > 0.); if state.enemies.len() == 0 { return Result::Win; } - state.bullets.retain(|bullet| !state::EBullet::outside(bullet)); + state.bullets.retain(|bullet| !U::outside(bullet)); state.bullets.append(&mut new_bullets); state.player_bullets.retain(|bullet| !state::PBullet::outside(bullet)); for bullet in state.player_bullets.iter() { @@ -74,7 +68,7 @@ fn frame(canvas: &mut sdl2::render::WindowCanvas, state: &mut state::State, even bullet.draw(canvas, &textures.ebullet); } canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 0, 0)); - canvas.draw_rect(sdl2::rect::Rect::new(0, 0, (state.enemies[0].health as f64 / state::STARTING_HEALTH as f64 * constants::X_DIM as f64).round() as u32, 20)).unwrap(); + canvas.draw_rect(sdl2::rect::Rect::new(0, 0, (state.enemies[0].health() * constants::X_DIM as f64).round() as u32, 20)).unwrap(); state.count += 1; Result::Nothing } @@ -93,7 +87,7 @@ fn main() { let start = std::time::Instant::now(); let frame_duration = std::time::Duration::new(0, 1_000_000_000u32 / 60); let mut frame_count = 0; - let mut state = state::State::new(); + let mut state: state::State = state::State::new(); let mut won = false; let player_surface: sdl2::surface::Surface = sdl2::image::LoadSurface::from_file("assets/stallman.png").unwrap(); let enemy_surface: sdl2::surface::Surface = sdl2::image::LoadSurface::from_file("assets/gates.png").unwrap(); diff --git a/src/player.rs b/src/player.rs index 996e554..fa2abfa 100644 --- a/src/player.rs +++ b/src/player.rs @@ -112,13 +112,13 @@ impl state::Positioned for Player { fn y(&self) -> i32 { self.y } - fn radius(&self) -> u32 { + fn radius() -> u32 { 13 } } impl state::Drawable for Player { - fn center(&self) -> (u32, u32) { + fn center() -> (u32, u32) { (30, 19) } } diff --git a/src/state.rs b/src/state.rs index 5f622b5..d80c550 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,167 +5,89 @@ extern crate rand; use super::player; use super::constants; -pub struct EBullet { - x: i32, - y: i32, - radius: u32, +pub trait StraightBullet: Positioned + Drawable { + fn real_x(&self) -> f64; + fn real_y(&self) -> f64; + fn set_real_x(&mut self, real_x: f64); + fn set_real_y(&mut self, real_y: f64); + fn d_x(&self) -> f64; + fn d_y(&self) -> f64; +} + +pub struct PBullet { real_x: f64, real_y: f64, d_x: f64, d_y: f64, } -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, -} - -pub 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 } +impl StraightBullet for PBullet { + fn real_x(&self) -> f64 { + self.real_x } - - 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; + fn real_y(&self) -> f64 { + self.real_y } -} - -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) } + fn set_real_x(&mut self, real_x: f64) { + self.real_x = real_x; } - pub fn act(&mut self) { - self.b.act() + fn set_real_y(&mut self, real_y: f64) { + self.real_y = real_y; } - pub fn delete(&mut self) { - self.b.x = -10; + fn d_x(&self) -> f64 { + self.d_x + } + fn d_y(&self) -> f64 { + self.d_y } } -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.) +pub trait Bullet: Positioned + Drawable { + fn act(&mut self); + fn delete(&mut self); } -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 Bullet for T { + fn act(&mut self) { + let real_x = self.real_x(); + let d_x = self.d_x(); + self.set_real_x(real_x + d_x); + let real_y = self.real_y(); + let d_y = self.d_y(); + self.set_real_y(real_y + d_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 } + fn delete(&mut self) { + self.set_real_x(-10.); } - pub fn act(&mut self, count: u64) -> Vec { - let max_accel = 0.25; - let max_velocity = 5.; - let dx = self.target_x - self.x; - let dy = self.target_y - self.y; - /* If we've arrived (close and stopped) */ - if (dx*dx + dy*dy).sqrt() < max_velocity / max_accel && (self.v_x*self.v_x + self.v_y*self.v_y).sqrt() < 0.01 { - /* Then pick a new target */ - let (x, y) = random_position(); - self.target_x = x; - self.target_y = y; - } - - let dx = self.target_x - self.x; - let dy = self.target_y - self.y; - /* If we're close */ - if (dx*dx + dy*dy).sqrt() < max_velocity / max_accel { - /* Then slow down */ - let (x, y) = shorten(-self.v_x, -self.v_y, max_accel); - self.a_x = x; - self.a_y = y; - } else { - /* Otherwise, speed up */ - let (x, y) = shorten(dx, dy, max_accel); - self.a_x = x; - self.a_y = y; - } - - /* If we're going too fast */ - if (self.v_x*self.v_x + self.v_y*self.v_y).sqrt() > max_velocity { - /* Then don't go faster */ - 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() - } +impl PBullet { + pub fn new(x: i32, d_x: f64, y: i32, d_y: f64) -> PBullet { + PBullet { real_x: x as f64, real_y: y as f64, d_x: d_x, d_y: d_y } } - pub fn health(&self) -> i64 { - self.health + pub fn damage() -> f64 { + 0.01 } } -pub struct State { +pub trait Enemy: Drawable { + fn new() -> Self; + fn act(&mut self, count: u64) -> Vec; + fn health(&self) -> f64; + fn set_health(&mut self, health: f64); +} + +pub struct State { pub player: player::Player, - pub enemies: Vec, - pub bullets: Vec, + 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() } +impl, U> State { + pub fn new() -> State { + State { player: player::Player::new(), enemies: vec![T::new()], bullets: Vec::new(), count: 0, player_bullets: Vec::new() } } } @@ -178,17 +100,17 @@ fn dist(x1: i32, y1: i32, x2: i32, y2: i32) -> f64 { pub trait Positioned { fn x(&self) -> i32; fn y(&self) -> i32; - fn radius(&self) -> u32; + fn radius() -> u32; fn contains(&self, x: i32, y: i32) -> bool { let d = dist(self.x(), self.y(), x, y); - d <= self.radius() as f64 + d <= Self::radius() as f64 } fn overlaps(&self, other: &T) -> bool { let sx = self.x(); let ox = other.x(); let sy = self.y(); let oy = other.y(); - let threshold = self.radius() + other.radius(); + let threshold = Self::radius() + T::radius(); if (sx - ox).abs() as u32 >= threshold || (sy - oy).abs() as u32 >= threshold { return false; } @@ -202,68 +124,32 @@ pub trait Positioned { } } -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() + self.real_x.round() as i32 } fn y(&self) -> i32 { - self.b.y() + self.real_y.round() as i32 } - 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 + fn radius() -> u32 { + 10 } } pub trait Drawable : Positioned { - fn center(&self) -> (u32, u32); + fn center() -> (u32, u32); fn draw(&self, canvas: &mut sdl2::render::WindowCanvas, texture: &sdl2::render::Texture) { let tw = texture.query().width; let th = texture.query().height; - let (cx, cy) = self.center(); + let (cx, cy) = Self::center(); let x0 = self.x() - cx as i32; let y0 = self.y() - cy as i32; canvas.copy(&texture, None, Some(sdl2::rect::Rect::new(x0, y0, tw, th))).unwrap(); } } -impl Drawable for Enemy { - fn center(&self) -> (u32, u32) { - (0, 0) - } -} - -impl Drawable for EBullet { - fn center(&self) -> (u32, u32) { - (20, 20) - } -} - impl Drawable for PBullet { - fn center(&self) -> (u32, u32) { + fn center() -> (u32, u32) { (16, 22) } } -- cgit v1.3.1