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