tetanes_core/
input.rs

1//! [`Joypad`] and [`Zapper`] implementation.
2
3use crate::{
4    common::{Clock, NesRegion, Reset, ResetKind},
5    cpu::Cpu,
6    ppu::Ppu,
7};
8use bitflags::bitflags;
9use serde::{Deserialize, Serialize};
10use std::str::FromStr;
11use thiserror::Error;
12use tracing::trace;
13
14#[derive(Error, Debug)]
15#[must_use]
16#[error("failed to parse `Player`")]
17pub struct ParsePlayerError;
18
19#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[must_use]
21pub enum Player {
22    #[default]
23    One,
24    Two,
25    Three,
26    Four,
27}
28
29impl std::fmt::Display for Player {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        let s = match self {
32            Self::One => "One",
33            Self::Two => "Two",
34            Self::Three => "Three",
35            Self::Four => "Four",
36        };
37        write!(f, "{s}")
38    }
39}
40
41impl AsRef<str> for Player {
42    fn as_ref(&self) -> &str {
43        match self {
44            Self::One => "one",
45            Self::Two => "two",
46            Self::Three => "three",
47            Self::Four => "four",
48        }
49    }
50}
51
52impl TryFrom<usize> for Player {
53    type Error = ParsePlayerError;
54
55    fn try_from(value: usize) -> Result<Self, Self::Error> {
56        match value {
57            0 => Ok(Self::One),
58            1 => Ok(Self::Two),
59            2 => Ok(Self::Three),
60            3 => Ok(Self::Four),
61            _ => Err(ParsePlayerError),
62        }
63    }
64}
65
66pub trait InputRegisters {
67    fn read(&mut self, player: Player, ppu: &Ppu) -> u8;
68    fn peek(&self, player: Player, ppu: &Ppu) -> u8;
69    fn write(&mut self, val: u8);
70}
71
72#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[must_use]
74pub enum FourPlayer {
75    #[default]
76    Disabled,
77    FourScore,
78    Satellite,
79}
80
81impl FourPlayer {
82    pub const fn as_slice() -> &'static [Self] {
83        &[Self::Disabled, Self::FourScore, Self::Satellite]
84    }
85
86    pub const fn as_str(&self) -> &'static str {
87        match self {
88            Self::Disabled => "disabled",
89            Self::FourScore => "four-score",
90            Self::Satellite => "satellite",
91        }
92    }
93}
94
95impl AsRef<str> for FourPlayer {
96    fn as_ref(&self) -> &str {
97        self.as_str()
98    }
99}
100
101impl std::fmt::Display for FourPlayer {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        let s = match self {
104            Self::Disabled => "Disabled",
105            Self::FourScore => "FourScore",
106            Self::Satellite => "Satellite",
107        };
108        write!(f, "{s}")
109    }
110}
111
112impl FromStr for FourPlayer {
113    type Err = &'static str;
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        match s {
116            "disabled" => Ok(Self::Disabled),
117            "four-score" => Ok(Self::FourScore),
118            "satellite" => Ok(Self::Satellite),
119            _ => Err(
120                "invalid FourPlayer value. valid options: `disabled`, `four-score`, or `satellite`",
121            ),
122        }
123    }
124}
125
126#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
127#[must_use]
128pub struct Input {
129    pub joypads: [Joypad; 4],
130    pub signatures: [Joypad; 2],
131    pub zapper: Zapper,
132    pub turbo_timer: u32,
133    pub four_player: FourPlayer,
134}
135
136impl Input {
137    pub fn new(region: NesRegion) -> Self {
138        Self {
139            joypads: [Joypad::new(); 4],
140            // Signature bits are reversed so they can shift right
141            signatures: [
142                Joypad::from_bytes(0b0000_1000),
143                Joypad::from_bytes(0b0000_0100),
144            ],
145            zapper: Zapper::new(region),
146            turbo_timer: 30,
147            four_player: FourPlayer::default(),
148        }
149    }
150
151    pub const fn joypad(&self, player: Player) -> &Joypad {
152        &self.joypads[player as usize]
153    }
154
155    pub const fn joypad_mut(&mut self, player: Player) -> &mut Joypad {
156        &mut self.joypads[player as usize]
157    }
158
159    pub fn set_region(&mut self, region: NesRegion) {
160        self.zapper.trigger_release_delay = Cpu::region_clock_rate(region) / 10.0;
161    }
162
163    pub fn set_concurrent_dpad(&mut self, enabled: bool) {
164        self.joypads
165            .iter_mut()
166            .for_each(|pad| pad.concurrent_dpad = enabled);
167    }
168
169    pub const fn connect_zapper(&mut self, connected: bool) {
170        self.zapper.connected = connected;
171    }
172
173    pub fn set_four_player(&mut self, four_player: FourPlayer) {
174        self.four_player = four_player;
175        self.reset(ResetKind::Hard);
176    }
177
178    pub fn clear(&mut self) {
179        for pad in &mut self.joypads {
180            pad.clear();
181        }
182        self.zapper.clear();
183    }
184}
185
186impl InputRegisters for Input {
187    fn read(&mut self, player: Player, ppu: &Ppu) -> u8 {
188        // Read $4016/$4017 D0 8x for controller #1/#2.
189        // Read $4016/$4017 D0 8x for controller #3/#4.
190        // Read $4016/$4017 D0 8x for signature: 0b00010000/0b00100000
191        let zapper = if player == Player::Two {
192            self.zapper.read(ppu)
193        } else {
194            0x00
195        };
196
197        let player = player as usize;
198        assert!(player < 4);
199        let val = match self.four_player {
200            FourPlayer::Disabled => self.joypads[player].read(),
201            FourPlayer::FourScore => {
202                if self.joypads[player].index() < 8 {
203                    self.joypads[player].read()
204                } else if self.joypads[player + 2].index() < 8 {
205                    self.joypads[player + 2].read()
206                } else if self.signatures[player].index() < 8 {
207                    self.signatures[player].read()
208                } else {
209                    0x01
210                }
211            }
212            FourPlayer::Satellite => {
213                self.joypads[player].read() | (self.joypads[player + 2].read() << 1)
214            }
215        };
216
217        zapper | val | 0x40
218    }
219
220    fn peek(&self, player: Player, ppu: &Ppu) -> u8 {
221        // Read $4016/$4017 D0 8x for controller #1/#2.
222        // Read $4016/$4017 D0 8x for controller #3/#4.
223        // Read $4016/$4017 D0 8x for signature: 0b00010000/0b00100000
224        let zapper = if player == Player::Two {
225            self.zapper.read(ppu)
226        } else {
227            0x00
228        };
229
230        let player = player as usize;
231        assert!(player < 4);
232        let val = match self.four_player {
233            FourPlayer::Disabled => self.joypads[player].peek(),
234            FourPlayer::FourScore => {
235                if self.joypads[player].index() < 8 {
236                    self.joypads[player].peek()
237                } else if self.joypads[player + 2].index() < 8 {
238                    self.joypads[player + 2].peek()
239                } else if self.signatures[player].index() < 8 {
240                    self.signatures[player].peek()
241                } else {
242                    0x01
243                }
244            }
245            FourPlayer::Satellite => {
246                self.joypads[player].peek() | (self.joypads[player + 2].peek() << 1)
247            }
248        };
249
250        zapper | val | 0x40
251    }
252
253    fn write(&mut self, val: u8) {
254        for pad in &mut self.joypads {
255            pad.write(val);
256        }
257        for sig in &mut self.signatures {
258            sig.write(val);
259        }
260    }
261}
262
263impl Clock for Input {
264    fn clock(&mut self) -> u64 {
265        self.zapper.clock();
266        if self.turbo_timer > 0 {
267            self.turbo_timer -= 1;
268        }
269        if self.turbo_timer == 0 {
270            // Roughly 20Hz
271            self.turbo_timer += 89500;
272            for pad in &mut self.joypads {
273                if pad.button(JoypadBtnState::TURBO_A) {
274                    let pressed = pad.button(JoypadBtnState::A);
275                    pad.set_button(JoypadBtnState::A, !pressed);
276                }
277                if pad.button(JoypadBtnState::TURBO_B) {
278                    let pressed = pad.button(JoypadBtnState::B);
279                    pad.set_button(JoypadBtnState::B, !pressed);
280                }
281            }
282        }
283        1
284    }
285}
286
287impl Reset for Input {
288    fn reset(&mut self, kind: ResetKind) {
289        for pad in &mut self.joypads {
290            pad.reset(kind);
291        }
292        self.signatures[0] = Joypad::from_bytes(0b0000_1000);
293        self.signatures[1] = Joypad::from_bytes(0b0000_0100);
294        self.zapper.reset(kind);
295    }
296}
297
298#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
299pub enum JoypadBtn {
300    /// Left D-Pad.
301    Left,
302    /// Right D-Pad.
303    Right,
304    /// Up D-Pad.
305    Up,
306    /// Down D-Pad.
307    Down,
308    /// A Button.
309    A,
310    /// B Button.
311    B,
312    /// A Button (Turbo).
313    TurboA,
314    /// B Button (Turbo).
315    TurboB,
316    /// Select Button.
317    Select,
318    /// Start Button.
319    Start,
320}
321
322impl AsRef<str> for JoypadBtn {
323    fn as_ref(&self) -> &str {
324        match *self {
325            JoypadBtn::A => "A",
326            JoypadBtn::B => "B",
327            JoypadBtn::Select => "Select",
328            JoypadBtn::Start => "Start",
329            JoypadBtn::Up => "Up",
330            JoypadBtn::Down => "Down",
331            JoypadBtn::Left => "Left",
332            JoypadBtn::Right => "Right",
333            JoypadBtn::TurboA => "A (Turbo)",
334            JoypadBtn::TurboB => "B (Turbo)",
335        }
336    }
337}
338
339bitflags! {
340    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
341    #[must_use]
342    pub struct JoypadBtnState: u16 {
343        const A = 0x01;
344        const B = 0x02;
345        const SELECT = 0x04;
346        const START = 0x08;
347        const UP = 0x10;
348        const DOWN = 0x20;
349        const LEFT = 0x40;
350        const RIGHT = 0x80;
351        const TURBO_A = 0x100;
352        const TURBO_B = 0x200;
353    }
354}
355
356impl From<JoypadBtn> for JoypadBtnState {
357    fn from(button: JoypadBtn) -> Self {
358        match button {
359            JoypadBtn::A => Self::A,
360            JoypadBtn::B => Self::B,
361            JoypadBtn::Select => Self::SELECT,
362            JoypadBtn::Start => Self::START,
363            JoypadBtn::Up => Self::UP,
364            JoypadBtn::Down => Self::DOWN,
365            JoypadBtn::Left => Self::LEFT,
366            JoypadBtn::Right => Self::RIGHT,
367            JoypadBtn::TurboA => Self::TURBO_A,
368            JoypadBtn::TurboB => Self::TURBO_B,
369        }
370    }
371}
372
373#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
374#[must_use]
375pub struct Joypad {
376    pub buttons: JoypadBtnState,
377    pub concurrent_dpad: bool,
378    pub index: u8,
379    pub strobe: bool,
380}
381
382impl Joypad {
383    pub const fn new() -> Self {
384        Self {
385            buttons: JoypadBtnState::empty(),
386            concurrent_dpad: false,
387            index: 0,
388            strobe: false,
389        }
390    }
391
392    #[must_use]
393    pub const fn button(&self, button: JoypadBtnState) -> bool {
394        self.buttons.contains(button)
395    }
396
397    pub fn set_button(&mut self, button: impl Into<JoypadBtnState>, pressed: bool) {
398        let button = button.into();
399        if pressed && !self.concurrent_dpad {
400            if let Some(button) = match button {
401                JoypadBtnState::LEFT => Some(JoypadBtnState::RIGHT),
402                JoypadBtnState::RIGHT => Some(JoypadBtnState::LEFT),
403                JoypadBtnState::UP => Some(JoypadBtnState::DOWN),
404                JoypadBtnState::DOWN => Some(JoypadBtnState::UP),
405                _ => None,
406            } {
407                self.buttons.set(button, false);
408            }
409        }
410        self.buttons.set(button, pressed);
411    }
412
413    pub const fn from_bytes(val: u16) -> Self {
414        Self {
415            buttons: JoypadBtnState::from_bits_truncate(val),
416            concurrent_dpad: false,
417            index: 0,
418            strobe: false,
419        }
420    }
421
422    #[must_use]
423    pub const fn read(&mut self) -> u8 {
424        let val = self.peek();
425        if !self.strobe && self.index < 8 {
426            self.index += 1;
427        }
428        val
429    }
430
431    #[must_use]
432    pub const fn peek(&self) -> u8 {
433        if self.index < 8 {
434            ((self.buttons.bits() as u8) & (1 << self.index)) >> self.index
435        } else {
436            0x01
437        }
438    }
439
440    pub const fn write(&mut self, val: u8) {
441        let prev_strobe = self.strobe;
442        self.strobe = val & 0x01 == 0x01;
443        if prev_strobe && !self.strobe {
444            self.index = 0;
445        }
446    }
447
448    #[must_use]
449    pub const fn index(&self) -> u8 {
450        self.index
451    }
452
453    pub const fn clear(&mut self) {
454        self.buttons = JoypadBtnState::empty();
455    }
456}
457
458impl Reset for Joypad {
459    fn reset(&mut self, _kind: ResetKind) {
460        self.buttons = JoypadBtnState::empty();
461        self.index = 0;
462        self.strobe = false;
463    }
464}
465
466#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
467#[must_use]
468pub struct Zapper {
469    #[serde(skip)] // Don't save triggered state
470    pub triggered: f32,
471    pub trigger_release_delay: f32,
472    #[serde(skip)] // Don't save zapper position
473    pub x: u32,
474    #[serde(skip)] // Don't save zapper position
475    pub y: u32,
476    pub radius: u32,
477    pub connected: bool,
478}
479
480impl Zapper {
481    #[must_use]
482    pub const fn x(&self) -> u32 {
483        self.x
484    }
485
486    #[must_use]
487    pub const fn y(&self) -> u32 {
488        self.y
489    }
490
491    pub fn trigger(&mut self) {
492        if self.triggered <= 0.0 {
493            self.triggered = self.trigger_release_delay;
494        }
495    }
496
497    pub fn aim(&mut self, x: u32, y: u32) {
498        if x != self.x || y != self.y {
499            trace!("zapper aim: {x}, {y}");
500        }
501        self.x = x;
502        self.y = y;
503    }
504
505    pub const fn clear(&mut self) {
506        self.triggered = 0.0;
507    }
508}
509
510impl Zapper {
511    fn new(region: NesRegion) -> Self {
512        Self {
513            triggered: 0.0,
514            // Zapper takes ~100ms to change to "released" after trigger is pulled
515            trigger_release_delay: Cpu::region_clock_rate(region) / 10.0,
516            x: 0,
517            y: 0,
518            radius: 3,
519            connected: false,
520        }
521    }
522
523    #[must_use]
524    fn read(&self, ppu: &Ppu) -> u8 {
525        if self.connected {
526            self.triggered() | self.light_sense(ppu)
527        } else {
528            0x00
529        }
530    }
531
532    fn triggered(&self) -> u8 {
533        if self.triggered > 0.0 { 0x10 } else { 0x00 }
534    }
535
536    fn light_sense(&self, ppu: &Ppu) -> u8 {
537        let width = Ppu::WIDTH;
538        let height = Ppu::HEIGHT;
539        let scanline = ppu.scanline;
540        let cycle = ppu.cycle;
541        let min_y = self.y.saturating_sub(self.radius);
542        let max_y = (self.y + self.radius).min(height - 1);
543        let min_x = self.x.saturating_sub(self.radius);
544        let max_x = (self.x + self.radius).min(width - 1);
545        for y in min_y..=max_y {
546            for x in min_x..=max_x {
547                let behind_ppu =
548                    scanline >= y && (scanline - y) <= 20 && (scanline != y || cycle > x);
549                let brightness = ppu.pixel_brightness(x, y);
550                if behind_ppu && brightness >= 85 {
551                    trace!("zapper light: {brightness}");
552                    return 0x00;
553                }
554            }
555        }
556        0x08
557    }
558}
559
560impl Clock for Zapper {
561    fn clock(&mut self) -> u64 {
562        if self.triggered > 0.0 {
563            self.triggered -= 1.0;
564            1
565        } else {
566            0
567        }
568    }
569}
570
571impl Reset for Zapper {
572    fn reset(&mut self, _kind: ResetKind) {
573        self.triggered = 0.0;
574    }
575}