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}