summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/constants.rs2
-rw-r--r--src/main.rs124
-rw-r--r--src/player.rs100
-rw-r--r--src/state.rs237
4 files changed, 463 insertions, 0 deletions
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)
+ }
+}