diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gates.rs | 185 | ||||
| -rw-r--r-- | src/main.rs | 24 | ||||
| -rw-r--r-- | src/player.rs | 4 | ||||
| -rw-r--r-- | src/state.rs | 246 |
4 files changed, 262 insertions, 197 deletions
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::<f64>() * (constants::X_DIM - 200) as f64 + 100., rand::random::<f64>() * 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<T: state::Enemy<GatesBullet>>(enemy: &T) -> GatesBullet { + let multiplier = 2. + 2.*(1. - enemy.health()); + let theta: f64 = rand::random::<f64>()*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<GatesBullet> 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<GatesBullet> { + 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<state::Enemy> { - 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<T: state::Enemy<U>, U: state::Bullet>(canvas: &mut sdl2::render::WindowCanvas, state: &mut state::State<T, U>, 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<gates::Gates, gates::GatesBullet> = 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::<f64>()*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::<f64>() * (constants::X_DIM - 200) as f64 + 100., rand::random::<f64>() * 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<T: StraightBullet> 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<EBullet> { - 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<T>: Drawable { + fn new() -> Self; + fn act(&mut self, count: u64) -> Vec<T>; + fn health(&self) -> f64; + fn set_health(&mut self, health: f64); +} + +pub struct State<T, U> { pub player: player::Player, - pub enemies: Vec<Enemy>, - pub bullets: Vec<EBullet>, + pub enemies: Vec<T>, + pub bullets: Vec<U>, pub player_bullets: Vec<PBullet>, 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<T: Enemy<U>, U> State<T, U> { + pub fn new() -> State<T, U> { + 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<T: Positioned>(&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) } } |
