Skip to main content

pixel8_runtime/
input.rs

1//! Game controller state: the classic 6-button pad.
2//!
3//! Carts see input only through `btn`/`btnp`. The host maps physical keys
4//! to these buttons (arrows + Z/X by default) and ticks this state once
5//! per logical frame.
6
7/// Button indices, matching the ABI and the classic layout.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum Button {
11    Left = 0,
12    Right = 1,
13    Up = 2,
14    Down = 3,
15    /// "O" action button (Z / C / N on the keyboard).
16    O = 4,
17    /// "X" action button (X / V / M on the keyboard).
18    X = 5,
19}
20
21pub const BUTTON_COUNT: usize = 6;
22
23/// Frames a button must be held before `btnp` starts repeating.
24const REPEAT_DELAY: u32 = 15;
25/// Repeat interval in frames once repeating.
26const REPEAT_EVERY: u32 = 4;
27
28/// Per-frame button state with press/repeat tracking.
29#[derive(Default)]
30pub struct InputState {
31    held: [bool; BUTTON_COUNT],
32    frames_held: [u32; BUTTON_COUNT],
33}
34
35impl InputState {
36    /// Update the raw held state of a button (called on key events).
37    pub fn set_button(&mut self, b: usize, down: bool) {
38        if b < BUTTON_COUNT {
39            self.held[b] = down;
40        }
41    }
42
43    /// Advance one logical frame. Must be called exactly once per update.
44    pub fn tick(&mut self) {
45        for i in 0..BUTTON_COUNT {
46            if self.held[i] {
47                self.frames_held[i] = self.frames_held[i].saturating_add(1);
48            } else {
49                self.frames_held[i] = 0;
50            }
51        }
52    }
53
54    /// Is the button currently held?
55    pub fn btn(&self, b: u32) -> bool {
56        (b as usize) < BUTTON_COUNT && self.held[b as usize]
57    }
58
59    /// Was the button just pressed this frame? Repeats after a short delay
60    /// while held, matching the classic `btnp` feel.
61    pub fn btnp(&self, b: u32) -> bool {
62        let i = b as usize;
63        if i >= BUTTON_COUNT {
64            return false;
65        }
66        let f = self.frames_held[i];
67        f == 1 || (f > REPEAT_DELAY && (f - REPEAT_DELAY) % REPEAT_EVERY == 1)
68    }
69
70    /// Bitmask of all currently-held buttons (bit `i` == button `i`).
71    pub fn btn_mask(&self) -> u32 {
72        let mut mask = 0;
73        for i in 0..BUTTON_COUNT {
74            if self.held[i] {
75                mask |= 1 << i;
76            }
77        }
78        mask
79    }
80
81    /// Bitmask of all buttons that fired this frame, with repeat
82    /// (bit `i` == button `i`), matching `btnp`.
83    pub fn btnp_mask(&self) -> u32 {
84        let mut mask = 0;
85        for i in 0..BUTTON_COUNT {
86            if self.btnp(i as u32) {
87                mask |= 1 << i;
88            }
89        }
90        mask
91    }
92
93    /// Clear all held buttons (e.g. when leaving run mode).
94    pub fn clear(&mut self) {
95        self.held = [false; BUTTON_COUNT];
96        self.frames_held = [0; BUTTON_COUNT];
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn btn_reflects_held_state() {
106        let mut s = InputState::default();
107        s.set_button(Button::Right as usize, true);
108        s.tick();
109        assert!(s.btn(1));
110        assert!(!s.btn(0));
111    }
112
113    #[test]
114    fn btnp_fires_once_then_repeats() {
115        let mut s = InputState::default();
116        s.set_button(0, true);
117        s.tick();
118        assert!(s.btnp(0), "fires on first frame");
119        s.tick();
120        assert!(!s.btnp(0), "does not fire on second frame");
121        // Hold until just past the repeat delay (frame REPEAT_DELAY + 1).
122        for _ in 0..(REPEAT_DELAY - 1) {
123            s.tick();
124        }
125        assert!(s.btnp(0), "repeats after delay");
126        s.tick();
127        assert!(!s.btnp(0));
128    }
129
130    #[test]
131    fn release_resets_press() {
132        let mut s = InputState::default();
133        s.set_button(0, true);
134        s.tick();
135        s.set_button(0, false);
136        s.tick();
137        s.set_button(0, true);
138        s.tick();
139        assert!(s.btnp(0), "fires again after release");
140    }
141
142    #[test]
143    fn btn_mask_sets_held_bits() {
144        let mut s = InputState::default();
145        s.set_button(Button::Left as usize, true);
146        s.set_button(Button::X as usize, true);
147        s.tick();
148        assert_eq!(s.btn_mask(), 0b10_0001); // bit 0 Left, bit 5 X
149    }
150
151    #[test]
152    fn btnp_mask_fires_then_clears() {
153        let mut s = InputState::default();
154        s.set_button(2, true); // Up
155        s.tick();
156        assert_eq!(s.btnp_mask(), 1 << 2);
157        s.tick();
158        assert_eq!(s.btnp_mask(), 0);
159    }
160}