From 69d44cda4e6e87ee53216523b6e689bb5c6ffb50 Mon Sep 17 00:00:00 2001 From: Denis Nutiu Date: Mon, 9 Dec 2024 21:23:55 +0200 Subject: [PATCH] implement input module --- README.md | 25 ++++++---- src/emulator.rs | 28 ++++++++---- src/input.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0bdb0fd..0226681 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,23 @@ The project is written in Rust and it's organized in the following modules: ```shell . -├── display.rs # The screen / display module. -├── emulator.rs # The emulator logic which emulates the CPU. -├── input.rs # The input logic. -├── instruction.rs # The instruction decoding logic. -├── main.rs # The main file. This is the entrypoint. -├── sound.rs # The sound module. -└── stack.rs # A stack implementation. - +├── Cargo.lock +├── Cargo.toml +├── LICENSE +├── proprietary_roms # Roms which I have no permission to share here. +├── README.md +├── roms +│   ├── 1-chip8-logo.ch8 # Chip8 Logo Test ROM +│   ├── 3-corax+.ch8 # Corax+ Instructions Test ROM +│   └── ibm-logo.ch8 # IBM Logo Test ROM +├── src +│   ├── display.rs # The screen / display module. +│   ├── emulator.rs # The emulator logic which emulates the CPU. +│   ├── input.rs # The input logic. +│   ├── instruction.rs # The instruction decoding logic. +│   ├── main.rs # The main file. This is the entrypoint. +│   ├── sound.rs # The sound module. +│   └── stack.rs # A stack implementation. ``` diff --git a/src/emulator.rs b/src/emulator.rs index b83625a..146fc9f 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,5 +1,6 @@ use crate::display::Display; use crate::display::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; +use crate::input::InputModule; use crate::instruction::{Instruction, ProcessorInstruction}; use crate::sound::SoundModule; use crate::stack::Stack; @@ -34,10 +35,11 @@ const FONT_SPRITES: [u8; 80] = [ ]; /// Emulator emulates the Chip8 CPU. -pub struct Emulator +pub struct Emulator where D: Display, S: SoundModule, + I: InputModule, { /// Memory represents the emulator's memory. memory: [u8; MEMORY_SIZE], @@ -58,25 +60,28 @@ where display: D, /// The sound module for making sounds. sound_module: S, + /// The module responsible for receiving user input. + input_module: I, /// The stack of the emulator. stack: Stack, /// Holds the display data, each bit corresponds to a pixel. display_data: [bool; DISPLAY_WIDTH * DISPLAY_HEIGHT], + /// Tracks the last key pressed by the user. + last_key_pressed: Option, } -impl Emulator +impl Emulator where D: Display, S: SoundModule, + I: InputModule, { /// Creates a new `Emulator` instance. /// - pub fn new(display: D, sound_module: S) -> Emulator { + pub fn new(display: D, sound_module: S, input_module: I) -> Emulator { let mut emulator = Emulator { memory: [0; MEMORY_SIZE], registers: [0; NUMBER_OF_REGISTERS], - display, - sound_module, index_register: 0, program_counter: 0, delay_timer: 0, @@ -84,6 +89,10 @@ where stack_pointer: 0, stack: Stack::new(), display_data: [false; DISPLAY_WIDTH * DISPLAY_HEIGHT], + display, + sound_module, + input_module, + last_key_pressed: None, }; emulator.load_font_data(); @@ -154,7 +163,9 @@ where } /// Handle the input - fn handle_input(&mut self) {} + fn handle_input(&mut self) { + self.last_key_pressed = self.input_module.get_key_pressed(); + } /// Should make an audible beep. fn do_beep(&mut self) { @@ -439,12 +450,13 @@ where mod tests { use super::*; use crate::display::TerminalDisplay; + use crate::input::NoInput; use crate::sound::TerminalSound; use pretty_assertions::assert_eq; #[test] fn test_load_font_data() { - let emulator = Emulator::new(TerminalDisplay::new(), TerminalSound); + let emulator = Emulator::new(TerminalDisplay::new(), TerminalSound, NoInput); assert_eq!(emulator.memory[0xf0..0xf0 + 80], FONT_SPRITES) } @@ -457,7 +469,7 @@ mod tests { .expect("Failed to read test ROM"); // Test - let mut emulator = Emulator::new(TerminalDisplay::new(), TerminalSound); + let mut emulator = Emulator::new(TerminalDisplay::new(), TerminalSound, NoInput); emulator .load_rom("roms/ibm-logo.ch8") .expect("failed to load ROM"); diff --git a/src/input.rs b/src/input.rs index 42c422c..3a2913c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1 +1,118 @@ -// TODO: grab keyboard keys and interrupt emulation cycle +use crossterm::event::{poll, read, Event, KeyCode}; +use crossterm::terminal::enable_raw_mode; +use std::time::Duration; + +/// InputModule retrieves the keys from the hardware or software input control module. +pub trait InputModule { + /// Returns the key value of the corresponding pressed key. + /// None if no key is pressed. + fn get_key_pressed(&mut self) -> Option; +} + +/// NoInput always returns none when queried for input. +pub struct NoInput; + +impl InputModule for NoInput { + fn get_key_pressed(&mut self) -> Option { + None + } +} + +/// CrossTermInput implements input events via the crossterm crate. +pub struct CrossTermInput { + initialized: bool, +} + +impl CrossTermInput { + pub fn new() -> Self { + enable_raw_mode().expect("failed to enable terminal raw mode."); + CrossTermInput { initialized: true } + } +} + +impl Default for CrossTermInput { + fn default() -> Self { + CrossTermInput::new() + } +} + +impl InputModule for CrossTermInput { + fn get_key_pressed(&mut self) -> Option { + if !self.initialized { + panic!("CrossTermInput needs to be constructed using ::new") + } + if let Ok(true) = poll(Duration::from_millis(2)) { + // It's guaranteed that read() won't block if `poll` returns `Ok(true)` + let read_result = read(); + + if let Ok(event) = read_result { + match event { + Event::Key(key_event) => match key_event.code { + KeyCode::Char(character) => { + let lowercase_character = character.to_lowercase(); + for char in lowercase_character { + match char { + '1' => { + return Some(1); + } + '2' => { + return Some(2); + } + '3' => { + return Some(3); + } + '4' => { + return Some(0xC); + } + 'q' => { + return Some(4); + } + 'w' => { + return Some(5); + } + 'e' => { + return Some(6); + } + 'r' => { + return Some(0xD); + } + 'a' => { + return Some(7); + } + 's' => { + return Some(8); + } + 'd' => { + return Some(9); + } + 'f' => { + return Some(0xE); + } + 'z' => { + return Some(0xA); + } + 'x' => { + return Some(0); + } + 'c' => { + return Some(0xB); + } + 'v' => { + return Some(0xF); + } + _ => {} + } + } + } + _ => {} + }, + // ignore non key events + _ => {} + } + } + + return None; + }; + None + } +}