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 Ok([0; 16])
78 }
79
80 /// This is a method that most of the time doesn't need to be implemented, you can just perform
81 /// a no-op, but it does give you access to when the emulator is no longer reading a key. This
82 /// helps particularly only when you are in an environment where you don't have continuous
83 /// access to the keys state and you have to keep track of it yourself. I mean, I ran into this
84 /// in the examples directory under my silly implementation for the normal terminal window, but
85 /// it could just be a skill issue.
86 fn reset_keys_state(&mut self) {}
87}
88
89/// Currently the only ApplicationError variant is the program being too large to fit in the
90/// interpreter's memory.
91#[derive(Debug, Clone)]
92pub enum ApplicationError {
93 MemoryLocationOutOfRange { max_addr: usize },
94}
95
96impl Error for ApplicationError {}
97
98impl Display for ApplicationError {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 ApplicationError::MemoryLocationOutOfRange { max_addr } => {
102 write!(
103 f,
104 "Tried to write memory out of range, max address: {max_addr}"
105 )
106 }
107 }
108 }
109}