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
.
├── 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.
```

View file

@ -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<D, S>
pub struct Emulator<D, S, I>
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<u16>,
/// 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<u8>,
}
impl<D, S> Emulator<D, S>
impl<D, S, I> Emulator<D, S, I>
where
D: Display,
S: SoundModule,
I: InputModule,
{
/// 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 {
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");

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
}
}