1use std::collections::HashMap;
4use rand::Rng;
5use sdl2::{
6 event::Event,
7 keyboard::Scancode,
8 rect::Rect, render::{Canvas, TextureCreator}, video::{Window, WindowContext}
9};
10use ycraft::{
11 collision::CollisionShape,
12 obj::{
13 ControlObjectBehavior, Frame, GameObjectBehavior, GameObjectState, Sprite
14 }, res::{Font, Image, Sound}, room::Room
15};
16use crate::game::{
17 Img, Snd, Fnt, Spr, Rm, Data, BASE_MOVE_SPD, MOVE_SPD_INC
18};
19
20#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
21pub enum Dir {
22 Up,
23 Down,
24 Left,
25 Right
26}
27
28#[derive(Clone)]
29struct SnakeHead {
30 state: GameObjectState<Img, Spr, Data>,
31 move_spd: f64,
32 inter_pos: (f64, f64),
33 can_change_dir: bool,
34 add_body_seg: bool,
35 should_die: bool,
36 play_eat_snd: bool
37}
38
39impl SnakeHead {
40 pub fn new() -> Self {
41 let pos = (640.0 / 2.0 + 32.0 + 32.0 / 2.0, 352.0 / 2.0);
42 Self {
43 state: GameObjectState {
44 name: "head".to_string(),
45 pos,
46 collider: CollisionShape::Rect { center: (0, 0), size: (31, 31) },
47 cur_spr: Spr::Head,
48 sprs: HashMap::from([(
49 Spr::Head,
50 Sprite::new(
51 vec![ Frame::new(Img::Snake, Rect::new(0, 0, 32, 32), (32, 32)) ],
52 0.0, (16, 16)
53 )
54 )]), custom: Data::Head {
55 dir: Dir::Right,
56 lurch_propagation: 0
57 }
58 }, move_spd: BASE_MOVE_SPD,
59 inter_pos: pos,
60 can_change_dir: true,
61 add_body_seg: false,
62 should_die: false,
63 play_eat_snd: false
64 }
65 }
66}
67
68impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for SnakeHead {
69 fn state(&self) -> GameObjectState<Img, Spr, Data> {
70 self.state.clone()
71 }
72
73 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
74 self.state = new_state.clone();
75 }
76
77 fn on_reset(&mut self) -> bool {
78 let nw = SnakeHead::new();
79 self.state = nw.state;
80 self.move_spd = nw.move_spd;
81 self.inter_pos = nw.inter_pos;
82 self.can_change_dir = nw.can_change_dir;
83 self.add_body_seg = nw.add_body_seg;
84 self.should_die = false;
85 false
86 }
87
88 fn handle_sdl_event(&mut self, event: &Event) {
89 match event {
90 Event::KeyDown { scancode, .. } => if scancode.is_some() {
91 if let Data::Head { ref mut dir, .. } = self.state.custom {
92 if self.can_change_dir {
93 let pos_dif = match *dir {
97 Dir::Up | Dir::Down => (self.inter_pos.1 - self.state.pos.1).abs(),
98 Dir::Left | Dir::Right => (self.inter_pos.0 - self.state.pos.0).abs()
99 };
100 self.inter_pos = self.state.pos;
101 match scancode.unwrap() {
102 Scancode::Up => {
103 self.can_change_dir = false;
104 *dir = Dir::Up;
105 self.inter_pos.1 -= pos_dif;
106 }, Scancode::Down => {
107 self.can_change_dir = false;
108 *dir = Dir::Down;
109 self.inter_pos.1 += pos_dif;
110 }, Scancode::Left => {
111 self.can_change_dir = false;
112 *dir = Dir::Left;
113 self.inter_pos.0 -= pos_dif;
114 }, Scancode::Right => {
115 self.can_change_dir = false;
116 *dir = Dir::Right;
117 self.inter_pos.0 += pos_dif;
118 }, _ => {}
119 }
120 }
121 }
122 }, _ => {}
123 }
124 }
125
126 fn update(
127 &mut self, delta: f64,
128 ctl_objs: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
129 others: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
130 Option<Rm>,
131 Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
132 ) {
133 let mut score = 0;
134 for obj in ctl_objs.iter() {
135 if let Data::Score(sc) = obj.data() {
136 score = sc;
137 break;
138 }
139 }
140 let mut added_objs: Vec<Box<dyn GameObjectBehavior<_, _, _, _, _, _>>> = Vec::new();
141 if let Data::Head { ref mut dir, ref mut lurch_propagation } =
142 self.state.custom {
143 if *lurch_propagation == 0 {
144 match dir {
145 Dir::Up => {
146 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
147 spr.angle = 0.0;
148 }
149 self.inter_pos.1 -= delta * self.move_spd;
150 if self.inter_pos.1.floor() < self.state.pos.1 - 32.0 {
151 self.can_change_dir = true;
152 self.state.pos.1 -= 32.0;
153 *lurch_propagation = score;
154 }
155 }, Dir::Down => {
156 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
157 spr.angle = 180.0;
158 }
159 self.inter_pos.1 += delta * self.move_spd;
160 if self.inter_pos.1.floor() > self.state.pos.1 + 32.0 {
161 self.can_change_dir = true;
162 self.state.pos.1 += 32.0;
163 *lurch_propagation = score;
164 }
165 }, Dir::Left => {
166 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
167 spr.angle = 270.0;
168 }
169 self.inter_pos.0 -= delta * self.move_spd;
170 if self.inter_pos.0.floor() < self.state.pos.0 - 32.0 {
171 self.can_change_dir = true;
172 self.state.pos.0 -= 32.0;
173 *lurch_propagation = score;
174 }
175 }, Dir::Right => {
176 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
177 spr.angle = 90.0;
178 }
179 self.inter_pos.0 += delta * self.move_spd;
180 if self.inter_pos.0.floor() > self.state.pos.0 + 32.0 {
181 self.can_change_dir = true;
182 self.state.pos.0 += 32.0;
183 *lurch_propagation = score;
184 }
185 }
186 }
187 } else {
188 *lurch_propagation -= 1;
189 }
190
191 if self.add_body_seg {
192 let mut max_body = -1;
193 let mut max_body_pos = (0.0, 0.0);
194 for other in others.iter() {
195 if let Data::Body { index, .. } = other.state().custom {
196 if index > max_body {
197 max_body = index;
198 max_body_pos = other.state().pos;
199 }
200 }
201 }
202 added_objs.push(Box::new(SnakeBody::new(max_body + 1, max_body_pos)));
203 self.add_body_seg = false;
204 self.move_spd += MOVE_SPD_INC;
205 }
206
207 if self.state.pos.0 < 32.0 || self.state.pos.1 < 32.0
208 || self.state.pos.0 > 640.0 - 32.0 || self.state.pos.1 > 360.0 - 32.0 {
209 return (Some(Rm::Dead), vec![]);
210 }
211
212 if self.should_die {
213 return (Some(Rm::Dead), vec![]);
214 }
215 }
216 (None, added_objs)
217 }
218
219 fn on_collision(
220 &mut self,
221 other: &Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>) {
222 match other.state().custom {
223 Data::Mouse => {
224 self.add_body_seg = true;
225 self.play_eat_snd = true;
226 }, Data::Tail | Data::Body { .. } => {
227 if let Data::Head { lurch_propagation, .. } = self.state.custom {
228 if lurch_propagation == 0 {
229 self.should_die = true;
230 }
231 }
232 }
233 _ => {}
234 }
235 }
236
237 fn render(
238 &mut self, cnv: &mut Canvas<Window>,
239 imgs: &HashMap<Img, Image>, snds: &HashMap<Snd, Sound>,
240 _fonts: &HashMap<Fnt, Font>, _creator: &TextureCreator<WindowContext>,
241 elapsed: f64) -> Result<(), String> {
242 if self.play_eat_snd {
243 snds[&Snd::Bite].play()?;
244 self.play_eat_snd = false;
245 }
246
247 let mut state = self.state().clone();
249 let GameObjectState { ref mut sprs, ref mut cur_spr, pos, .. } = state;
250 if let Some(spr) = sprs.get_mut(cur_spr) {
251 spr.update(elapsed);
252 spr.render(cnv, imgs, (pos.0 as i32, pos.1 as i32))?;
253 }
254 self.set_state(&state);
255 Ok(())
256 }
257}
258
259#[derive(Clone)]
260struct SnakeBody {
261 state: GameObjectState<Img, Spr, Data>,
262 last_dir: Dir,
263 last_pos: (f64, f64),
264 def_pos: (f64, f64)
265}
266
267impl SnakeBody {
268 pub fn new(index: isize, def_pos: (f64, f64)) -> Self {
269 Self {
270 state: GameObjectState {
271 name: format!("snake_body_{}", index),
272 pos: def_pos,
273 collider: CollisionShape::Rect { center: (0, 0), size: (31, 31) },
274 cur_spr: Spr::Body,
275 sprs: HashMap::from([(
276 Spr::Body,
277 Sprite::new(
278 vec![ Frame::new(Img::Snake, Rect::new(32, 0, 32, 32), (32, 32)) ],
279 0.0, (16, 16)
280 )
281 )]), custom: Data::Body {
282 index,
283 dir: Dir::Right
284 }
285 }, last_dir: Dir::Right,
286 last_pos: def_pos,
287 def_pos
288 }
289 }
290}
291
292impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for SnakeBody {
293 fn state(&self) -> GameObjectState<Img, Spr, Data> {
294 self.state.clone()
295 }
296
297 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
298 self.state = new_state.clone();
299 }
300
301 fn on_reset(&mut self) -> bool {
302 if let Data::Body { index, .. } = self.state.custom {
303 if index > 1 {
304 true
305 } else {
306 let nw = SnakeBody::new(index, self.def_pos);
307 self.state = nw.state;
308 self.last_dir = nw.last_dir;
309 self.last_pos = nw.last_pos;
310 false
311 }
312 } else {
313 true
314 }
315 }
316
317 fn update(
318 &mut self, _delta: f64,
319 _ctl_objs: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
320 others: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
321 Option<Rm>,
322 Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
323 ) {
324 if let Data::Body { ref mut index, ref mut dir } = self.state.custom {
325 let parent_id = if *index == 0 {
326 "head".to_string()
327 } else {
328 format!("snake_body_{}", *index - 1)
329 };
330 for other in others.iter() {
331 if *other.state().name == parent_id && other.state().pos != self.last_pos {
332 self.state.pos = self.last_pos;
333 *dir = self.last_dir;
334 self.last_pos = other.state().pos;
335 if *index == 0 {
336 if let Data::Head { dir: other_dir, .. } = other.state().custom {
337 self.last_dir = other_dir;
338 }
339 } else {
340 if let Data::Body { dir: other_dir, .. } = other.state().custom {
341 self.last_dir = other_dir;
342 }
343 }
344 }
345 }
346 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
347 spr.angle = match dir {
348 Dir::Up => 0.0,
349 Dir::Down => 180.0,
350 Dir::Left => 270.0,
351 Dir::Right => 90.0
352 };
353 }
354 }
355 (None, vec![])
356 }
357}
358
359#[derive(Clone)]
360struct SnakeTail {
361 state: GameObjectState<Img, Spr, Data>,
362 dir: Dir,
363 last_dir: Dir,
364 last_pos: (f64, f64),
365}
366
367impl SnakeTail {
368 pub fn new() -> Self {
369 Self {
370 state: GameObjectState {
371 name: "snake_tail".to_string(),
372 pos: (640.0 / 2.0 - 32.0 - 32.0 / 2.0, 352.0 / 2.0),
373 cur_spr: Spr::Tail,
374 sprs: HashMap::from([(
375 Spr::Tail,
376 Sprite::new(
377 vec![ Frame::new(Img::Snake, Rect::new(0, 32, 32, 32), (32, 32)) ],
378 0.0, (16, 16)
379 )
380 )]), collider: CollisionShape::Rect { center: (0, 0), size: (31, 31) },
381 custom: Data::Tail
382 }, dir: Dir::Right,
383 last_dir: Dir::Right,
384 last_pos: (640.0 / 2.0 - 32.0 - 32.0 / 2.0, 352.0 / 2.0),
385 }
386 }
387}
388
389impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for SnakeTail {
390 fn state(&self) -> GameObjectState<Img, Spr, Data> {
391 self.state.clone()
392 }
393
394 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
395 self.state = new_state.clone();
396 }
397
398 fn on_reset(&mut self) -> bool {
399 let nw = SnakeTail::new();
400 self.state = nw.state;
401 self.last_pos = nw.last_pos;
402 self.dir = nw.dir;
403 self.last_dir = nw.dir;
404 false
405 }
406
407 fn update(
408 &mut self, _delta: f64,
409 _ctl_objs: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
410 others: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
411 Option<Rm>, Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
412 ) {
413 let mut max_body = -1;
414 for other in others.iter() {
415 if let Data::Body { index, .. } = other.state().custom {
416 if index > max_body {
417 max_body = index;
418 }
419 }
420 }
421 for other in others.iter() {
422 if let Data::Body { index, dir: other_dir} = other.state().custom {
423 if index == max_body && self.last_pos != other.state().pos {
424 self.dir = self.last_dir;
425 self.state.pos = self.last_pos;
426 self.last_dir = other_dir;
427 self.last_pos = other.state().pos;
428 }
429 }
430 }
431 if let Some(spr) = self.state.sprs.get_mut(&self.state.cur_spr) {
432 spr.angle = match self.dir {
433 Dir::Up => 0.0,
434 Dir::Down => 180.0,
435 Dir::Left => 270.0,
436 Dir::Right => 90.0
437 };
438 }
439 (None, vec![])
440 }
441}
442
443#[derive(Clone)]
444struct Mouse {
445 state: GameObjectState<Img, Spr, Data>
446}
447
448impl Mouse {
449 pub fn new() -> Self {
450 Self {
451 state: GameObjectState {
452 name: "mouse".to_string(),
453 pos: Self::random_mouse_pos(),
454 cur_spr: Spr::Mouse,
455 sprs: HashMap::from([(
456 Spr::Mouse,
457 Sprite::new(
458 vec![ Frame::new(Img::Mouse, Rect::new(0, 0, 32, 32), (32, 32)) ],
459 0.0, (16, 16)
460 )
461 )]), collider: CollisionShape::Circle { center: (0, 0), radius: 15 },
462 custom: Data::Mouse
463 }
464 }
465 }
466
467 fn random_mouse_pos() -> (f64, f64) {
468 let mut rng = rand::thread_rng();
469 (
470 (rng.gen_range(32.0..640.0 - 96.0) / 32.0 as f64).floor() * 32.0 + 16.0,
471 (rng.gen_range(32.0..360.0 - 96.0) / 32.0 as f64).floor() * 32.0 + 16.0
472 )
473 }
474}
475
476impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for Mouse {
477 fn state(&self) -> GameObjectState<Img, Spr, Data> {
478 self.state.clone()
479 }
480
481 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
482 self.state = new_state.clone();
483 }
484
485 fn on_reset(&mut self) -> bool {
486 let nw = Mouse::new();
487 self.state = nw.state;
488 false
489 }
490
491 fn on_collision(
492 &mut self,
493 other: &Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>) {
494 if let Data::Head { .. } = other.state().custom {
495 self.on_reset();
496 }
497 }
498}
499
500#[derive(Clone)]
501struct Board {
502 state: GameObjectState<Img, Spr, Data>
503}
504
505impl Board {
506 pub fn new() -> Self {
507 Self {
508 state: GameObjectState {
509 name: "board".to_string(),
510 pos: (0.0, 0.0),
511 collider: CollisionShape::Rect { center: (320, 180), size: (640, 480) },
512 cur_spr: Spr::Board,
513 sprs: HashMap::from([(
514 Spr::Board,
515 Sprite::new(
516 vec![Frame::new(
517 Img::Board, Rect::new(0, 0, 640, 360), (640, 360)
518 )], 0.0, (0, 0)
519 )
520 )]), custom: Data::Board
521 }
522 }
523 }
524}
525
526impl GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> for Board {
527 fn state(&self) -> GameObjectState<Img, Spr, Data> {
528 self.state.clone()
529 }
530
531 fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>) {
532 self.state = new_state.clone();
533 }
534
535 fn on_reset(&mut self) -> bool {
536 false
537 }
538}
539
540pub fn play() -> Room<Img, Snd, Fnt, Spr, Rm, Data> {
541 Room::new(
542 vec![
543 Box::new(Board::new()),
544 Box::new(SnakeHead::new()),
545 Box::new(SnakeBody::new(0, (640.0 / 2.0 + 32.0 / 2.0, 352.0 / 2.0))),
546 Box::new(SnakeBody::new(1, (640.0 / 2.0 - 32.0 / 2.0, 352.0 / 2.0))),
547 Box::new(SnakeTail::new()),
548 Box::new(Mouse::new())
549 ], false
550 )
551}
552