db96656e26c16f5f0f59b84820781bca38c73af7
[bug-basher.git] / src / main.rs
1 extern crate gate;
2
3 use gate::{App, AppContext, AppInfo, KeyCode};
4 use gate::renderer::{Renderer, Affine};
5
6 extern crate rand;
7
8 use rand::distributions::IndependentSample;
9 use rand::Isaac64Rng;
10 use std::f64::consts::PI;
11 use std::f64::consts::FRAC_PI_2;
12
13 mod asset_id { include!(concat!(env!("OUT_DIR"), "/asset_id.rs")); }
14 use asset_id::*;
15
16 mod geometry;
17 use geometry::*;
18     
19 mod hitbox;
20 use hitbox::*;
21
22 mod entities;
23 use entities::bug::Bug;
24 use entities::home::Home;
25
26 struct BugBasherGame {
27     rng: Isaac64Rng,
28     bugs: Vec<Bug>,
29     home: Home,
30     points: i64,
31     lives: i64,
32     game_over: bool,
33     time_to_next_bug: f64,
34     total_time: f64,
35     cursor_pos: Vec2d
36 }
37
38 impl App<AssetId> for BugBasherGame {
39     fn start(&mut self, _ctx: &mut AppContext<AssetId>) {
40     }
41
42     fn advance(&mut self, seconds: f64, ctx: &mut AppContext<AssetId>) {
43         let (x, y) = ctx.cursor();
44         self.cursor_pos = Vec2d::new(x, y);
45         
46         if !self.game_over {
47             self.bugs.retain(|b| b.alive);
48             for bug in self.bugs.iter_mut() {
49                 bug.advance(seconds);
50                 if self.home.touches_circle(bug) {
51                     bug.alive = false;
52                     self.lives -= 1;
53                 }
54             }
55             self.home.advance(seconds);
56             if self.lives <= 0 {
57                 self.game_over = true;
58             }
59         
60             self.time_to_next_bug -= seconds;
61             self.total_time += seconds;
62             
63             if self.time_to_next_bug <= 0. {
64                 let mean = if self.total_time < 30. {
65                     f64::max(4. - (self.total_time as f64 * 0.2), 1.)
66                 } else if self.total_time < 60. {
67                     f64::max(1. - ((self.total_time as f64 - 30.) * 0.05), 0.5)
68                 } else if self.total_time < 90. {
69                     f64::max(0.5 - ((self.total_time as f64 - 60.) * 0.05), 0.3)
70                 } else {
71                     f64::max(0.3 - ((self.total_time as f64 - 90.) * 0.005), 0.2)
72                 };
73                 
74                 let sd = mean / 3.;
75                 let time_dist = rand::distributions::Normal::new(mean, sd);
76                 self.time_to_next_bug = time_dist.ind_sample(&mut self.rng);
77
78                 let angle_dist = rand::distributions::Range::new(0., 2.*PI);
79                 let angle = angle_dist.ind_sample(&mut self.rng);
80                 self.bugs.push(Bug::new(
81                     angle.cos()*1000.,
82                     angle.sin()*1000.
83                 ));
84                 
85             }
86         }
87     }
88
89     fn key_down(&mut self, key: KeyCode, ctx: &mut AppContext<AssetId>) {
90         let (x, y) = ctx.cursor();
91         match key {
92             KeyCode::MouseLeft => {
93                 for bug in self.bugs.iter_mut().filter(|bug| bug.touches_point(Vec2d { x, y })) {
94                     if !self.game_over && bug.alive == true {
95                         self.points += 1;
96                     }
97
98                     bug.alive = false;
99                 }
100             },
101             KeyCode::Return => {
102                 self.reset();
103             },
104             _ => {}
105         }
106     }
107
108     fn render(&mut self, renderer: &mut Renderer<AssetId>, ctx: &AppContext<AssetId>) {
109         let (app_width, app_height) = ctx.dims();
110         renderer.clear((255,255,255));
111         {
112             let points_str = format!("{}", self.points);
113             let lives_str = format!("{}", self.lives);
114             BugBasherGame::print_string(renderer, &points_str, Alignment::Left, - app_width / 2. + 50., app_height / 2. - 50.);
115             BugBasherGame::print_string(renderer, &lives_str, Alignment::Right, app_width / 2. - 50., app_height / 2. - 50.);
116         }
117         {
118             let mut renderer = renderer.sprite_mode();
119             renderer.draw(
120                 &Affine::translate(self.home.pos.x, self.home.pos.y),
121                 self.home.sprite
122             );
123             for bug in &self.bugs {
124                 let affine = if bug.rotation <= FRAC_PI_2 && bug.rotation >= -FRAC_PI_2 {
125                     Affine::translate(bug.pos.x, bug.pos.y).pre_rotate(bug.rotation)
126                 } else {
127                     Affine::translate(bug.pos.x, bug.pos.y).pre_rotate(bug.rotation).pre_scale_axes(1., -1.)
128                 };
129                 
130                 renderer.draw(
131                     &affine,
132                     SpriteId::Pom
133                 );
134             }
135         }
136         
137         if self.game_over {
138             {
139                 let mut renderer = renderer.sprite_mode();
140                 renderer.draw(
141                     &Affine::translate(0.,0.),
142                     SpriteId::Gameover
143                 );
144             }
145             {
146                 let points_str = format!("{}", self.points);
147                 BugBasherGame::print_string(renderer, &points_str, Alignment::Center, 0., -25.);
148             }
149         }
150
151         {
152             let mut renderer = renderer.sprite_mode();
153             renderer.draw(
154                 &Affine::translate(self.cursor_pos.x, self.cursor_pos.y),
155                 SpriteId::Cursor
156             );
157         }
158     }
159 }
160
161 enum Alignment {
162     Left,
163     Right,
164     Center
165 }
166
167 impl BugBasherGame {
168     fn new() -> BugBasherGame {
169         let mut game = BugBasherGame {
170             rng: Isaac64Rng::new_unseeded(),
171             home: Home::new(0., 0.),
172             bugs: Vec::with_capacity(1000),
173             points: 0,
174             lives: 0,
175             game_over: true,
176             time_to_next_bug: 0.,
177             total_time: 0.,
178             cursor_pos: Vec2d::new(0.,0.)
179         };
180         game.reset();
181         game
182     }
183
184     fn reset(&mut self) {
185         self.bugs = Vec::with_capacity(1000);
186         self.points = 0;
187         self.lives = 3;
188         self.game_over = false;
189         self.time_to_next_bug = 0.;
190         self.total_time = 0.;
191     }
192
193     fn print_string(renderer: &mut Renderer<AssetId>, string: &str, alignment: Alignment, x: f64, y: f64) {
194         let letter_spacing = 45.;
195         let left = match alignment {
196             Alignment::Left => x,
197             Alignment::Right => x - string.len() as f64 * letter_spacing,
198             Alignment::Center => x - string.len() as f64 * letter_spacing / 2.
199         };
200         
201         let mut renderer = renderer.sprite_mode();
202         for (i, c) in string.chars().enumerate() {
203             let affine = Affine::translate(i as f64 * letter_spacing - left, -y);
204             let tile = match c {
205                 '-' => SpriteId::NumberFontR0C0,
206                 '0' => SpriteId::NumberFontR0C1,
207                 '1' => SpriteId::NumberFontR0C2,
208                 '2' => SpriteId::NumberFontR0C3,
209                 '3' => SpriteId::NumberFontR0C4,
210                 '4' => SpriteId::NumberFontR0C5,
211                 '5' => SpriteId::NumberFontR0C6,
212                 '6' => SpriteId::NumberFontR0C7,
213                 '7' => SpriteId::NumberFontR0C8,
214                 '8' => SpriteId::NumberFontR0C9,
215                 '9' => SpriteId::NumberFontR0C10,
216                 _ => SpriteId::NumberFontR0C0,
217             };
218             renderer.draw(&affine, tile);
219         };
220     }
221 }
222
223 fn main() {
224     let info = AppInfo::with_max_dims(2000., 1000.)
225         .min_dims(500., 1000.)
226         .tile_width(16)
227         .title("Nap Attack");
228     gate::run(info, BugBasherGame::new());
229 }