#[macro_use] extern crate gate; gate_header!(); use gate::{App, AppContext, AppInfo, KeyCode}; use gate::renderer::{Renderer, Affine}; extern crate rand; use rand::distributions::IndependentSample; use rand::Isaac64Rng; use std::f64::consts::PI; use std::f64::consts::FRAC_PI_2; mod asset_id { include!(concat!(env!("OUT_DIR"), "/asset_id.rs")); } use asset_id::*; mod geometry; use geometry::*; mod hitbox; use hitbox::*; mod entities; use entities::bug::Bug; use entities::home::Home; struct BugBasherGame { rng: Isaac64Rng, bugs: Vec, home: Home, points: i64, lives: i64, game_over: bool, time_to_next_bug: f64, total_time: f64, camera: Affine, cursor: SpriteId } impl App for BugBasherGame { fn start(&mut self, _ctx: &mut AppContext) { } fn advance(&mut self, seconds: f64, _ctx: &mut AppContext) { if !self.game_over { self.bugs.retain(|b| b.alive); for bug in &mut self.bugs { bug.advance(seconds); if Hitbox::intersect(&self.home.hitbox(), &bug.hitbox()) { bug.alive = false; self.lives -= 1; } } self.home.advance(seconds); if self.lives <= 0 { self.game_over = true; } self.time_to_next_bug -= seconds; self.total_time += seconds; if self.time_to_next_bug <= 0. { let mean = if self.total_time < 30. { f64::max(4. - (self.total_time * 0.2), 1.) } else if self.total_time < 60. { f64::max(1. - ((self.total_time - 30.) * 0.05), 0.5) } else if self.total_time < 90. { f64::max(0.5 - ((self.total_time - 60.) * 0.05), 0.3) } else { f64::max(0.3 - ((self.total_time - 90.) * 0.005), 0.2) }; let sd = mean / 3.; let time_dist = rand::distributions::Normal::new(mean, sd); self.time_to_next_bug = time_dist.ind_sample(&mut self.rng); let angle_dist = rand::distributions::Range::new(0., 2.*PI); let angle = angle_dist.ind_sample(&mut self.rng); self.bugs.push(Bug::new( angle.cos()*1000., angle.sin()*1000. )); } } } fn key_down(&mut self, key: KeyCode, ctx: &mut AppContext) { let (x, y) = self.camera.invert_translate().apply_f64(ctx.cursor()); let cursor_hitbox = Hitbox::Circle(CircleHitbox { pos: Vec2d::new(x, y), radius: 10. }); match key { KeyCode::MouseLeft => { let mut hit = false; for bug in self.bugs.iter_mut().filter(|bug| Hitbox::intersect(&cursor_hitbox, &bug.hitbox())) { if !self.game_over && bug.alive { self.points += 1; } bug.alive = false; hit = true; } if hit { self.rotate_cursor(); } }, KeyCode::Return => { self.reset(); }, _ => {} } } fn render(&mut self, renderer: &mut Renderer, ctx: &AppContext) { let (app_width, app_height) = ctx.dims(); let (cursor_x, cursor_y) = ctx.cursor(); self.camera = Affine::translate(app_width/2., app_height/2.); renderer.clear((255,255,255)); { let points_str = format!("{}", self.points); let lives_str = format!("{}", self.lives); BugBasherGame::print_string(renderer, &points_str, &Alignment::Left, 50., app_height - 50.); BugBasherGame::print_string(renderer, &lives_str, &Alignment::Right, app_width - 50., app_height - 50.); } { let mut renderer = renderer.sprite_mode(); renderer.draw( &self.camera.post_translate(self.home.pos.x, self.home.pos.y), self.home.sprite ); for bug in &self.bugs { let affine = if bug.rotation <= FRAC_PI_2 && bug.rotation >= -FRAC_PI_2 { self.camera.post_translate(bug.pos.x, bug.pos.y).pre_rotate(bug.rotation) } else { self.camera.post_translate(bug.pos.x, bug.pos.y).pre_rotate(bug.rotation).pre_scale_axes(1., -1.) }; renderer.draw( &affine, SpriteId::Pom ); } } if self.game_over { { let mut renderer = renderer.sprite_mode(); renderer.draw( &self.camera, SpriteId::Gameover ); } { let points_str = format!("{}", self.points); BugBasherGame::print_string(renderer, &points_str, &Alignment::Center, app_width/2., app_height/2.-25.); } } { let mut renderer = renderer.sprite_mode(); renderer.draw( &Affine::translate(cursor_x, cursor_y), self.cursor ); } } } enum Alignment { Left, Right, Center } impl BugBasherGame { fn new() -> BugBasherGame { let mut game = BugBasherGame { rng: Isaac64Rng::new_unseeded(), home: Home::new(0., 0.), bugs: Vec::with_capacity(1000), points: 0, lives: 0, game_over: true, time_to_next_bug: 0., total_time: 0., camera: Affine::id(), cursor: SpriteId::Cursor1 }; game.reset(); game } fn reset(&mut self) { self.bugs = Vec::with_capacity(1000); self.points = 0; self.lives = 3; self.game_over = false; self.time_to_next_bug = 0.; self.total_time = 0.; } fn print_string(renderer: &mut Renderer, string: &str, alignment: &Alignment, x: f64, y: f64) { let letter_spacing = 45.; let left = match alignment { Alignment::Left => x, Alignment::Right => x - string.len() as f64 * letter_spacing, Alignment::Center => x - string.len() as f64 * letter_spacing / 2. }; let mut renderer = renderer.sprite_mode(); for (i, c) in string.chars().enumerate() { let affine = Affine::translate(left + i as f64 * letter_spacing, y); let tile = match c { '-' => SpriteId::NumberFontR0C0, '0' => SpriteId::NumberFontR0C1, '1' => SpriteId::NumberFontR0C2, '2' => SpriteId::NumberFontR0C3, '3' => SpriteId::NumberFontR0C4, '4' => SpriteId::NumberFontR0C5, '5' => SpriteId::NumberFontR0C6, '6' => SpriteId::NumberFontR0C7, '7' => SpriteId::NumberFontR0C8, '8' => SpriteId::NumberFontR0C9, '9' => SpriteId::NumberFontR0C10, _ => SpriteId::NumberFontR0C0, }; renderer.draw(&affine, tile); }; } fn rotate_cursor(&mut self) { self.cursor = match self.cursor { SpriteId::Cursor1 => SpriteId::Cursor2, SpriteId::Cursor2 => SpriteId::Cursor3, _ => SpriteId::Cursor1 } } } fn main() { let info = AppInfo::with_max_dims(2000., 1000.) .tile_width(16) .title("Nap Attack"); gate::run(info, BugBasherGame::new()); }