implement input module
This commit is contained in:
parent
9334c7752d
commit
69d44cda4e
3 changed files with 155 additions and 17 deletions
25
README.md
25
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.
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
119
src/input.rs
119
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<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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue