1use std::collections::HashMap;
4use sdl2::{
5 event::Event,
6 keyboard::Scancode,
7 rect::Rect,
8 render::{
9 Canvas, TextureCreator
10 }, video::{
11 Window, WindowContext
12 }
13};
14use ycraft::{
15 collision::CollisionShape,
16 obj::{
17 ControlObjectBehavior, Frame, GameObjectBehavior, GameObjectState, Sprite
18 }, res::{
19 Font, Image, Sound
20 }, room::Room
21};
22
23const MOVE_SPD: f64 = 512.0;
24const JUMP_SPD: f64 = 1500.0;
25const ACC: f64 = 50.0;
26const GRAVITY: f64 = 4096.0;
27const EXTRA_GRAV: f64 = 13000.0;
28const BRICK_POS: [(f64, f64); 6] = [
29 (96.0, 900.0),
30 (160.0, 1000.0),
31 (224.0, 1000.0),
32 (288.0, 1000.0),
33 (352.0, 1000.0),
34 (416.0, 1000.0)
35];
36
37#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
38pub enum Img {
39 Brick,
40 Character
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
44pub enum Snd {
45 Music,
46 Jump
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
50pub enum Fnt {
51 Geist
52}
53
54#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
55pub enum Spr {
56 Brick,
57 Idle,
58 Walk
59}
60
61#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
62pub enum Rm {
63 Room0
64}
65
66#[derive(Clone, Copy)]
67pub enum Data {
68 Brick(usize),
69 Player
70}
71
72#[derive(Clone)]
73struct Brick {
74 state: GameObjectState<Img, Spr, Data>,
75 def_pos: (f64, f64),
76 should_die: bool
77}
78
79impl Brick {
80 pub fn new(def_pos: (f64, f64), id: usize) -> Self {
81 Self {
82 state: GameObjectState {
83 name: "brick".to_string(),
84 pos: def_pos,
85 collider: CollisionShape::Rect { center: (0, 0), size: (64, 64) },
86 cur_spr: Spr::Brick,
87 sprs: HashMap::from([(
88 Spr::Brick,
89 Sprite::new(
90 vec![ Frame::new(Img::Brick, Rect::new(0, 0, 32, 32), (64, 64)) ],
91 0.0, (16, 16)
92 )
93 )]), custom: Data::Brick(id)
94 }, def_pos,
95 should_die: false
96 }
97 }
98}
99
100impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for Brick {
101 fn state(&self) -> GameObjectState<Img, Spr, Data> {
102 self.state.clone()
103 }
104
105 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
106 self.state = new_state.clone();
107 }
108
109 fn handle_sdl_event(&mut self, event: &Event) {
110 match event {
111 Event::KeyUp { scancode, .. } if *scancode == Some(Scancode::Escape) => {
112 self.should_die = true;
113 }, _ => {}
114 }
115 }
116
117 fn should_remove(&self) -> bool {
118 self.should_die
119 }
120
121 fn on_reset(&mut self) -> bool {
122 self.state.pos = self.def_pos;
123 self.should_die = false;
124 false
125 }
126}
127
128#[derive(Clone)]
129struct Player {
130 state: GameObjectState<Img, Spr, Data>,
131 def_pos: (f64, f64),
132 vel: (f64, f64),
133 right: bool,
134 left: bool,
135 grounded: bool,
136 extra_grav: bool,
137 facing_right: bool,
138 play_jump_sound: bool,
139 should_reset: bool
140}
141
142impl Player {
143 pub fn new(def_pos: (f64, f64)) -> Self {
144 Self {
145 state: GameObjectState {
146 name: "player".to_string(),
147 pos: def_pos,
148 collider: CollisionShape::Rect { center: (0, 0), size: (64, 64) },
149 cur_spr: Spr::Idle,
150 sprs: HashMap::from([
151 (
152 Spr::Idle,
153 Sprite::new(
154 vec![Frame::new(Img::Character, Rect::new(0, 0, 32, 32), (128, 128))],
155 0.0, (16, 16)
156 )
157 ), (
158 Spr::Walk,
159 Sprite::new(
160 vec![
161 Frame::new(Img::Character, Rect::new(0, 0, 32, 32), (128, 128)),
162 Frame::new(Img::Character, Rect::new(32, 0, 32, 32), (128, 128))
163 ], 12.0, (16, 16)
164 )
165 )
166 ]), custom: Data::Player
167 }, def_pos,
168 vel: (0.0, 0.0),
169 right: false,
170 left: false,
171 grounded: false,
172 extra_grav: false,
173 facing_right: true,
174 play_jump_sound: false,
175 should_reset: false
176 }
177 }
178}
179
180impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for Player {
181 fn state(&self) -> GameObjectState<Img, Spr, Data> {
182 self.state.clone()
183 }
184
185 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
186 self.state = new_state.clone();
187 }
188
189 fn on_reset(&mut self) -> bool {
190 self.state.pos = self.def_pos;
191 self.right = false;
192 self.left = false;
193 self.grounded = false;
194 self.state.cur_spr = Spr::Idle;
195 self.extra_grav = false;
196 self.facing_right = true;
197 self.play_jump_sound = false;
198 self.should_reset = false;
199 false
200 }
201
202 fn handle_sdl_event(&mut self, event: &Event) {
203 match event {
204 Event::KeyDown { scancode, .. } if *scancode == Some(Scancode::Up) => {
205 if self.grounded {
206 self.state.pos.1 -= 1.0;
207 self.vel.1 = -JUMP_SPD;
208 self.play_jump_sound = true;
209 }
210 }, Event::KeyDown { scancode, .. } if *scancode == Some(Scancode::Left) => {
211 self.left = true;
212 }, Event::KeyDown { scancode, .. } if *scancode == Some(Scancode::Right) => {
213 self.right = true;
214 }, Event::KeyUp { scancode, .. } if *scancode == Some(Scancode::Up) => {
215 if self.vel.1 < -0.1 {
216 self.extra_grav = true;
217 }
218 }, Event::KeyUp { scancode, .. } if *scancode == Some(Scancode::Left) => {
219 self.left = false;
220 }, Event::KeyUp { scancode, .. } if *scancode == Some(Scancode::Right) => {
221 self.right = false;
222 }, Event::KeyUp { scancode, .. } if *scancode == Some(Scancode::R) => {
223 self.should_reset = true;
224 }, _ => {}
225 }
226 }
227
228 fn update(
229 &mut self, delta: f64,
230 _ctl_objs: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
231 others: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
232 Option<Rm>, Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
233 ) {
234 if self.should_reset {
235 let mut existing_bricks = Vec::new();
239 for obj in others.iter() {
240 if let Data::Brick(id) = obj.state().custom {
241 existing_bricks.push(id);
242 }
243 }
244 let to_respawn = (0..=5).into_iter()
245 .map(|id| if !existing_bricks.contains(&id) {
246 Some(
247 Box::new(Brick::new(BRICK_POS[id], id))
248 as Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>
249 )
250 } else {
251 None
252 }).filter(|brick| brick.is_some())
253 .map(|brick| brick.unwrap())
254 .collect::<Vec<Box<_>>>();
255 return (Some(Rm::Room0), to_respawn);
256 }
257
258 self.state.pos.0 += self.vel.0 * delta;
259 self.state.pos.1 += self.vel.1 * delta;
260
261 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
263 spr.scale = (0.5, 0.5);
264 spr.flip = (!self.facing_right, false);
265 }
266 if !self.grounded && self.state.cur_spr != Spr::Idle {
267 self.state.cur_spr = Spr::Idle;
268 } else if self.grounded {
269 if self.vel.0.abs() < MOVE_SPD * 0.8 && self.state.cur_spr != Spr::Idle {
270 self.state.cur_spr = Spr::Idle;
271 } else if self.vel.0.abs() >= MOVE_SPD * 0.8 && self.state.cur_spr != Spr::Walk {
272 self.state.cur_spr = Spr::Walk;
273 self.facing_right = self.vel.0 > 0.1;
274 }
275 }
276
277 let hor = (if self.right { 1.0 } else { 0.0 }) + (if self.left { -1.0 } else { 0.0 });
279 self.vel.0 = ycraft::util::lerp(self.vel.0, hor * MOVE_SPD, ACC * delta);
280
281 if let CollisionShape::Rect { center, size } = self.state.collider {
282 let gnd_check = CollisionShape::Rect {
283 center: (
284 center.0 + self.state.pos.0 as i32,
285 center.1 + (self.state.pos.1 + self.vel.1 * delta) as i32 + 1
286 ), size
287 };
288 self.grounded = false;
289 for other in others.iter() {
290 if self.state.pos.1 > other.state().pos.1 - size.1 as f64 * 0.8 {
291 continue;
292 }
293 if let Data::Brick(_) = other.state().custom {
294 let mut other_col = other.state().collider.clone();
295 if let CollisionShape::Rect { center: ref mut other_center, .. } = other_col {
296 other_center.0 += other.state().pos.0 as i32;
297 other_center.1 += other.state().pos.1 as i32;
298 };
299 if gnd_check.collides_with(&other_col) {
300 if let CollisionShape::Rect { center, size } = other_col {
301 self.grounded = true;
302 self.vel.1 = 0.0;
303 self.state.pos.1 = (center.1 - size.1 as i32 - 1) as f64;
304 }
305 }
306 }
307 }
308 }
309 if self.grounded || self.vel.1 > 0.1 {
310 self.extra_grav = false;
311 }
312 if self.extra_grav {
313 self.vel.1 += EXTRA_GRAV * delta;
314 } else {
315 self.vel.1 += GRAVITY * delta;
316 }
317
318 (None, vec![])
319 }
320
321 fn on_collision(
322 &mut self,
323 other: &Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>) {
324 if let Data::Brick(_) = other.state().custom {
325 if (other.state().pos.0 <= self.state.pos.0 && self.vel.0 < -0.1)
326 || (other.state().pos.0 >= self.state.pos.0 && self.vel.0 > 0.1) {
327 self.state.pos.0 -= if self.vel.0 > 0.1 { 1.0 } else { -1.0 };
328 self.vel.0 = 0.0;
329 }
330 }
331 }
332
333 fn render(
334 &mut self, cnv: &mut Canvas<Window>,
335 imgs: &HashMap<Img, Image>, snds: &HashMap<Snd, Sound>,
336 _fonts: &HashMap<Fnt, Font>, _creator: &TextureCreator<WindowContext>,
337 elapsed: f64) -> Result<(), String> {
338 if self.play_jump_sound {
339 snds[&Snd::Jump].play()?;
340 self.play_jump_sound = false;
341 }
342
343 if !Sound::is_music_playing() {
344 snds[&Snd::Music].play()?;
345 }
346
347 let mut state = self.state().clone();
349 let GameObjectState { ref mut sprs, ref mut cur_spr, pos, .. } = state;
350 if let Some(spr) = sprs.get_mut(cur_spr) {
351 spr.update(elapsed);
352 spr.render(cnv, imgs, (pos.0 as i32, pos.1 as i32))?;
353 }
354 self.set_state(&state);
355 Ok(())
356 }
357}
358
359pub fn room0() -> Room<Img, Snd, Fnt, Spr, Rm, Data> {
360 Room::new(
361 vec![
362 Box::new(Player::new((256.0, 900.0))),
363 Box::new(Brick::new(BRICK_POS[0], 0)),
364 Box::new(Brick::new(BRICK_POS[1], 1)),
365 Box::new(Brick::new(BRICK_POS[2], 2)),
366 Box::new(Brick::new(BRICK_POS[3], 3)),
367 Box::new(Brick::new(BRICK_POS[4], 4)),
368 Box::new(Brick::new(BRICK_POS[5], 5))
369 ], false
370 )
371}
372