implement input module

This commit is contained in:
Denis-Cosmin Nutiu 2024-12-09 21:23:55 +02:00
parent 9334c7752d
commit 69d44cda4e
3 changed files with 155 additions and 17 deletions

View file

@ -29,14 +29,23 @@ The project is written in Rust and it's organized in the following modules:
```shell ```shell
. .
├── display.rs # The screen / display module. ├── Cargo.lock
├── emulator.rs # The emulator logic which emulates the CPU. ├── Cargo.toml
├── input.rs # The input logic. ├── LICENSE
├── instruction.rs # The instruction decoding logic. ├── proprietary_roms # Roms which I have no permission to share here.
├── main.rs # The main file. This is the entrypoint. ├── README.md
├── sound.rs # The sound module. ├── roms
└── stack.rs # A stack implementation. │   ├── 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.
``` ```

View file

@ -1,5 +1,6 @@
use crate::display::Display; use crate::display::Display;
use crate::display::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; use crate::display::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
use crate::input::InputModule;
use crate::instruction::{Instruction, ProcessorInstruction}; use crate::instruction::{Instruction, ProcessorInstruction};
use crate::sound::SoundModule; use crate::sound::SoundModule;
use crate::stack::Stack; use crate::stack::Stack;
@ -34,10 +35,11 @@ const FONT_SPRITES: [u8; 80] = [
]; ];
/// Emulator emulates the Chip8 CPU. /// Emulator emulates the Chip8 CPU.
pub struct Emulator<D, S> pub struct Emulator<D, S, I>
where where
D: Display, D: Display,
S: SoundModule, S: SoundModule,
I: InputModule,
{ {
/// Memory represents the emulator's memory. /// Memory represents the emulator's memory.
memory: [u8; MEMORY_SIZE], memory: [u8; MEMORY_SIZE],
@ -58,25 +60,28 @@ where
display: D, display: D,
/// The sound module for making sounds. /// The sound module for making sounds.
sound_module: S, sound_module: S,
/// The module responsible for receiving user input.
input_module: I,
/// The stack of the emulator. /// The stack of the emulator.
stack: Stack<u16>, stack: Stack<u16>,
/// Holds the display data, each bit corresponds to a pixel. /// Holds the display data, each bit corresponds to a pixel.
display_data: [bool; DISPLAY_WIDTH * DISPLAY_HEIGHT], display_data: [bool; DISPLAY_WIDTH * DISPLAY_HEIGHT],
/// Tracks the last key pressed by the user.
last_key_pressed: Option<u8>,
} }
impl<D, S> Emulator<D, S> impl<D, S, I> Emulator<D, S, I>
where where
D: Display, D: Display,
S: SoundModule, S: SoundModule,
I: InputModule,
{ {
/// Creates a new `Emulator` instance. /// Creates a new `Emulator` instance.
/// ///
pub fn new(display: D, sound_module: S) -> Emulator<D, S> { pub fn new(display: D, sound_module: S, input_module: I) -> Emulator<D, S, I> {
let mut emulator = Emulator { let mut emulator = Emulator {
memory: [0; MEMORY_SIZE], memory: [0; MEMORY_SIZE],
registers: [0; NUMBER_OF_REGISTERS], registers: [0; NUMBER_OF_REGISTERS],
display,
sound_module,
index_register: 0, index_register: 0,
program_counter: 0, program_counter: 0,
delay_timer: 0, delay_timer: 0,
@ -84,6 +89,10 @@ where
stack_pointer: 0, stack_pointer: 0,
stack: Stack::new(), stack: Stack::new(),
display_data: [false; DISPLAY_WIDTH * DISPLAY_HEIGHT], display_data: [false; DISPLAY_WIDTH * DISPLAY_HEIGHT],
display,
sound_module,
input_module,
last_key_pressed: None,
}; };
emulator.load_font_data(); emulator.load_font_data();
@ -154,7 +163,9 @@ where
} }
/// Handle the input /// 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. /// Should make an audible beep.
fn do_beep(&mut self) { fn do_beep(&mut self) {
@ -439,12 +450,13 @@ where
mod tests { mod tests {
use super::*; use super::*;
use crate::display::TerminalDisplay; use crate::display::TerminalDisplay;
use crate::input::NoInput;
use crate::sound::TerminalSound; use crate::sound::TerminalSound;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn test_load_font_data() { 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) assert_eq!(emulator.memory[0xf0..0xf0 + 80], FONT_SPRITES)
} }
@ -457,7 +469,7 @@ mod tests {
.expect("Failed to read test ROM"); .expect("Failed to read test ROM");
// Test // Test
let mut emulator = Emulator::new(TerminalDisplay::new(), TerminalSound); let mut emulator = Emulator::new(TerminalDisplay::new(), TerminalSound, NoInput);
emulator emulator
.load_rom("roms/ibm-logo.ch8") .load_rom("roms/ibm-logo.ch8")
.expect("failed to load ROM"); .expect("failed to load ROM");

View file

@ -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<u8>;
}
/// NoInput always returns none when queried for input.
pub struct NoInput;
impl InputModule for NoInput {
fn get_key_pressed(&mut self) -> Option<u8> {
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<u8> {
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
}
}