Skip to main content

chip_eight/
lib.rs

1//! This crate provides the internal implementations for a chip 8 interpreter/emulator.
2//!
3//! It currently provides two external traits that need to be implemented by the user of the crate
4//! (a third trait might be added if sound is ever supported), the Draw and ReadInputState traits.
5//!
6//! The main idea that I was hoping to achieve here was to not have any external dependencies so I
7//! can load the interpreter on to almost any device I want in future without needing to rewrite the
8//! entire application.
9//!
10//! Once the abovementioned traits are implemented, the interpreter will call these functions
11//! when appropriate.
12//!
13//! For example implementations see the examples directory.
14//!
15//! # Usage:
16//! ```
17//! // These would be your own implementation
18//! struct Drawer;
19//! struct InputReader;
20//!
21//! impl chip_eight::Draw for Drawer {
22//!     fn draw_buffer(&mut self, screen_buf: &[u8], screen_width: usize, screen_height: usize) { todo!() }
23//!     fn clear_screen(&mut self) { todo!() }
24//! }
25//! impl chip_eight::ReadInputState for InputReader {
26//!     fn read_keys_state(&self) -> Result<[u8; 16], String> { todo!() }
27//!     fn reset_keys_state(&mut self) { todo!() }
28//! }
29//!
30//! let (drawer, input_reader) = (Drawer, InputReader);
31//! let program = vec![]; // Read it from somewhere
32//!
33//! let mut emulator = chip_eight::Emulator::init(program, drawer, input_reader)
34//!     .expect("The chip 8 program is probably too large");
35//!
36//! emulator
37//!     .set_max_draw_delay(std::time::Duration::from_millis(6))
38//!     .set_quirks_mode(chip_eight::QuirksMode::Chip8);
39//!
40//! // Most simply you can then run the emulator as follows:
41//! // EITHER: emulator.run_blocking();
42//! // OR:
43//! for emulator_state in emulator {
44//!     // Here emulator_state gives you access to the previous instruction as well as
45//!     // This is the expensive way of using the emulator, but is nice for debuggers
46//!     //(Just breaking here for the doctest)
47//!     break;
48//! }
49//! ```
50use std::{error::Error, fmt::Display};
51
52mod emulator;
53mod twister_rand;
54mod utils;
55
56pub use emulator::*;
57
58const BASE_SCREEN_WIDTH: usize = 64;
59const BASE_SCREEN_HEIGHT: usize = 32;
60
61/// Implement this trait on a type and use it to draw to or clear the screen.
62pub trait Draw {
63    /// This function will be called each time the interpreter requests to draw to the screen. The
64    /// buffer you are given is just a flat list, but you are given the width and height so you can
65    /// make it square yourself.
66    fn draw_buffer(&mut self, screen_buf: &[u8], screen_width: usize, screen_height: usize);
67
68    /// Clear screen will simply be called when the interpreter calls the clear screen instruction.
69    fn clear_screen(&mut self);
70}
71
72/// Implement this trait on a type and use it to read input state.
73pub trait ReadInputState {
74    /// This function is called whenever the interpreter needs to read keyboard state. You need to
75    /// give back an array of 16 characters, each mapping to the 0x0 - 0xF keys on the hex keyboard
76    fn read_keys_state(&self) -> Result<[u8; 16], String>;
77
78    /// This is a method that most of the time doesn't need to be implemented, you can just perform
79    /// a no-op, but it does give you access to when the emulator is no longer reading a key. This
80    /// helps particularly only when you are in an environment where you don't have continuous
81    /// access to the keys state and you have to keep track of it yourself. I mean, I ran into this
82    /// in the examples directory under my silly implementation for the normal terminal window, but
83    /// it could just be a skill issue.
84    fn reset_keys_state(&mut self);
85}
86
87/// Currently the only ApplicationError variant is the program being too large to fit in the
88/// interpreter's memory.
89#[derive(Debug)]
90pub enum ApplicationError {
91    MemoryLocationOutOfRange { max_addr: usize },
92}
93
94impl Error for ApplicationError {}
95
96impl Display for ApplicationError {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self {
99            ApplicationError::MemoryLocationOutOfRange { max_addr } => {
100                write!(
101                    f,
102                    "Tried to write memory out of range, max address: {max_addr}"
103                )
104            }
105        }
106    }
107}