98dd39f52dd964aff3e3359e6d4a8c049a45ee32
[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 }
37
38 impl App<AssetId> for BugBasherGame {
39     fn start(&mut self, _audio: &mut Audio<AssetId>) {
40     }
41
42     fn advance(&mut self, seconds: f64, _audio: &mut Audio<AssetId>) -> bool {
43         if !self.game_over {
44             self.bugs.retain(|b| b.alive);
45             for bug in self.bugs.iter_mut() {
46                 bug.advance(seconds);
47                 if self.home.touches_circle(bug) {
48                     bug.alive = false;
49                     self.lives -= 1;
50                 }
51             }
52             if self.lives <= 0 {
53                 self.game_over = true;
54             }
55         
56             self.time_to_next_bug -= seconds;
57             self.total_time += seconds;
58             
59             if self.time_to_next_bug <= 0. {
60                 let mean = f64::max(4. - (self.total_time as f64 * 0.25), 0.5);
61                 let sd = mean / 3.;
62                 let time_dist = rand::distributions::Normal::new(mean, sd);
63                 self.time_to_next_bug = time_dist.ind_sample(&mut self.rng);
64
65                 let angle_dist = rand::distributions::Range::new(0., 2.*PI);
66                 let angle = angle_dist.ind_sample(&mut self.rng);
67                 self.bugs.push(Bug::new(
68                     angle.cos()*1000.,
69                     angle.sin()*1000.,
70                     angle + PI
71                 ));
72                 
73             }
74         }
75         
76         true
77     }
78
79     fn input(&mut self, evt: InputEvent, _audio: &mut Audio<AssetId>) -> bool {
80         match evt {
81             InputEvent::MousePressed(MouseButton::Left, x, y) => {
82                 for bug in self.bugs.iter_mut().filter(|bug| bug.touches_point(Vec2d { x, y })) {
83                     if !self.game_over && bug.alive == true {
84                         self.points += 1;
85                     }
86
87                     bug.alive = false;
88                 }
89             },
90             InputEvent::KeyPressed(KeyCode::Return) => {
91                 self.reset();
92             },
93             _ => {}
94         }
95         true
96     }
97
98     fn render(&mut self, renderer: &mut Renderer<AssetId>) {
99         let (app_width, app_height) = (renderer.app_width(), renderer.app_height());
100         renderer.clear((255,255,255));
101         {
102             let points_str = format!("{}", self.points);
103             let lives_str = format!("{}", self.lives);
104             BugBasherGame::print_string(renderer, &points_str, Alignment::Left, - app_width / 2. + 50., app_height / 2. - 50.);
105             BugBasherGame::print_string(renderer, &lives_str, Alignment::Right, app_width / 2. - 50., app_height / 2. - 50.);
106         }
107         {
108             let mut renderer = renderer.sprite_mode();
109             renderer.draw(
110                 &Affine::translate(self.home.pos.x, self.home.pos.y),
111                 SpriteId::Home
112             );
113             for bug in &self.bugs {
114                 renderer.draw(
115                     &Affine::translate(bug.pos.x, bug.pos.y).pre_rotate(bug.rotation),
116                     SpriteId::Bug
117                 );
118             }
119         }
120         
121         if self.game_over {
122             {
123                 let mut renderer = renderer.sprite_mode();
124                 renderer.draw(
125                     &Affine::translate(0.,0.),
126                     SpriteId::Gameover
127                 );
128             }
129             {
130                 let points_str = format!("{}", self.points);
131                 BugBasherGame::print_string(renderer, &points_str, Alignment::Center, 0., -25.);
132             }
133         }
134     }
135 }
136
137 enum Alignment {
138     Left,
139     Right,
140     Center
141 }
142
143 impl BugBasherGame {
144     fn new() -> BugBasherGame {
145         let mut game = BugBasherGame {
146             rng: Isaac64Rng::new_unseeded(),
147             home: Home::new(0., 0.),
148             bugs: Vec::with_capacity(1000),
149             points: 0,
150             lives: 0,
151             game_over: true,
152             time_to_next_bug: 0.,
153             total_time: 0.
154         };
155         game.reset();
156         game
157     }
158
159     fn reset(&mut self) {
160         self.bugs = Vec::with_capacity(1000);
161         self.points = 0;
162         self.lives = 3;
163         self.game_over = false;
164         self.time_to_next_bug = 0.;
165         self.total_time = 0.;
166     }
167
168     fn print_string(renderer: &mut Renderer<AssetId>, string: &str, alignment: Alignment, x: f64, y: f64) {
169         let letter_spacing = 45.;
170         let left = match alignment {
171             Alignment::Left => x,
172             Alignment::Right => x - string.len() as f64 * letter_spacing,
173             Alignment::Center => x - string.len() as f64 * letter_spacing / 2.
174         };
175         
176         let mut renderer = renderer.tiled_mode(-left, -y);
177
178         for (i, c) in string.chars().enumerate() {
179             let affine = Affine::translate(i as f64 * letter_spacing, 0.);
180             let tile = match c {
181                 '-' => TileId::NumberFontR0C0,
182                 '0' => TileId::NumberFontR0C1,
183                 '1' => TileId::NumberFontR0C2,
184                 '2' => TileId::NumberFontR0C3,
185                 '3' => TileId::NumberFontR0C4,
186                 '4' => TileId::NumberFontR0C5,
187                 '5' => TileId::NumberFontR0C6,
188                 '6' => TileId::NumberFontR0C7,
189                 '7' => TileId::NumberFontR0C8,
190                 '8' => TileId::NumberFontR0C9,
191                 '9' => TileId::NumberFontR0C10,
192                 _ => TileId::NumberFontR0C0,
193             };
194             renderer.draw(&affine, tile);
195         };
196     }
197 }
198
199 fn main() {
200     let info = AppInfo::with_app_height(1000.).title("Bug Basher").build();
201     gate::run(info, BugBasherGame::new());
202 }