diff options
| author | Raymond Hogenson <rhogenson@posteo.net> | 2018-08-18 00:25:29 -0400 |
|---|---|---|
| committer | Raymond Hogenson <rhogenson@posteo.net> | 2018-08-18 00:25:29 -0400 |
| commit | daa61a51eb97f868a56a52fc61b560832dde353b (patch) | |
| tree | 4da42fd8b8265b7e639275e613d4c7f78bf980e9 | |
| download | stallman-shooter-daa61a51eb97f868a56a52fc61b560832dde353b.tar.zst | |
Write a working bullet hell
I just fucked up the enemy movement, but hopefully I won't ever need
that old code. The new one is going to look really cool and sweet.
Anything with just modifying acceleration must look very cool and
smooth.
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/constants.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 124 | ||||
| -rw-r--r-- | src/player.rs | 100 | ||||
| -rw-r--r-- | src/state.rs | 237 |
6 files changed, 477 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..232e7c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +/target/ +**/*.rs.bk +result +default.nix +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8b4a64e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "bullet-hell" +version = "0.1.0" +authors = ["Raymond Hogenson <rhogenson@posteo.net>"] + +[dependencies] +sdl2 = "0.31.0" +rand = "0.5.5" diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..3868ce3 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,2 @@ +pub const X_DIM: u32 = 800; +pub const Y_DIM: u32 = 600; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..76b7a5b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,124 @@ +extern crate sdl2; + +mod state; +mod player; +mod constants; + +use state::Positioned; +use state::Drawable; + +enum Result { + Win, + Loss, + Nothing, +} + +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) -> Result { + let mut new_bullets = Vec::new(); + let count = state.count; + for enemy in state.enemies.iter_mut() { + new_bullets.append(&mut enemy.act(count)); + } + for bullet in state.bullets.iter_mut() { + bullet.act(); + } + for bullet in state.player_bullets.iter_mut() { + bullet.act(); + } + state.player.act(events); + if count % state.player.fire_rate as u64 == 0 { + state.player_bullets.push(state::PBullet::new(state.player.x, 0., state.player.y, -1.)); + } + if state.bullets.iter().any(|bullet| state.player.overlaps(bullet)) || new_bullets.iter().any(|bullet| state.player.overlaps(bullet)) { + return Result::Loss; + } + for enemy in state.enemies.iter_mut() { + for bullet in state.player_bullets.iter_mut() { + if enemy.overlaps(bullet) { + bullet.delete(); + enemy.health -= 1; + } + } + } + state.enemies.retain(|enemy| state::Enemy::health(enemy) > 0); + state.enemies.append(&mut spawn(count)); + if state.enemies.len() == 0 { + return Result::Win; + } + state.bullets.retain(|bullet| !state::EBullet::outside(bullet)); + state.bullets.append(&mut new_bullets); + state.player_bullets.retain(|bullet| !state::PBullet::outside(bullet)); + for bullet in state.player_bullets.iter() { + bullet.draw(canvas); + } + for enemy in state.enemies.iter() { + enemy.draw(canvas); + } + for bullet in state.bullets.iter() { + bullet.draw(canvas); + } + state.player.draw(canvas); + state.count += 1; + Result::Nothing +} + +fn main() { + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + + let window = video_subsystem.window("rust-sdl2 demo", constants::X_DIM, constants::Y_DIM).position_centered().build().unwrap(); + let mut canvas = window.into_canvas().build().unwrap(); + + canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); + canvas.clear(); + canvas.present(); + let mut event_pump = sdl_context.event_pump().unwrap(); + 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 won = false; + 'running: loop { + if won { + for event in event_pump.poll_iter() { + match event { + sdl2::event::Event::Quit {..} | sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => { + std::process::exit(0); + }, + _ => { }, + } + } + } else { + canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); + canvas.clear(); + match frame(&mut canvas, &mut state, &mut event_pump.poll_iter()) { + Result::Loss => { + break; + } + Result::Win => { + canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 204, 102)); + canvas.clear(); + canvas.present(); + won = true; + } + _ => { + canvas.present(); + } + } + } + let calc_time = frame_duration * frame_count; + match calc_time.checked_sub(start.elapsed()) { + Some(t) => std::thread::sleep(t), + None => (), + } + frame_count += 1; + } +} diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..97c98f6 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,100 @@ +extern crate sdl2; +extern crate std; + +use super::state; + +pub struct Player { + pub x: i32, + pub y: i32, + left_p: bool, + right_p: bool, + up_p: bool, + down_p: bool, + y_d: i32, + x_d: i32, + pub fire_rate: u32, +} + +impl Player { + pub fn new() -> Player { + Player { x: 400, y: 500, x_d: 0, y_d: 0, down_p: false, left_p: false, right_p: false, up_p: false, fire_rate: 30 } + } + + pub fn act(&mut self, events: &mut sdl2::event::EventPollIterator){ + for event in events { + match event { + sdl2::event::Event::Quit {..} | + sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => { + std::process::exit(0); + }, + sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Down), .. } => { + self.down_p = true; + self.y_d = -1; + } + sdl2::event::Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::Down), .. } => { + self.down_p = false; + } + sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Up), .. } => { + self.up_p = true; + self.y_d = 1; + } + sdl2::event::Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::Up), .. } => { + self.up_p = false; + } + sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Left), .. } => { + self.left_p = true; + self.x_d = -1; + } + sdl2::event::Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::Left), .. } => { + self.left_p = false; + } + sdl2::event::Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Right), .. } => { + self.right_p = true; + self.x_d = 1; + } + sdl2::event::Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::Right), .. } => { + self.right_p = false; + } + _ => { }, + } + } + if self.left_p { + if !self.right_p { + self.x_d = -1; + } + } else if self.right_p { + self.x_d = 1; + } else { + self.x_d = 0; + } + if self.up_p { + if !self.down_p { + self.y_d = -1; + } + } else if self.down_p { + self.y_d = 1; + } else { + self.y_d = 0; + } + self.x += self.x_d; + self.y += self.y_d; + } +} + +impl state::Positioned for Player { + fn x(&self) -> i32 { + self.x + } + fn y(&self) -> i32 { + self.y + } + fn radius(&self) -> u32 { + 5 + } +} + +impl state::Drawable for Player { + fn color(&self) -> sdl2::pixels::Color { + sdl2::pixels::Color::RGB(0, 0, 0) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..74ac73b --- /dev/null +++ b/src/state.rs @@ -0,0 +1,237 @@ +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, +} + +pub struct Enemy { + pub x: i32, + pub y: i32, + action_count: u64, + 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::<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, dx, enemy.y, dy) +} + +fn random_position() -> (f64, f64) { + (rand::random() * (constants::X_DIM - 200) as f64 + 100., rand::random() * 200) +} + +impl Enemy { + pub fn new() -> Enemy { + Enemy { x: 200, y: 40, action_count: 1, v_x: 0., v_y: 0., a_x: 0., a_y: 0., radius: 30, health: STARTING_HEALTH, target_x: 200, target_y: 400 } + } + pub fn act(&mut self, count: u64) -> Vec<EBullet> { + let dx = self.target_x - self.x; + let dy = self.target_y - self.y; + /* Made it; pick a new destination */ + if f64::sqrt(dx*dx + dy*dy) < 10 { + let (self.target_x, self.target_y) = random_position(); + } + let vec_x = self.target_x - self.real_x; + let vec_y = self.target_y - self.real_y; + let len = f64::sqrt(vec_x*vec_x + vec_y*vec_y); + self.a_x = vec_x / len / 2.; + self.a_y = vec_y / len / 2.; + + self.v_x += self.a_x; + self.x = (self.x as f64 + self.v_x).round() as i32; + self.v_y += self.a_y; + self.y = (self.y as f64 + self.v_y).round() as i32; + 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<Enemy>, + pub bullets: Vec<EBullet>, + 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() } + } +} + +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<T: Positioned>(&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 + } + fn y(&self) -> i32 { + self.y + } + 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) + } +} |
