1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3
4#[derive(Clone, Serialize, Deserialize)]
15pub struct Input {
16 keys_pressed: HashSet<u32>, keys_just_pressed: HashSet<u32>, keys_just_released: HashSet<u32>, mouse_buttons_pressed: HashSet<u32>,
23 mouse_buttons_just_pressed: HashSet<u32>,
24 mouse_buttons_just_released: HashSet<u32>,
25
26 mouse_position: (f32, f32),
28 mouse_delta: (f32, f32),
29
30 mouse_scroll_delta: f32,
32}
33
34impl Input {
35 pub fn new() -> Self {
36 Self {
37 keys_pressed: HashSet::new(),
38 keys_just_pressed: HashSet::new(),
39 keys_just_released: HashSet::new(),
40 mouse_buttons_pressed: HashSet::new(),
41 mouse_buttons_just_pressed: HashSet::new(),
42 mouse_buttons_just_released: HashSet::new(),
43 mouse_position: (0.0, 0.0),
44 mouse_delta: (0.0, 0.0),
45 mouse_scroll_delta: 0.0,
46 }
47 }
48
49 pub fn begin_frame(&mut self) {
59 for k in &self.keys_just_released {
61 self.keys_pressed.remove(k);
62 }
63 for b in &self.mouse_buttons_just_released {
64 self.mouse_buttons_pressed.remove(b);
65 }
66
67 self.keys_just_pressed.clear();
68 self.keys_just_released.clear();
69 self.mouse_buttons_just_pressed.clear();
70 self.mouse_buttons_just_released.clear();
71 self.mouse_delta = (0.0, 0.0);
72 self.mouse_scroll_delta = 0.0;
73 }
74
75 pub fn pressed_keys(&self) -> Vec<u32> {
79 self.keys_pressed.iter().copied().collect()
80 }
81
82 pub fn on_key_pressed(&mut self, key: u32) {
84 self.keys_just_released.remove(&key);
89 if self.keys_pressed.insert(key) {
90 self.keys_just_pressed.insert(key);
91 }
92 }
93
94 pub fn on_key_released(&mut self, key: u32) {
100 self.keys_just_released.insert(key);
101 if !self.keys_just_pressed.contains(&key) {
102 self.keys_pressed.remove(&key);
104 }
105 }
107
108 pub fn release_all(&mut self) {
116 for k in self.keys_pressed.drain() {
117 self.keys_just_released.insert(k);
118 }
119 for b in self.mouse_buttons_pressed.drain() {
120 self.mouse_buttons_just_released.insert(b);
121 }
122 self.mouse_delta = (0.0, 0.0);
123 self.mouse_scroll_delta = 0.0;
124 }
125
126 #[inline]
128 pub fn is_key_pressed(&self, key: u32) -> bool {
129 self.keys_pressed.contains(&key)
130 }
131
132 #[inline]
134 pub fn is_key_just_pressed(&self, key: u32) -> bool {
135 self.keys_just_pressed.contains(&key)
136 }
137
138 #[inline]
140 pub fn is_key_just_released(&self, key: u32) -> bool {
141 self.keys_just_released.contains(&key)
142 }
143
144 pub fn on_mouse_button_pressed(&mut self, button: u32) {
148 self.mouse_buttons_just_released.remove(&button);
150 if self.mouse_buttons_pressed.insert(button) {
151 self.mouse_buttons_just_pressed.insert(button);
152 }
153 }
154
155 pub fn on_mouse_button_released(&mut self, button: u32) {
157 self.mouse_buttons_just_released.insert(button);
158 if !self.mouse_buttons_just_pressed.contains(&button) {
159 self.mouse_buttons_pressed.remove(&button);
160 }
161 }
162
163 #[inline]
165 pub fn is_mouse_button_pressed(&self, button: u32) -> bool {
166 self.mouse_buttons_pressed.contains(&button)
167 }
168
169 #[inline]
171 pub fn is_mouse_button_just_pressed(&self, button: u32) -> bool {
172 self.mouse_buttons_just_pressed.contains(&button)
173 }
174
175 #[inline]
177 pub fn is_mouse_button_just_released(&self, button: u32) -> bool {
178 self.mouse_buttons_just_released.contains(&button)
179 }
180
181 pub fn on_mouse_moved(&mut self, x: f32, y: f32) {
187 self.mouse_delta.0 += x - self.mouse_position.0;
188 self.mouse_delta.1 += y - self.mouse_position.1;
189 self.mouse_position = (x, y);
190 }
191
192 pub fn set_mouse_position(&mut self, x: f32, y: f32) {
197 self.mouse_position = (x, y);
198 }
199
200 pub fn on_mouse_delta(&mut self, dx: f32, dy: f32) {
205 self.mouse_delta.0 += dx;
206 self.mouse_delta.1 += dy;
207 }
208
209 #[inline]
211 pub fn mouse_position(&self) -> (f32, f32) {
212 self.mouse_position
213 }
214
215 #[inline]
217 pub fn mouse_delta(&self) -> (f32, f32) {
218 self.mouse_delta
219 }
220
221 pub fn on_mouse_scroll(&mut self, delta: f32) {
226 self.mouse_scroll_delta += delta;
227 }
228
229 #[inline]
232 pub fn mouse_scroll(&self) -> f32 {
233 self.mouse_scroll_delta
234 }
235}
236
237impl Default for Input {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243pub mod mouse {
245 pub const LEFT: u32 = 0;
246 pub const RIGHT: u32 = 1;
247 pub const MIDDLE: u32 = 2;
248}
249
250use std::collections::HashMap;
253
254#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
256pub enum InputBinding {
257 Key(u32),
259 MouseButton(u32),
261}
262
263#[derive(Clone)]
277pub struct ActionMap {
278 bindings: HashMap<String, Vec<InputBinding>>,
279}
280
281impl ActionMap {
282 pub fn new() -> Self {
283 Self {
284 bindings: HashMap::new(),
285 }
286 }
287
288 pub fn bind_key(&mut self, action_name: &str, keycode: u32) {
290 self.bindings
291 .entry(action_name.to_string())
292 .or_default()
293 .push(InputBinding::Key(keycode));
294 }
295
296 pub fn bind_mouse_button(&mut self, action_name: &str, button: u32) {
298 self.bindings
299 .entry(action_name.to_string())
300 .or_default()
301 .push(InputBinding::MouseButton(button));
302 }
303
304 pub fn bind_action(&mut self, action_name: &str, keycode: u32) {
306 self.bind_key(action_name, keycode);
307 }
308
309 pub fn is_action_pressed(&self, input: &Input, action_name: &str) -> bool {
311 if let Some(bindings) = self.bindings.get(action_name) {
312 for binding in bindings {
313 match binding {
314 InputBinding::Key(k) => {
315 if input.is_key_pressed(*k) {
316 return true;
317 }
318 }
319 InputBinding::MouseButton(b) => {
320 if input.is_mouse_button_pressed(*b) {
321 return true;
322 }
323 }
324 }
325 }
326 }
327 false
328 }
329
330 pub fn is_action_just_pressed(&self, input: &Input, action_name: &str) -> bool {
332 if let Some(bindings) = self.bindings.get(action_name) {
333 for binding in bindings {
334 match binding {
335 InputBinding::Key(k) => {
336 if input.is_key_just_pressed(*k) {
337 return true;
338 }
339 }
340 InputBinding::MouseButton(b) => {
341 if input.is_mouse_button_just_pressed(*b) {
342 return true;
343 }
344 }
345 }
346 }
347 }
348 false
349 }
350
351 pub fn is_action_just_released(&self, input: &Input, action_name: &str) -> bool {
353 if let Some(bindings) = self.bindings.get(action_name) {
354 for binding in bindings {
355 match binding {
356 InputBinding::Key(k) => {
357 if input.is_key_just_released(*k) {
358 return true;
359 }
360 }
361 InputBinding::MouseButton(b) => {
362 if input.is_mouse_button_just_released(*b) {
363 return true;
364 }
365 }
366 }
367 }
368 }
369 false
370 }
371}
372
373impl Default for ActionMap {
374 fn default() -> Self {
375 Self::new()
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
388 fn fast_tap_release_then_repress_keeps_key_held() {
389 let mut input = Input::new();
390 input.on_key_pressed(5);
391 input.begin_frame(); assert!(input.is_key_pressed(5));
393
394 input.on_key_released(5);
396 input.on_key_pressed(5);
397 input.begin_frame();
398
399 assert!(input.is_key_pressed(5), "re-pressed key must stay held");
400 assert!(!input.is_key_just_pressed(5), "no spurious just_pressed after begin_frame");
401 }
402
403 #[test]
406 fn test_fast_tap_preserves_pressed_for_one_frame() {
407 let mut input = Input::new();
408
409 input.on_key_pressed(42);
411 input.on_key_released(42);
412
413 assert!(input.is_key_pressed(42), "fast-tap: tuş pressed olmalı");
415 assert!(
416 input.is_key_just_pressed(42),
417 "fast-tap: tuş just_pressed olmalı"
418 );
419 assert!(
420 input.is_key_just_released(42),
421 "fast-tap: tuş just_released olmalı"
422 );
423
424 input.begin_frame();
426
427 assert!(
429 !input.is_key_pressed(42),
430 "sonraki frame: pressed false olmalı"
431 );
432 assert!(
433 !input.is_key_just_pressed(42),
434 "sonraki frame: just_pressed false olmalı"
435 );
436 assert!(
437 !input.is_key_just_released(42),
438 "sonraki frame: just_released false olmalı"
439 );
440 }
441
442 #[test]
443 fn test_normal_press_release_across_frames() {
444 let mut input = Input::new();
445
446 input.on_key_pressed(10);
448 assert!(input.is_key_pressed(10));
449 assert!(input.is_key_just_pressed(10));
450
451 input.begin_frame();
453 assert!(input.is_key_pressed(10));
454 assert!(!input.is_key_just_pressed(10));
455
456 input.on_key_released(10);
458 assert!(!input.is_key_pressed(10)); assert!(input.is_key_just_released(10));
460
461 input.begin_frame();
463 assert!(!input.is_key_pressed(10));
464 assert!(!input.is_key_just_released(10));
465 }
466
467 #[test]
468 fn test_fast_tap_mouse_button() {
469 let mut input = Input::new();
470
471 input.on_mouse_button_pressed(mouse::LEFT);
472 input.on_mouse_button_released(mouse::LEFT);
473
474 assert!(input.is_mouse_button_pressed(mouse::LEFT));
475 assert!(input.is_mouse_button_just_pressed(mouse::LEFT));
476 assert!(input.is_mouse_button_just_released(mouse::LEFT));
477
478 input.begin_frame();
479
480 assert!(!input.is_mouse_button_pressed(mouse::LEFT));
481 assert!(!input.is_mouse_button_just_pressed(mouse::LEFT));
482 assert!(!input.is_mouse_button_just_released(mouse::LEFT));
483 }
484
485 #[test]
488 fn test_mouse_moved_accumulates_delta() {
489 let mut input = Input::new();
490
491 input.on_mouse_moved(100.0, 200.0);
492 assert_eq!(input.mouse_delta(), (100.0, 200.0));
494
495 input.on_mouse_moved(150.0, 250.0);
496 assert_eq!(input.mouse_delta(), (150.0, 250.0));
498
499 assert_eq!(input.mouse_position(), (150.0, 250.0));
500 }
501
502 #[test]
503 fn test_mouse_delta_resets_on_begin_frame() {
504 let mut input = Input::new();
505
506 input.on_mouse_moved(100.0, 200.0);
507 assert_ne!(input.mouse_delta(), (0.0, 0.0));
508
509 input.begin_frame();
510 assert_eq!(input.mouse_delta(), (0.0, 0.0));
511 assert_eq!(input.mouse_position(), (100.0, 200.0));
513 }
514
515 #[test]
518 fn test_release_all_clears_held_keys_and_buttons() {
519 let mut input = Input::new();
520 input.on_key_pressed(65); input.on_key_pressed(87); input.on_mouse_button_pressed(1);
523 input.on_mouse_moved(10.0, 10.0);
524 input.begin_frame(); assert!(input.is_key_pressed(65));
526 assert!(input.is_key_pressed(87));
527 assert!(input.is_mouse_button_pressed(1));
528
529 input.release_all();
531 assert!(!input.is_key_pressed(65), "A odak kaybından sonra hâlâ basılı");
532 assert!(!input.is_key_pressed(87), "W odak kaybından sonra hâlâ basılı");
533 assert!(!input.is_mouse_button_pressed(1));
534 assert_eq!(input.mouse_delta(), (0.0, 0.0));
535 assert!(input.is_key_just_released(65));
537 }
538
539 #[test]
542 fn test_scroll_accumulates_and_resets() {
543 let mut input = Input::new();
544
545 input.on_mouse_scroll(3.0);
546 input.on_mouse_scroll(-1.0);
547 assert_eq!(input.mouse_scroll(), 2.0);
548
549 input.begin_frame();
550 assert_eq!(input.mouse_scroll(), 0.0);
551 }
552
553 #[test]
556 fn test_pressed_keys() {
557 let mut input = Input::new();
558 input.on_key_pressed(1);
559 input.on_key_pressed(2);
560 input.on_key_pressed(3);
561
562 let mut keys = input.pressed_keys();
563 keys.sort();
564 assert_eq!(keys, vec![1, 2, 3]);
565 }
566
567 #[test]
570 fn test_action_map_key_binding() {
571 let mut input = Input::new();
572 let mut actions = ActionMap::new();
573 actions.bind_key("Jump", 42);
574
575 input.on_key_pressed(42);
576 assert!(actions.is_action_pressed(&input, "Jump"));
577 assert!(actions.is_action_just_pressed(&input, "Jump"));
578 }
579
580 #[test]
581 fn test_action_map_mouse_binding() {
582 let mut input = Input::new();
583 let mut actions = ActionMap::new();
584 actions.bind_mouse_button("Attack", mouse::LEFT);
585
586 input.on_mouse_button_pressed(mouse::LEFT);
587 assert!(actions.is_action_pressed(&input, "Attack"));
588 assert!(actions.is_action_just_pressed(&input, "Attack"));
589
590 input.begin_frame();
591 input.on_mouse_button_released(mouse::LEFT);
592 assert!(actions.is_action_just_released(&input, "Attack"));
593 }
594
595 #[test]
596 fn test_action_map_mixed_bindings() {
597 let mut input = Input::new();
598 let mut actions = ActionMap::new();
599 actions.bind_key("Fire", 42);
600 actions.bind_mouse_button("Fire", mouse::LEFT);
601
602 assert!(!actions.is_action_pressed(&input, "Fire"));
604
605 input.on_mouse_button_pressed(mouse::LEFT);
607 assert!(actions.is_action_pressed(&input, "Fire"));
608
609 input.begin_frame();
610 input.on_mouse_button_released(mouse::LEFT);
611
612 input.on_key_pressed(42);
614 assert!(actions.is_action_pressed(&input, "Fire"));
615 }
616
617 #[test]
618 fn test_action_map_just_released() {
619 let mut input = Input::new();
620 let mut actions = ActionMap::new();
621 actions.bind_key("Charge", 99);
622
623 input.on_key_pressed(99);
624 input.begin_frame();
625 input.on_key_released(99);
626
627 assert!(actions.is_action_just_released(&input, "Charge"));
628 assert!(!actions.is_action_pressed(&input, "Charge"));
629 }
630
631 #[test]
632 fn test_bind_action_backward_compat() {
633 let mut actions = ActionMap::new();
634 actions.bind_action("Jump", 42); assert!(matches!(
636 actions.bindings.get("Jump").unwrap()[0],
637 InputBinding::Key(42)
638 ));
639 }
640}
641
642#[derive(Serialize, Deserialize, Clone)]
643pub struct FrameRecord {
644 pub dt: f32,
645 pub input: Input,
646}
647
648#[derive(Serialize, Deserialize, Clone)]
649pub struct PlaybackData {
650 pub frames: Vec<FrameRecord>,
651}
652
653impl PlaybackData {
654 pub fn save(&self, path: &str) -> Result<(), String> {
655 let string_data = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
656 .map_err(|e| format!("Serilestirme hatasi: {}", e))?;
657 std::fs::write(path, string_data).map_err(|e| format!("Dosya yazma hatasi: {}", e))?;
658 Ok(())
659 }
660
661 pub fn load(path: &str) -> Result<Self, String> {
662 let string_data =
663 std::fs::read_to_string(path).map_err(|e| format!("Dosya okuma hatasi: {}", e))?;
664 ron::from_str(&string_data).map_err(|e| format!("Deserilestirme hatasi: {}", e))
665 }
666}
667
668#[derive(Clone, Debug, Serialize, Deserialize)]
672pub struct FrameActions {
673 pub pressed: HashSet<String>,
674 pub just_pressed: HashSet<String>,
675 pub just_released: HashSet<String>,
676}
677
678#[derive(Clone, Debug, Serialize, Deserialize)]
681pub struct FighterInputBuffer {
682 pub frames: std::collections::VecDeque<FrameActions>,
683 pub max_frames: usize,
684}
685
686impl FighterInputBuffer {
687 pub fn new(max_frames: usize) -> Self {
689 Self {
690 frames: std::collections::VecDeque::with_capacity(max_frames),
691 max_frames,
692 }
693 }
694
695 pub fn update(&mut self, input: &Input, action_map: &ActionMap, actions_to_track: &[&str]) {
697 let mut frame = FrameActions {
698 pressed: HashSet::new(),
699 just_pressed: HashSet::new(),
700 just_released: HashSet::new(),
701 };
702
703 for &action in actions_to_track {
704 if action_map.is_action_pressed(input, action) {
705 frame.pressed.insert(action.to_string());
706 }
707 if action_map.is_action_just_pressed(input, action) {
708 frame.just_pressed.insert(action.to_string());
709 }
710 if action_map.is_action_just_released(input, action) {
711 frame.just_released.insert(action.to_string());
712 }
713 }
714
715 self.frames.push_front(frame);
716 if self.frames.len() > self.max_frames {
717 self.frames.pop_back();
718 }
719 }
720
721 pub fn check_combo_strict(&self, sequence: &[&str], max_gap: usize) -> bool {
726 if sequence.is_empty() || self.frames.is_empty() {
727 return false;
728 }
729
730 let mut seq_idx = sequence.len() as isize - 1;
733 let mut frames_since_last_match = 0;
734
735 for frame in &self.frames {
736 if frames_since_last_match > max_gap {
737 return false;
739 }
740
741 let required_action = sequence[seq_idx as usize];
742
743 if frame.just_pressed.contains(required_action) || frame.pressed.contains(required_action) {
746 seq_idx -= 1;
748 frames_since_last_match = 0;
749
750 if seq_idx < 0 {
751 return true;
753 }
754 } else {
755 frames_since_last_match += 1;
756 }
757 }
758
759 false
760 }
761}
762
763impl Default for FighterInputBuffer {
764 fn default() -> Self {
765 Self::new(60)
766 }
767}
768
769#[cfg(test)]
770mod fighter_tests {
771 use super::*;
772
773 #[test]
774 fn test_fighter_input_buffer_combo() {
775 let mut buffer = FighterInputBuffer::new(60);
776 let _input = Input::new();
777 let _action_map = ActionMap::new();
778
779 let frame1 = FrameActions {
781 pressed: ["Down".to_string()].into_iter().collect(),
782 just_pressed: [].into_iter().collect(),
783 just_released: [].into_iter().collect(),
784 };
785 buffer.frames.push_front(frame1);
786
787 let frame2 = FrameActions {
789 pressed: ["Down".to_string(), "Right".to_string()].into_iter().collect(),
790 just_pressed: ["Right".to_string()].into_iter().collect(),
791 just_released: [].into_iter().collect(),
792 };
793 buffer.frames.push_front(frame2);
794
795 let frame3 = FrameActions {
797 pressed: ["Right".to_string()].into_iter().collect(),
798 just_pressed: [].into_iter().collect(),
799 just_released: ["Down".to_string()].into_iter().collect(),
800 };
801 buffer.frames.push_front(frame3);
802
803 let frame4 = FrameActions {
805 pressed: ["LightPunch".to_string()].into_iter().collect(),
806 just_pressed: ["LightPunch".to_string()].into_iter().collect(),
807 just_released: [].into_iter().collect(),
808 };
809 buffer.frames.push_front(frame4);
810
811 let combo = ["Down", "Right", "LightPunch"];
813
814 assert!(buffer.check_combo_strict(&combo, 5), "Kombo basariyla algilanmali");
816
817 let wrong_combo = ["LightPunch", "Right", "Down"];
819 assert!(!buffer.check_combo_strict(&wrong_combo, 5), "Yanlis kombo sirasi algilanmamali");
820 }
821
822 #[test]
823 fn test_fighter_input_buffer_max_gap() {
824 let mut buffer = FighterInputBuffer::new(60);
825
826 let frame_down = FrameActions {
827 pressed: ["Down".to_string()].into_iter().collect(),
828 just_pressed: ["Down".to_string()].into_iter().collect(),
829 just_released: [].into_iter().collect(),
830 };
831 buffer.frames.push_front(frame_down);
832
833 for _ in 0..10 {
835 let empty = FrameActions {
836 pressed: [].into_iter().collect(),
837 just_pressed: [].into_iter().collect(),
838 just_released: [].into_iter().collect(),
839 };
840 buffer.frames.push_front(empty);
841 }
842
843 let frame_punch = FrameActions {
844 pressed: ["LightPunch".to_string()].into_iter().collect(),
845 just_pressed: ["LightPunch".to_string()].into_iter().collect(),
846 just_released: [].into_iter().collect(),
847 };
848 buffer.frames.push_front(frame_punch);
849
850 let combo = ["Down", "LightPunch"];
851
852 assert!(!buffer.check_combo_strict(&combo, 5), "Cok yavas basildi, algilanmamali");
854
855 assert!(buffer.check_combo_strict(&combo, 15), "Max gap genis oldugu icin algilanmali");
857 }
858}
859