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        // Cancel a pending fast-tap deferral: if the key was released and re-pressed
85        // within the SAME frame, `begin_frame` would otherwise honor the earlier
86        // deferred removal and drop a physically-held key (then spuriously re-fire
87        // just_pressed on the next auto-repeat).
88        self.keys_just_released.remove(&key);
89        if self.keys_pressed.insert(key) {
90            self.keys_just_pressed.insert(key);
91        }
92    }
93
94    /// Tuş bırakıldığında çağır.
95    ///
96    /// Eğer tuş aynı frame'de basılıp bırakıldıysa (`keys_just_pressed` içindeyse),
97    /// `keys_pressed`'den silmeyi `begin_frame()`'e erteler. Böylece oyun bu "fast tap"ı
98    /// kaçırmaz — hem `is_key_pressed` hem `is_key_just_pressed` o frame boyunca true döner.
99    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            // Normal bırakma — hemen sil
103            self.keys_pressed.remove(&key);
104        }
105        // else: fast-tap — begin_frame()'de silinecek
106    }
107
108    /// Basılı tüm tuş ve fare düğmelerini bırakılmış sayar (odak kaybı için).
109    ///
110    /// Pencere/canvas odağı kaybettiğinde (Alt-Tab, tarayıcı sekmesi değişimi)
111    /// işletim sistemi artık key-up olayı GÖNDERMEZ → o an basılı olan tuşlar
112    /// sonsuza dek "basılı" kalır ve kamera/karakter kayıp gider. Bu, tüm
113    /// basılı durumları temizler; hâlâ fiziksel olarak basılı bir tuş, odak
114    /// geri gelince yeni bir key-down ile yeniden kaydolur.
115    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    /// Tuş şu an basılı mı? (sürekli kontrol)
127    #[inline]
128    pub fn is_key_pressed(&self, key: u32) -> bool {
129        self.keys_pressed.contains(&key)
130    }
131
132    /// Tuş bu frame'de mi basıldı? (tek seferlik tetikleme)
133    #[inline]
134    pub fn is_key_just_pressed(&self, key: u32) -> bool {
135        self.keys_just_pressed.contains(&key)
136    }
137
138    /// Tuş bu frame'de mi bırakıldı?
139    #[inline]
140    pub fn is_key_just_released(&self, key: u32) -> bool {
141        self.keys_just_released.contains(&key)
142    }
143
144    // ==================== FARE GİRDİSİ ====================
145
146    /// Fare butonu basıldığında çağır (0=Left, 1=Right, 2=Middle)
147    pub fn on_mouse_button_pressed(&mut self, button: u32) {
148        // See `on_key_pressed`: a re-press cancels a same-frame fast-tap deferral.
149        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    /// Fare butonu bırakıldığında çağır
156    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    /// Fare butonu basılı mı?
164    #[inline]
165    pub fn is_mouse_button_pressed(&self, button: u32) -> bool {
166        self.mouse_buttons_pressed.contains(&button)
167    }
168
169    /// Fare butonu bu frame'de mi basıldı?
170    #[inline]
171    pub fn is_mouse_button_just_pressed(&self, button: u32) -> bool {
172        self.mouse_buttons_just_pressed.contains(&button)
173    }
174
175    /// Fare butonu bu frame'de mi bırakıldı?
176    #[inline]
177    pub fn is_mouse_button_just_released(&self, button: u32) -> bool {
178        self.mouse_buttons_just_released.contains(&button)
179    }
180
181    // ==================== FARE POZİSYONU ====================
182
183    /// Fare ekran pozisyonu değiştiğinde çağır.
184    /// Pozisyon farkından delta biriktirilir — `DeviceEvent::MouseMotion`
185    /// olmayan platformlarda (web, bazı Linux konfigürasyonları) fallback sağlar.
186    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    /// Fare ekran pozisyonunu günceller — delta BİRİKTİRMEZ.
193    /// `DeviceEvent::MouseMotion` sağlayan platformlarda (masaüstü) delta o kanaldan
194    /// (`on_mouse_delta`) gelir; `CursorMoved` yalnızca mutlak pozisyonu taşımalı,
195    /// aksi halde ikisi birden delta'yı İKİ KEZ sayar (2× fare-bakış hassasiyeti).
196    pub fn set_mouse_position(&mut self, x: f32, y: f32) {
197        self.mouse_position = (x, y);
198    }
199
200    /// Fare delta hareketi (DeviceEvent::MouseMotion).
201    /// `on_mouse_moved` zaten delta biriktirdiği için, bu metot yalnızca
202    /// platform `DeviceEvent::MouseMotion` veriyorsa ek doğruluk sağlar.
203    /// İkisi birlikte çağrılmamalı — platform'a göre birini kullanın.
204    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    /// Fare ekran pozisyonu
210    #[inline]
211    pub fn mouse_position(&self) -> (f32, f32) {
212        self.mouse_position
213    }
214
215    /// Bu frame'deki fare hareketi (delta)
216    #[inline]
217    pub fn mouse_delta(&self) -> (f32, f32) {
218        self.mouse_delta
219    }
220
221    // ==================== FARE TEKERLEK (SCROLL) ====================
222
223    /// Fare tekerleği hareket ettiğinde çağır.
224    /// Pozitif = yukarı/ileri, negatif = aşağı/geri.
225    pub fn on_mouse_scroll(&mut self, delta: f32) {
226        self.mouse_scroll_delta += delta;
227    }
228
229    /// Bu frame'deki fare tekerlek deltası.
230    /// Pozitif = yukarı/ileri, negatif = aşağı/geri.
231    #[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
243/// Fare buton sabitleri
244pub mod mouse {
245    pub const LEFT: u32 = 0;
246    pub const RIGHT: u32 = 1;
247    pub const MIDDLE: u32 = 2;
248}
249
250// ==================== ACTION MAP (Tuş Soyutlama) ====================
251
252use std::collections::HashMap;
253
254/// Girdi binding türü — klavye tuşu veya fare butonu.
255#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
256pub enum InputBinding {
257    /// Klavye tuşu (winit KeyCode as u32)
258    Key(u32),
259    /// Fare butonu (mouse::LEFT, mouse::RIGHT, mouse::MIDDLE)
260    MouseButton(u32),
261}
262
263/// Evrensel Girdi Çevirici.
264/// "W" veya "Yukarı Ok" tuşlarını doğrudan kontrol etmek yerine,
265/// "Accelerate" veya "Jump" gibi mantıksal isimlendirmelerle dinlememizi sağlar.
266///
267/// # Örnek
268/// ```rust,ignore
269/// let mut actions = ActionMap::new();
270/// actions.bind_key("Jump", KeyCode::Space as u32);
271/// actions.bind_mouse_button("Attack", mouse::LEFT);
272///
273/// if actions.is_action_just_pressed(&input, "Jump") { player.jump(); }
274/// if actions.is_action_pressed(&input, "Attack") { player.attack(); }
275/// ```
276#[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    /// Bir isme (Action) klavye tuşu bağlar
289    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    /// Bir isme (Action) fare butonu bağlar
297    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    /// Geriye dönük uyumluluk — `bind_key()` ile aynı.
305    pub fn bind_action(&mut self, action_name: &str, keycode: u32) {
306        self.bind_key(action_name, keycode);
307    }
308
309    /// Action (eylem) şu an uygulanıyor mu? (Basılı tutuluyor mu)
310    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    /// Action bu frame'de yeni mi tetiklendi?
331    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    /// Action bu frame'de mi bırakıldı? (Şarj-bırak, toggle gibi mekanikler için)
352    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    /// A held key released and re-pressed within the SAME frame must STAY held.
384    /// The release defers removal to begin_frame (fast-tap protection); without
385    /// cancelling that deferral on the re-press, begin_frame dropped the physically
386    /// held key (and it then spuriously re-fired just_pressed on auto-repeat).
387    #[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(); // 5 is now a plain held key
392        assert!(input.is_key_pressed(5));
393
394        // Same frame: release, then immediately re-press.
395        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    // ──── Fast-Tap Testleri ────
404
405    #[test]
406    fn test_fast_tap_preserves_pressed_for_one_frame() {
407        let mut input = Input::new();
408
409        // Aynı frame'de basılıp bırakılan tuş
410        input.on_key_pressed(42);
411        input.on_key_released(42);
412
413        // O frame boyunca hem pressed hem just_pressed true olmalı
414        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        // Sonraki frame
425        input.begin_frame();
426
427        // Artık hiçbiri true olmamalı
428        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        // Frame 1: Tuş basıldı
447        input.on_key_pressed(10);
448        assert!(input.is_key_pressed(10));
449        assert!(input.is_key_just_pressed(10));
450
451        // Frame 2: Tuş hâlâ basılı
452        input.begin_frame();
453        assert!(input.is_key_pressed(10));
454        assert!(!input.is_key_just_pressed(10));
455
456        // Frame 3: Tuş bırakıldı
457        input.on_key_released(10);
458        assert!(!input.is_key_pressed(10)); // Normal bırakma — hemen silinir
459        assert!(input.is_key_just_released(10));
460
461        // Frame 4: Temiz
462        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    // ──── Mouse Delta Testleri ────
486
487    #[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        // İlk hareket: (0,0) → (100,200) = delta (100, 200)
493        assert_eq!(input.mouse_delta(), (100.0, 200.0));
494
495        input.on_mouse_moved(150.0, 250.0);
496        // İkinci hareket: (100,200) → (150,250) = ek delta (50, 50), toplam (150, 250)
497        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        // Pozisyon korunmalı
512        assert_eq!(input.mouse_position(), (100.0, 200.0));
513    }
514
515    // ──── Odak Kaybı (release_all) Testleri ────
516
517    #[test]
518    fn test_release_all_clears_held_keys_and_buttons() {
519        let mut input = Input::new();
520        input.on_key_pressed(65); // 'A' basılı tutuluyor
521        input.on_key_pressed(87); // 'W' basılı tutuluyor
522        input.on_mouse_button_pressed(1);
523        input.on_mouse_moved(10.0, 10.0);
524        input.begin_frame(); // just_pressed temizlenir, pressed KALIR
525        assert!(input.is_key_pressed(65));
526        assert!(input.is_key_pressed(87));
527        assert!(input.is_mouse_button_pressed(1));
528
529        // Odak kaybı: OS artık key-up göndermez → release_all hepsini bırakmalı.
530        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        // Bırakma bu frame'de just_released olarak görünür (temiz kenar).
536        assert!(input.is_key_just_released(65));
537    }
538
539    // ──── Scroll Testleri ────
540
541    #[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    // ──── Pressed Keys ────
554
555    #[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    // ──── ActionMap Testleri ────
568
569    #[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        // Hiçbiri basılı değil
603        assert!(!actions.is_action_pressed(&input, "Fire"));
604
605        // Sadece fare basılı
606        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        // Sadece tuş basılı
613        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); // Eski API
635        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// ==================== FIGHTER INPUT BUFFER ====================
669
670/// Her frame için tuş durumlarını tutan yapı.
671#[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/// Dövüş oyunları (Gizmo Fight) için özel olarak tasarlanmış Girdi Belleği (Input Buffer).
679/// Son N karedeki tüm tuş hareketlerini hafızada tutarak kombo (Hadouken vb.) algılamayı sağlar.
680#[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    /// 60 kare (1 saniye) standart bir buffer boyutu dövüş oyunları için idealdir.
688    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    /// Her oyun karesinde çağrılıp buffer'ı günceller.
696    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    /// Verilen kombo diziliminin son karelerde gerçekleşip gerçekleşmediğini kontrol eder.
722    /// `sequence`: Sırasıyla basılması gereken tuşlar dizisi. Örn: ["Down", "Right", "Punch"]
723    /// `max_gap`: İki tuş basımı arasında geçebilecek maksimum kare sayısı (Hata toleransı).
724    /// Dövüş oyunlarında genellikle 10-15 kare tolerans verilir.
725    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        // Aramaya dizilimin SON tuşundan (en yakın zamandaki) başlıyoruz.
731        // Çünkü `self.frames[0]` mevcut frame'i (şimdi) temsil eder.
732        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                // Kombodaki iki tuş arasına çok fazla zaman girmiş, kombo bozuldu.
738                return false;
739            }
740
741            let required_action = sequence[seq_idx as usize];
742
743            // Dövüş oyunlarında yön tuşları 'pressed', saldırı tuşları 'just_pressed' olabilir
744            // ama en güvenlisi kombodaki her adımın 'just_pressed' (yeni basılmış) olmasıdır.
745            if frame.just_pressed.contains(required_action) || frame.pressed.contains(required_action) {
746                // Eşleşme bulundu, komboda bir önceki adıma geç
747                seq_idx -= 1;
748                frames_since_last_match = 0;
749
750                if seq_idx < 0 {
751                    // Dizilimin en başına (ilk tuşa) başarıyla ulaştık! Kombo yapıldı!
752                    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        // 1. Frame: Sadece Down (pressed olarak gelecek)
780        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        // 2. Frame: DownRight (Down + Right pressed)
788        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        // 3. Frame: Sadece Right (pressed), Down bırakıldı
796        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        // 4. Frame: Punch (just_pressed)
804        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        // Şimdi kombo arıyoruz: ["Down", "Right", "LightPunch"]
812        let combo = ["Down", "Right", "LightPunch"];
813        
814        // max_gap = 5 kare (Çok rahat yetişir)
815        assert!(buffer.check_combo_strict(&combo, 5), "Kombo basariyla algilanmali");
816        
817        // Kombo sırasını bozarak test edelim
818        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        // Araya 10 boş kare girsin
834        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        // max_gap = 5 ise başarısız olmalı (10 kare boşluk var)
853        assert!(!buffer.check_combo_strict(&combo, 5), "Cok yavas basildi, algilanmamali");
854        
855        // max_gap = 15 ise başarılı olmalı
856        assert!(buffer.check_combo_strict(&combo, 15), "Max gap genis oldugu icin algilanmali");
857    }
858}
859