Skip to main content

gizmo_core/
input.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3
4/// Ergonomik input soyutlama katmanı.
5///
6/// Kullanım:
7/// ```rust,ignore
8/// if input.is_key_pressed(KeyCode::KeyW as u32) { /* ileri git */ }
9/// if input.is_key_just_pressed(KeyCode::Space as u32) { /* zıpla (tek sefer) */ }
10/// if input.is_mouse_button_pressed(mouse::LEFT) { /* ateş et */ }
11/// let (dx, dy) = input.mouse_delta(); /* fare hareketi */
12/// let scroll = input.mouse_scroll(); /* tekerlek */
13/// ```
14#[derive(Clone, Serialize, Deserialize)]
15pub struct Input {
16    // Tuş durumları
17    keys_pressed: HashSet<u32>,       // Şu an basılı tuşlar
18    keys_just_pressed: HashSet<u32>,  // Bu frame'de yeni basılan
19    keys_just_released: HashSet<u32>, // Bu frame'de bırakılan
20
21    // Fare durumları
22    mouse_buttons_pressed: HashSet<u32>,
23    mouse_buttons_just_pressed: HashSet<u32>,
24    mouse_buttons_just_released: HashSet<u32>,
25
26    // Fare pozisyonu ve hareket
27    mouse_position: (f32, f32),
28    mouse_delta: (f32, f32),
29
30    // Fare tekerlek (scroll) deltası
31    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    // ==================== FRAME YAŞAM DÖNGÜSÜ ====================
50
51    /// Her frame başında çağrılmalı — "just pressed/released" setlerini temizler
52    /// ve deferred tuş bırakmalarını gerçekleştirir.
53    ///
54    /// Mantık:
55    /// - `on_key_released()` aynı frame'de basılıp bırakılan tuşlar için `keys_pressed`'den
56    ///   silmeyi erteliyordu (fast-tap koruması). `begin_frame()` bu deferred silmeleri gerçekleştirir.
57    /// - Ardından just_pressed ve just_released setleri temizlenir, fare deltaları sıfırlanır.
58    pub fn begin_frame(&mut self) {
59        // Deferred removal: aynı frame'de basılıp bırakılan tuşları artık kaldır
60        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    // ==================== TUŞ GİRDİSİ ====================
76
77    /// Basılı tüm tuşları döndürür (Debug için)
78    pub fn pressed_keys(&self) -> Vec<u32> {
79        self.keys_pressed.iter().copied().collect()
80    }
81
82    /// Tuş basıldığında çağır (winit KeyCode'un scan code'u)
83    pub fn on_key_pressed(&mut self, key: u32) {
84        if self.keys_pressed.insert(key) {
85            self.keys_just_pressed.insert(key);
86        }
87    }
88
89    /// Tuş bırakıldığında çağır.
90    ///
91    /// Eğer tuş aynı frame'de basılıp bırakıldıysa (`keys_just_pressed` içindeyse),
92    /// `keys_pressed`'den silmeyi `begin_frame()`'e erteler. Böylece oyun bu "fast tap"ı
93    /// kaçırmaz — hem `is_key_pressed` hem `is_key_just_pressed` o frame boyunca true döner.
94    pub fn on_key_released(&mut self, key: u32) {
95        self.keys_just_released.insert(key);
96        if !self.keys_just_pressed.contains(&key) {
97            // Normal bırakma — hemen sil
98            self.keys_pressed.remove(&key);
99        }
100        // else: fast-tap — begin_frame()'de silinecek
101    }
102
103    /// Tuş şu an basılı mı? (sürekli kontrol)
104    #[inline]
105    pub fn is_key_pressed(&self, key: u32) -> bool {
106        self.keys_pressed.contains(&key)
107    }
108
109    /// Tuş bu frame'de mi basıldı? (tek seferlik tetikleme)
110    #[inline]
111    pub fn is_key_just_pressed(&self, key: u32) -> bool {
112        self.keys_just_pressed.contains(&key)
113    }
114
115    /// Tuş bu frame'de mi bırakıldı?
116    #[inline]
117    pub fn is_key_just_released(&self, key: u32) -> bool {
118        self.keys_just_released.contains(&key)
119    }
120
121    // ==================== FARE GİRDİSİ ====================
122
123    /// Fare butonu basıldığında çağır (0=Left, 1=Right, 2=Middle)
124    pub fn on_mouse_button_pressed(&mut self, button: u32) {
125        if self.mouse_buttons_pressed.insert(button) {
126            self.mouse_buttons_just_pressed.insert(button);
127        }
128    }
129
130    /// Fare butonu bırakıldığında çağır
131    pub fn on_mouse_button_released(&mut self, button: u32) {
132        self.mouse_buttons_just_released.insert(button);
133        if !self.mouse_buttons_just_pressed.contains(&button) {
134            self.mouse_buttons_pressed.remove(&button);
135        }
136    }
137
138    /// Fare butonu basılı mı?
139    #[inline]
140    pub fn is_mouse_button_pressed(&self, button: u32) -> bool {
141        self.mouse_buttons_pressed.contains(&button)
142    }
143
144    /// Fare butonu bu frame'de mi basıldı?
145    #[inline]
146    pub fn is_mouse_button_just_pressed(&self, button: u32) -> bool {
147        self.mouse_buttons_just_pressed.contains(&button)
148    }
149
150    /// Fare butonu bu frame'de mi bırakıldı?
151    #[inline]
152    pub fn is_mouse_button_just_released(&self, button: u32) -> bool {
153        self.mouse_buttons_just_released.contains(&button)
154    }
155
156    // ==================== FARE POZİSYONU ====================
157
158    /// Fare ekran pozisyonu değiştiğinde çağır.
159    /// Pozisyon farkından delta biriktirilir — `DeviceEvent::MouseMotion`
160    /// olmayan platformlarda (web, bazı Linux konfigürasyonları) fallback sağlar.
161    pub fn on_mouse_moved(&mut self, x: f32, y: f32) {
162        self.mouse_delta.0 += x - self.mouse_position.0;
163        self.mouse_delta.1 += y - self.mouse_position.1;
164        self.mouse_position = (x, y);
165    }
166
167    /// Fare delta hareketi (DeviceEvent::MouseMotion).
168    /// `on_mouse_moved` zaten delta biriktirdiği için, bu metot yalnızca
169    /// platform `DeviceEvent::MouseMotion` veriyorsa ek doğruluk sağlar.
170    /// İkisi birlikte çağrılmamalı — platform'a göre birini kullanın.
171    pub fn on_mouse_delta(&mut self, dx: f32, dy: f32) {
172        self.mouse_delta.0 += dx;
173        self.mouse_delta.1 += dy;
174    }
175
176    /// Fare ekran pozisyonu
177    #[inline]
178    pub fn mouse_position(&self) -> (f32, f32) {
179        self.mouse_position
180    }
181
182    /// Bu frame'deki fare hareketi (delta)
183    #[inline]
184    pub fn mouse_delta(&self) -> (f32, f32) {
185        self.mouse_delta
186    }
187
188    // ==================== FARE TEKERLEK (SCROLL) ====================
189
190    /// Fare tekerleği hareket ettiğinde çağır.
191    /// Pozitif = yukarı/ileri, negatif = aşağı/geri.
192    pub fn on_mouse_scroll(&mut self, delta: f32) {
193        self.mouse_scroll_delta += delta;
194    }
195
196    /// Bu frame'deki fare tekerlek deltası.
197    /// Pozitif = yukarı/ileri, negatif = aşağı/geri.
198    #[inline]
199    pub fn mouse_scroll(&self) -> f32 {
200        self.mouse_scroll_delta
201    }
202}
203
204impl Default for Input {
205    fn default() -> Self {
206        Self::new()
207    }
208}
209
210/// Fare buton sabitleri
211pub mod mouse {
212    pub const LEFT: u32 = 0;
213    pub const RIGHT: u32 = 1;
214    pub const MIDDLE: u32 = 2;
215}
216
217// ==================== ACTION MAP (Tuş Soyutlama) ====================
218
219use std::collections::HashMap;
220
221/// Girdi binding türü — klavye tuşu veya fare butonu.
222#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
223pub enum InputBinding {
224    /// Klavye tuşu (winit KeyCode as u32)
225    Key(u32),
226    /// Fare butonu (mouse::LEFT, mouse::RIGHT, mouse::MIDDLE)
227    MouseButton(u32),
228}
229
230/// Evrensel Girdi Çevirici.
231/// "W" veya "Yukarı Ok" tuşlarını doğrudan kontrol etmek yerine,
232/// "Accelerate" veya "Jump" gibi mantıksal isimlendirmelerle dinlememizi sağlar.
233///
234/// # Örnek
235/// ```rust,ignore
236/// let mut actions = ActionMap::new();
237/// actions.bind_key("Jump", KeyCode::Space as u32);
238/// actions.bind_mouse_button("Attack", mouse::LEFT);
239///
240/// if actions.is_action_just_pressed(&input, "Jump") { player.jump(); }
241/// if actions.is_action_pressed(&input, "Attack") { player.attack(); }
242/// ```
243#[derive(Clone)]
244pub struct ActionMap {
245    bindings: HashMap<String, Vec<InputBinding>>,
246}
247
248impl ActionMap {
249    pub fn new() -> Self {
250        Self {
251            bindings: HashMap::new(),
252        }
253    }
254
255    /// Bir isme (Action) klavye tuşu bağlar
256    pub fn bind_key(&mut self, action_name: &str, keycode: u32) {
257        self.bindings
258            .entry(action_name.to_string())
259            .or_default()
260            .push(InputBinding::Key(keycode));
261    }
262
263    /// Bir isme (Action) fare butonu bağlar
264    pub fn bind_mouse_button(&mut self, action_name: &str, button: u32) {
265        self.bindings
266            .entry(action_name.to_string())
267            .or_default()
268            .push(InputBinding::MouseButton(button));
269    }
270
271    /// Geriye dönük uyumluluk — `bind_key()` ile aynı.
272    pub fn bind_action(&mut self, action_name: &str, keycode: u32) {
273        self.bind_key(action_name, keycode);
274    }
275
276    /// Action (eylem) şu an uygulanıyor mu? (Basılı tutuluyor mu)
277    pub fn is_action_pressed(&self, input: &Input, action_name: &str) -> bool {
278        if let Some(bindings) = self.bindings.get(action_name) {
279            for binding in bindings {
280                match binding {
281                    InputBinding::Key(k) => {
282                        if input.is_key_pressed(*k) {
283                            return true;
284                        }
285                    }
286                    InputBinding::MouseButton(b) => {
287                        if input.is_mouse_button_pressed(*b) {
288                            return true;
289                        }
290                    }
291                }
292            }
293        }
294        false
295    }
296
297    /// Action bu frame'de yeni mi tetiklendi?
298    pub fn is_action_just_pressed(&self, input: &Input, action_name: &str) -> bool {
299        if let Some(bindings) = self.bindings.get(action_name) {
300            for binding in bindings {
301                match binding {
302                    InputBinding::Key(k) => {
303                        if input.is_key_just_pressed(*k) {
304                            return true;
305                        }
306                    }
307                    InputBinding::MouseButton(b) => {
308                        if input.is_mouse_button_just_pressed(*b) {
309                            return true;
310                        }
311                    }
312                }
313            }
314        }
315        false
316    }
317
318    /// Action bu frame'de mi bırakıldı? (Şarj-bırak, toggle gibi mekanikler için)
319    pub fn is_action_just_released(&self, input: &Input, action_name: &str) -> bool {
320        if let Some(bindings) = self.bindings.get(action_name) {
321            for binding in bindings {
322                match binding {
323                    InputBinding::Key(k) => {
324                        if input.is_key_just_released(*k) {
325                            return true;
326                        }
327                    }
328                    InputBinding::MouseButton(b) => {
329                        if input.is_mouse_button_just_released(*b) {
330                            return true;
331                        }
332                    }
333                }
334            }
335        }
336        false
337    }
338}
339
340impl Default for ActionMap {
341    fn default() -> Self {
342        Self::new()
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    // ──── Fast-Tap Testleri ────
351
352    #[test]
353    fn test_fast_tap_preserves_pressed_for_one_frame() {
354        let mut input = Input::new();
355
356        // Aynı frame'de basılıp bırakılan tuş
357        input.on_key_pressed(42);
358        input.on_key_released(42);
359
360        // O frame boyunca hem pressed hem just_pressed true olmalı
361        assert!(input.is_key_pressed(42), "fast-tap: tuş pressed olmalı");
362        assert!(
363            input.is_key_just_pressed(42),
364            "fast-tap: tuş just_pressed olmalı"
365        );
366        assert!(
367            input.is_key_just_released(42),
368            "fast-tap: tuş just_released olmalı"
369        );
370
371        // Sonraki frame
372        input.begin_frame();
373
374        // Artık hiçbiri true olmamalı
375        assert!(
376            !input.is_key_pressed(42),
377            "sonraki frame: pressed false olmalı"
378        );
379        assert!(
380            !input.is_key_just_pressed(42),
381            "sonraki frame: just_pressed false olmalı"
382        );
383        assert!(
384            !input.is_key_just_released(42),
385            "sonraki frame: just_released false olmalı"
386        );
387    }
388
389    #[test]
390    fn test_normal_press_release_across_frames() {
391        let mut input = Input::new();
392
393        // Frame 1: Tuş basıldı
394        input.on_key_pressed(10);
395        assert!(input.is_key_pressed(10));
396        assert!(input.is_key_just_pressed(10));
397
398        // Frame 2: Tuş hâlâ basılı
399        input.begin_frame();
400        assert!(input.is_key_pressed(10));
401        assert!(!input.is_key_just_pressed(10));
402
403        // Frame 3: Tuş bırakıldı
404        input.on_key_released(10);
405        assert!(!input.is_key_pressed(10)); // Normal bırakma — hemen silinir
406        assert!(input.is_key_just_released(10));
407
408        // Frame 4: Temiz
409        input.begin_frame();
410        assert!(!input.is_key_pressed(10));
411        assert!(!input.is_key_just_released(10));
412    }
413
414    #[test]
415    fn test_fast_tap_mouse_button() {
416        let mut input = Input::new();
417
418        input.on_mouse_button_pressed(mouse::LEFT);
419        input.on_mouse_button_released(mouse::LEFT);
420
421        assert!(input.is_mouse_button_pressed(mouse::LEFT));
422        assert!(input.is_mouse_button_just_pressed(mouse::LEFT));
423        assert!(input.is_mouse_button_just_released(mouse::LEFT));
424
425        input.begin_frame();
426
427        assert!(!input.is_mouse_button_pressed(mouse::LEFT));
428        assert!(!input.is_mouse_button_just_pressed(mouse::LEFT));
429        assert!(!input.is_mouse_button_just_released(mouse::LEFT));
430    }
431
432    // ──── Mouse Delta Testleri ────
433
434    #[test]
435    fn test_mouse_moved_accumulates_delta() {
436        let mut input = Input::new();
437
438        input.on_mouse_moved(100.0, 200.0);
439        // İlk hareket: (0,0) → (100,200) = delta (100, 200)
440        assert_eq!(input.mouse_delta(), (100.0, 200.0));
441
442        input.on_mouse_moved(150.0, 250.0);
443        // İkinci hareket: (100,200) → (150,250) = ek delta (50, 50), toplam (150, 250)
444        assert_eq!(input.mouse_delta(), (150.0, 250.0));
445
446        assert_eq!(input.mouse_position(), (150.0, 250.0));
447    }
448
449    #[test]
450    fn test_mouse_delta_resets_on_begin_frame() {
451        let mut input = Input::new();
452
453        input.on_mouse_moved(100.0, 200.0);
454        assert_ne!(input.mouse_delta(), (0.0, 0.0));
455
456        input.begin_frame();
457        assert_eq!(input.mouse_delta(), (0.0, 0.0));
458        // Pozisyon korunmalı
459        assert_eq!(input.mouse_position(), (100.0, 200.0));
460    }
461
462    // ──── Scroll Testleri ────
463
464    #[test]
465    fn test_scroll_accumulates_and_resets() {
466        let mut input = Input::new();
467
468        input.on_mouse_scroll(3.0);
469        input.on_mouse_scroll(-1.0);
470        assert_eq!(input.mouse_scroll(), 2.0);
471
472        input.begin_frame();
473        assert_eq!(input.mouse_scroll(), 0.0);
474    }
475
476    // ──── Pressed Keys ────
477
478    #[test]
479    fn test_pressed_keys() {
480        let mut input = Input::new();
481        input.on_key_pressed(1);
482        input.on_key_pressed(2);
483        input.on_key_pressed(3);
484
485        let mut keys = input.pressed_keys();
486        keys.sort();
487        assert_eq!(keys, vec![1, 2, 3]);
488    }
489
490    // ──── ActionMap Testleri ────
491
492    #[test]
493    fn test_action_map_key_binding() {
494        let mut input = Input::new();
495        let mut actions = ActionMap::new();
496        actions.bind_key("Jump", 42);
497
498        input.on_key_pressed(42);
499        assert!(actions.is_action_pressed(&input, "Jump"));
500        assert!(actions.is_action_just_pressed(&input, "Jump"));
501    }
502
503    #[test]
504    fn test_action_map_mouse_binding() {
505        let mut input = Input::new();
506        let mut actions = ActionMap::new();
507        actions.bind_mouse_button("Attack", mouse::LEFT);
508
509        input.on_mouse_button_pressed(mouse::LEFT);
510        assert!(actions.is_action_pressed(&input, "Attack"));
511        assert!(actions.is_action_just_pressed(&input, "Attack"));
512
513        input.begin_frame();
514        input.on_mouse_button_released(mouse::LEFT);
515        assert!(actions.is_action_just_released(&input, "Attack"));
516    }
517
518    #[test]
519    fn test_action_map_mixed_bindings() {
520        let mut input = Input::new();
521        let mut actions = ActionMap::new();
522        actions.bind_key("Fire", 42);
523        actions.bind_mouse_button("Fire", mouse::LEFT);
524
525        // Hiçbiri basılı değil
526        assert!(!actions.is_action_pressed(&input, "Fire"));
527
528        // Sadece fare basılı
529        input.on_mouse_button_pressed(mouse::LEFT);
530        assert!(actions.is_action_pressed(&input, "Fire"));
531
532        input.begin_frame();
533        input.on_mouse_button_released(mouse::LEFT);
534
535        // Sadece tuş basılı
536        input.on_key_pressed(42);
537        assert!(actions.is_action_pressed(&input, "Fire"));
538    }
539
540    #[test]
541    fn test_action_map_just_released() {
542        let mut input = Input::new();
543        let mut actions = ActionMap::new();
544        actions.bind_key("Charge", 99);
545
546        input.on_key_pressed(99);
547        input.begin_frame();
548        input.on_key_released(99);
549
550        assert!(actions.is_action_just_released(&input, "Charge"));
551        assert!(!actions.is_action_pressed(&input, "Charge"));
552    }
553
554    #[test]
555    fn test_bind_action_backward_compat() {
556        let mut actions = ActionMap::new();
557        actions.bind_action("Jump", 42); // Eski API
558        assert!(matches!(
559            actions.bindings.get("Jump").unwrap()[0],
560            InputBinding::Key(42)
561        ));
562    }
563}
564
565#[derive(Serialize, Deserialize, Clone)]
566pub struct FrameRecord {
567    pub dt: f32,
568    pub input: Input,
569}
570
571#[derive(Serialize, Deserialize, Clone)]
572pub struct PlaybackData {
573    pub frames: Vec<FrameRecord>,
574}
575
576impl PlaybackData {
577    pub fn save(&self, path: &str) -> Result<(), String> {
578        let string_data = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
579            .map_err(|e| format!("Serilestirme hatasi: {}", e))?;
580        std::fs::write(path, string_data).map_err(|e| format!("Dosya yazma hatasi: {}", e))?;
581        Ok(())
582    }
583
584    pub fn load(path: &str) -> Result<Self, String> {
585        let string_data =
586            std::fs::read_to_string(path).map_err(|e| format!("Dosya okuma hatasi: {}", e))?;
587        ron::from_str(&string_data).map_err(|e| format!("Deserilestirme hatasi: {}", e))
588    }
589}
590
591// ==================== FIGHTER INPUT BUFFER ====================
592
593/// Her frame için tuş durumlarını tutan yapı.
594#[derive(Clone, Debug, Serialize, Deserialize)]
595pub struct FrameActions {
596    pub pressed: HashSet<String>,
597    pub just_pressed: HashSet<String>,
598    pub just_released: HashSet<String>,
599}
600
601/// Dövüş oyunları (Gizmo Fight) için özel olarak tasarlanmış Girdi Belleği (Input Buffer).
602/// Son N karedeki tüm tuş hareketlerini hafızada tutarak kombo (Hadouken vb.) algılamayı sağlar.
603#[derive(Clone, Debug, Serialize, Deserialize)]
604pub struct FighterInputBuffer {
605    pub frames: std::collections::VecDeque<FrameActions>,
606    pub max_frames: usize,
607}
608
609impl FighterInputBuffer {
610    /// 60 kare (1 saniye) standart bir buffer boyutu dövüş oyunları için idealdir.
611    pub fn new(max_frames: usize) -> Self {
612        Self {
613            frames: std::collections::VecDeque::with_capacity(max_frames),
614            max_frames,
615        }
616    }
617
618    /// Her oyun karesinde çağrılıp buffer'ı günceller.
619    pub fn update(&mut self, input: &Input, action_map: &ActionMap, actions_to_track: &[&str]) {
620        let mut frame = FrameActions {
621            pressed: HashSet::new(),
622            just_pressed: HashSet::new(),
623            just_released: HashSet::new(),
624        };
625
626        for &action in actions_to_track {
627            if action_map.is_action_pressed(input, action) {
628                frame.pressed.insert(action.to_string());
629            }
630            if action_map.is_action_just_pressed(input, action) {
631                frame.just_pressed.insert(action.to_string());
632            }
633            if action_map.is_action_just_released(input, action) {
634                frame.just_released.insert(action.to_string());
635            }
636        }
637
638        self.frames.push_front(frame);
639        if self.frames.len() > self.max_frames {
640            self.frames.pop_back();
641        }
642    }
643
644    /// Verilen kombo diziliminin son karelerde gerçekleşip gerçekleşmediğini kontrol eder.
645    /// `sequence`: Sırasıyla basılması gereken tuşlar dizisi. Örn: ["Down", "Right", "Punch"]
646    /// `max_gap`: İki tuş basımı arasında geçebilecek maksimum kare sayısı (Hata toleransı).
647    /// Dövüş oyunlarında genellikle 10-15 kare tolerans verilir.
648    pub fn check_combo_strict(&self, sequence: &[&str], max_gap: usize) -> bool {
649        if sequence.is_empty() || self.frames.is_empty() {
650            return false;
651        }
652
653        // Aramaya dizilimin SON tuşundan (en yakın zamandaki) başlıyoruz.
654        // Çünkü `self.frames[0]` mevcut frame'i (şimdi) temsil eder.
655        let mut seq_idx = sequence.len() as isize - 1;
656        let mut frames_since_last_match = 0;
657
658        for frame in &self.frames {
659            if frames_since_last_match > max_gap {
660                // Kombodaki iki tuş arasına çok fazla zaman girmiş, kombo bozuldu.
661                return false;
662            }
663
664            let required_action = sequence[seq_idx as usize];
665
666            // Dövüş oyunlarında yön tuşları 'pressed', saldırı tuşları 'just_pressed' olabilir
667            // ama en güvenlisi kombodaki her adımın 'just_pressed' (yeni basılmış) olmasıdır.
668            if frame.just_pressed.contains(required_action) || frame.pressed.contains(required_action) {
669                // Eşleşme bulundu, komboda bir önceki adıma geç
670                seq_idx -= 1;
671                frames_since_last_match = 0;
672
673                if seq_idx < 0 {
674                    // Dizilimin en başına (ilk tuşa) başarıyla ulaştık! Kombo yapıldı!
675                    return true;
676                }
677            } else {
678                frames_since_last_match += 1;
679            }
680        }
681
682        false
683    }
684}
685
686impl Default for FighterInputBuffer {
687    fn default() -> Self {
688        Self::new(60)
689    }
690}
691
692#[cfg(test)]
693mod fighter_tests {
694    use super::*;
695
696    #[test]
697    fn test_fighter_input_buffer_combo() {
698        let mut buffer = FighterInputBuffer::new(60);
699        let mut input = Input::new();
700        let mut action_map = ActionMap::new();
701
702        // 1. Frame: Sadece Down (pressed olarak gelecek)
703        let mut frame1 = FrameActions {
704            pressed: ["Down".to_string()].into_iter().collect(),
705            just_pressed: [].into_iter().collect(),
706            just_released: [].into_iter().collect(),
707        };
708        buffer.frames.push_front(frame1);
709
710        // 2. Frame: DownRight (Down + Right pressed)
711        let mut frame2 = FrameActions {
712            pressed: ["Down".to_string(), "Right".to_string()].into_iter().collect(),
713            just_pressed: ["Right".to_string()].into_iter().collect(),
714            just_released: [].into_iter().collect(),
715        };
716        buffer.frames.push_front(frame2);
717
718        // 3. Frame: Sadece Right (pressed), Down bırakıldı
719        let mut frame3 = FrameActions {
720            pressed: ["Right".to_string()].into_iter().collect(),
721            just_pressed: [].into_iter().collect(),
722            just_released: ["Down".to_string()].into_iter().collect(),
723        };
724        buffer.frames.push_front(frame3);
725
726        // 4. Frame: Punch (just_pressed)
727        let mut frame4 = FrameActions {
728            pressed: ["LightPunch".to_string()].into_iter().collect(),
729            just_pressed: ["LightPunch".to_string()].into_iter().collect(),
730            just_released: [].into_iter().collect(),
731        };
732        buffer.frames.push_front(frame4);
733
734        // Şimdi kombo arıyoruz: ["Down", "Right", "LightPunch"]
735        let combo = ["Down", "Right", "LightPunch"];
736        
737        // max_gap = 5 kare (Çok rahat yetişir)
738        assert!(buffer.check_combo_strict(&combo, 5), "Kombo basariyla algilanmali");
739        
740        // Kombo sırasını bozarak test edelim
741        let wrong_combo = ["LightPunch", "Right", "Down"];
742        assert!(!buffer.check_combo_strict(&wrong_combo, 5), "Yanlis kombo sirasi algilanmamali");
743    }
744
745    #[test]
746    fn test_fighter_input_buffer_max_gap() {
747        let mut buffer = FighterInputBuffer::new(60);
748
749        let mut frame_down = FrameActions {
750            pressed: ["Down".to_string()].into_iter().collect(),
751            just_pressed: ["Down".to_string()].into_iter().collect(),
752            just_released: [].into_iter().collect(),
753        };
754        buffer.frames.push_front(frame_down);
755
756        // Araya 10 boş kare girsin
757        for _ in 0..10 {
758            let empty = FrameActions {
759                pressed: [].into_iter().collect(),
760                just_pressed: [].into_iter().collect(),
761                just_released: [].into_iter().collect(),
762            };
763            buffer.frames.push_front(empty);
764        }
765
766        let mut frame_punch = FrameActions {
767            pressed: ["LightPunch".to_string()].into_iter().collect(),
768            just_pressed: ["LightPunch".to_string()].into_iter().collect(),
769            just_released: [].into_iter().collect(),
770        };
771        buffer.frames.push_front(frame_punch);
772
773        let combo = ["Down", "LightPunch"];
774        
775        // max_gap = 5 ise başarısız olmalı (10 kare boşluk var)
776        assert!(!buffer.check_combo_strict(&combo, 5), "Cok yavas basildi, algilanmamali");
777        
778        // max_gap = 15 ise başarılı olmalı
779        assert!(buffer.check_combo_strict(&combo, 15), "Max gap genis oldugu icin algilanmali");
780    }
781}
782