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