rename to gemma
add gemmaegui
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "gemmaemu"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
autobenches = true
|
||||
|
||||
[dependencies]
|
||||
ratatui = "0.28.0"
|
||||
glium = { version = "0.34.0", default-features = true }
|
||||
image = "0.23"
|
||||
imgui = { version ="0.12.0", features = ["tables-api"] }
|
||||
imgui-glium-renderer = { version = "0.12.0" }
|
||||
imgui-winit-support = { version = "0.12.0" }
|
||||
winit = { version = "0.27", features = ["x11", "mint"] }
|
||||
pretty_env_logger = "0.5.0"
|
||||
copypasta = "0.8"
|
||||
rand = "0.9.0-alpha.2"
|
||||
log = "0.4.22"
|
||||
beep = "0.3.0"
|
||||
chrono = "0.4.38"
|
||||
dimensioned = "0.8.0"
|
||||
@@ -0,0 +1,168 @@
|
||||
use std::io::{stdout, Result};
|
||||
/*
|
||||
use emmaemu::{chip8::{computer::Chip8Computer, video::Chip8Video}, constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT, CHIP8_ROM_SIZE, CHIP8_VIDEO_MEMORY}};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Stylize},
|
||||
widgets::{List, Paragraph, Widget},
|
||||
Frame, Terminal,
|
||||
};
|
||||
|
||||
|
||||
fn system_memory_to_text_render(data_to_dump: [u8; 2048]) -> String {
|
||||
let mut to_return = String::new();
|
||||
|
||||
for i in 0..256 {
|
||||
to_return += &format!("{:x}\t", data_to_dump[i as usize]).to_string();
|
||||
|
||||
if ((i + 1) % CHIP8_REGISTER_COUNT) == 0 {
|
||||
to_return += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
to_return
|
||||
}
|
||||
|
||||
fn dump_memory_to_console(data_to_dump: [u8; 2048]) {
|
||||
println!("STARTING TO DUMP MEMORY TO CONSOLE");
|
||||
println!("{}", system_memory_to_text_render(data_to_dump));
|
||||
println!("DONE DUMPING!");
|
||||
panic!("DONE DUMPING");
|
||||
}
|
||||
|
||||
struct ControlKeyboard {
|
||||
|
||||
}
|
||||
|
||||
impl Widget for ControlKeyboard {
|
||||
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized {
|
||||
let style = Style::new();
|
||||
buf.set_string(0, 0, "F1 to cycle foreground - F2 to cycle background", style)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
pub menu_items: Vec<String>,
|
||||
pub selected_menu_item: i32,
|
||||
pub system: Chip8Computer,
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> Self {
|
||||
println!("Creating new AppState");
|
||||
Self {
|
||||
menu_items: vec![
|
||||
"Step CPU <F5>".into(),
|
||||
"Reset CPU <F12>".into(),
|
||||
"Item 3".into(),
|
||||
],
|
||||
selected_menu_item: 0,
|
||||
system: Chip8Computer {
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
terminal.clear()?;
|
||||
|
||||
let app = AppState::default();
|
||||
|
||||
loop {
|
||||
// Draw Ui...
|
||||
terminal.draw(|frame| {
|
||||
frame.render_widget(app.system.memory, frame.area());
|
||||
// frame.render_widget(app.control_keyboard, area);
|
||||
//render_cpu_state(app.system.clone(), frame);
|
||||
// render_video_state(app.system.video_memory, frame);
|
||||
// render_menu_list(app.clone(), frame);
|
||||
})?;
|
||||
// ...handle Events.
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
if let event::Event::Key(key) = event::read()? {
|
||||
match key.kind {
|
||||
KeyEventKind::Press => match key.code {
|
||||
KeyCode::F(5) => {
|
||||
println!("Execute Next Instruction");
|
||||
}
|
||||
KeyCode::F(12) => {
|
||||
println!("Resetting CPU");
|
||||
}
|
||||
KeyCode::Char('q') => { break; }
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stdout().execute(LeaveAlternateScreen)?;
|
||||
// disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use emmaemu::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
|
||||
|
||||
use crate::*;
|
||||
#[test]
|
||||
fn blank_screen_renders_to_text() {
|
||||
let test_video = Chip8Video::default();
|
||||
|
||||
|
||||
let blank_data: [bool; CHIP8_VIDEO_MEMORY] = [false; CHIP8_VIDEO_MEMORY];
|
||||
let blank_screen = (" ".repeat(CHIP8_VIDEO_WIDTH.try_into().unwrap()) + "\n")
|
||||
.repeat(CHIP8_VIDEO_HEIGHT.try_into().unwrap());
|
||||
|
||||
assert_eq!(Chip8Video::new(blank_data).format_as_string(), blank_screen);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filled_screen_renders_to_text() {
|
||||
let filled_data: [bool; CHIP8_VIDEO_MEMORY] = [true; CHIP8_VIDEO_MEMORY];
|
||||
let filled_screen = ("*".repeat(CHIP8_VIDEO_WIDTH.try_into().unwrap()) + "\n")
|
||||
.repeat(CHIP8_VIDEO_HEIGHT.try_into().unwrap());
|
||||
assert_eq!(Chip8Video::new(filled_data).format_as_string(), filled_screen);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_pattern_renders_to_text() {
|
||||
let mut grid_data: [bool; CHIP8_VIDEO_MEMORY] = [false; CHIP8_VIDEO_MEMORY];
|
||||
let mut expected_data = String::new();
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
grid_data[i] = (i % 2 == 0);
|
||||
if i % 2 == 0 {
|
||||
grid_data[i] = true;
|
||||
expected_data += "*";
|
||||
} else {
|
||||
grid_data[i] = false;
|
||||
expected_data += " ";
|
||||
}
|
||||
if (i as i32 % CHIP8_VIDEO_WIDTH as i32 == CHIP8_VIDEO_WIDTH - 1) {
|
||||
expected_data += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(Chip8Video::new(grid_data).format_as_string(), expected_data);
|
||||
}
|
||||
}
|
||||
*/
|
||||
fn main() {
|
||||
println!("Taxation is theft");
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use std::default::Default;
|
||||
use std::fs::DirEntry;
|
||||
use std::time::Instant;
|
||||
use gemmaemu::{
|
||||
chip8::computer::Chip8Computer,
|
||||
constants::{CHIP8_MEMORY_SIZE, CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH},
|
||||
};
|
||||
use imgui::*;
|
||||
use ratatui::symbols::half_block;
|
||||
use sys::{ImColor, ImVec2, ImVector_ImU32};
|
||||
use rand::random;
|
||||
use gemmaemu::chip8::system_memory::Chip8SystemMemory;
|
||||
use support::emmagui_support::EmmaGui;
|
||||
mod support;
|
||||
|
||||
|
||||
struct UiState {
|
||||
pub show_registers: bool,
|
||||
pub show_memory: bool,
|
||||
pub show_video: bool,
|
||||
pub filename_to_load: String,
|
||||
pub on_colour: ImColor32,
|
||||
pub off_colour: ImColor32,
|
||||
pub is_running: bool,
|
||||
pub frame_time: f32
|
||||
}
|
||||
|
||||
impl Clone for UiState {
|
||||
fn clone(&self) -> Self {
|
||||
UiState {
|
||||
show_registers: self.show_registers,
|
||||
show_memory: self.show_memory,
|
||||
show_video: self.show_video,
|
||||
filename_to_load: self.filename_to_load.to_string(),
|
||||
on_colour: self.on_colour,
|
||||
off_colour: self.off_colour,
|
||||
is_running: self.is_running,
|
||||
frame_time: self.frame_time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiState {
|
||||
fn default() -> Self {
|
||||
UiState {
|
||||
show_registers: false,
|
||||
show_memory: false,
|
||||
show_video: true,
|
||||
filename_to_load: String::new(),
|
||||
on_colour: ImColor32::from_rgb(0xff, 0xff, 0x00),
|
||||
off_colour: ImColor32::from_rgb(0x00, 0xff, 0xff),
|
||||
is_running: false,
|
||||
frame_time: 32.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
let mut system = Chip8Computer::default();
|
||||
let mut ui_state = UiState::default();
|
||||
let mut last_tick_time = Instant::now();
|
||||
|
||||
support::simple_init(file!(), move |_, ui| {
|
||||
let current_time = Instant::now();
|
||||
let time_since_last_tick = current_time.duration_since(last_tick_time).as_millis();
|
||||
if ui_state.is_running && time_since_last_tick > ui_state.frame_time as u128 {
|
||||
system.step_system();
|
||||
last_tick_time = current_time;
|
||||
}
|
||||
|
||||
EmmaGui::system_controls(&mut system, &mut ui_state, ui);
|
||||
|
||||
if ui_state.show_registers {
|
||||
EmmaGui::registers_view(&system, ui);
|
||||
}
|
||||
|
||||
if ui_state.show_video {
|
||||
EmmaGui::video_display(&system, &ui_state, ui);
|
||||
}
|
||||
|
||||
if ui_state.show_memory {
|
||||
let active_instruction = system.registers.peek_pc();
|
||||
EmmaGui::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use ratatui::{style::Stylize, widgets::{List, Paragraph}, Frame};
|
||||
|
||||
use crate::{AppState, Chip8Computer, CHIP8_REGISTER_COUNT, CHIP8_VIDEO_MEMORY};
|
||||
|
||||
|
||||
pub fn render_cpu_state(cpu_state: Chip8Computer, frame: &mut Frame) {
|
||||
let mut area = frame.area();
|
||||
|
||||
let mut current_state: Vec<String> = vec![];
|
||||
|
||||
current_state.push(format!("Delay Timer {:X}", cpu_state.delay_timer));
|
||||
current_state.push(format!("Sound Timer {:X}", cpu_state.sound_timer));
|
||||
current_state.push(format!("PC {:X}", cpu_state.pc));
|
||||
for i in 0 .. CHIP8_REGISTER_COUNT {
|
||||
current_state.push(format!("V{}: {}", i, cpu_state.registers[i as usize]));
|
||||
}
|
||||
|
||||
frame.render_widget(
|
||||
List::new(current_state).blue().on_white(), area)
|
||||
}
|
||||
|
||||
pub fn render_menu_list(state: AppState, frame: &mut Frame) {
|
||||
let mut area = frame.area();
|
||||
|
||||
frame.render_widget(List::new(state.menu_items).blue().on_white(), area);
|
||||
}
|
||||
|
||||
pub fn render_hello_world(frame: &mut Frame) {
|
||||
let area = frame.area();
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
|
||||
.white()
|
||||
.on_blue(),
|
||||
area,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_video_state(video_memory: [bool; CHIP8_VIDEO_MEMORY], frame: &mut Frame) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use imgui::{Condition, ImColor32, Ui};
|
||||
use log::debug;
|
||||
use gemmaemu::chip8::computer::Chip8Computer;
|
||||
use gemmaemu::chip8::system_memory::Chip8SystemMemory;
|
||||
use gemmaemu::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
|
||||
use crate::UiState;
|
||||
|
||||
|
||||
pub struct EmmaGui {}
|
||||
|
||||
const CELL_WIDTH: i32 = 5i32;
|
||||
const CELL_HEIGHT: i32 = 5i32;
|
||||
|
||||
struct GuiFileList {}
|
||||
|
||||
impl GuiFileList {
|
||||
pub fn display_path(root: PathBuf, selected_filename: &String, ui: &Ui) -> String {
|
||||
let mut working_filename = selected_filename.clone();
|
||||
ui.text(format!("Displaying {}", root.to_str().unwrap_or("Unable to parse path")));
|
||||
for (index, entry) in std::fs::read_dir(root.as_path()).unwrap().enumerate() {
|
||||
let filename = entry.unwrap().file_name();
|
||||
let filename = filename.to_str().unwrap();
|
||||
let mut working_select = ui.selectable_config(format!("{}", filename));
|
||||
if filename == selected_filename {
|
||||
working_select = working_select.selected(true);
|
||||
}
|
||||
if working_select.build() {
|
||||
debug!("SELECTED {index} / {filename}");
|
||||
working_filename = filename.to_string();
|
||||
};
|
||||
}
|
||||
working_filename
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl EmmaGui {
|
||||
pub fn video_display(system_to_control: &Chip8Computer, gui_state: &UiState, ui: &Ui) {
|
||||
// draw area size
|
||||
let draw_area_size = ui.io().display_size;
|
||||
let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10;
|
||||
let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10;
|
||||
|
||||
ui.window(format!("Display {cell_width}x{cell_height}"))
|
||||
.size([300.0, 300.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
let origin = ui.cursor_screen_pos();
|
||||
let fg = ui.get_window_draw_list();
|
||||
ui.text("This is the video display.");
|
||||
for current_row in 0..=31 {
|
||||
for current_column in 0..=63 {
|
||||
let x_offset = origin[0] as i32 + (current_column * cell_width);
|
||||
let y_offset = origin[1] as i32 + (current_row * cell_height);
|
||||
let current_origin = [x_offset as f32, y_offset as f32];
|
||||
let current_limit = [(x_offset + cell_width) as f32, (y_offset + cell_height) as f32];
|
||||
let memory_offset = (current_row * 64 + current_column) as u16;
|
||||
let to_render = system_to_control.video_memory.peek(memory_offset);
|
||||
let color: ImColor32 = if to_render {
|
||||
gui_state.on_colour
|
||||
} else {
|
||||
gui_state.off_colour
|
||||
};
|
||||
fg.add_rect_filled_multicolor(current_origin, current_limit, color, color, color, color);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
pub fn system_controls(system_to_control: &mut Chip8Computer, gui_state: &mut UiState, ui: &Ui) {
|
||||
ui.window("!!!! CONTROLS !!!!")
|
||||
.size([345.0, 200.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
/* ROM Lister */
|
||||
let new_filename = GuiFileList::display_path(PathBuf::from("resources/roms"), &gui_state.filename_to_load, ui);
|
||||
if !new_filename.is_empty() {
|
||||
if new_filename != gui_state.filename_to_load {
|
||||
println!("NEW FILENAME SELECTED -> {new_filename}");
|
||||
gui_state.filename_to_load = new_filename;
|
||||
}
|
||||
if ui.button("Load Program") {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
println!("PREPARING TO LOAD {}", gui_state.filename_to_load);
|
||||
// let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory");
|
||||
let mut input_file = File::open(Path::new(&("resources/roms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory");
|
||||
input_file.read_to_end(&mut buffer).expect("unable to read file");
|
||||
system_to_control.load_bytes_to_memory(0x200, buffer.into());
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Step") {
|
||||
system_to_control.step_system();
|
||||
};
|
||||
if ui.button("Run") {
|
||||
gui_state.is_running = true;
|
||||
println!("STARTING THE SYSTEM");
|
||||
}
|
||||
if ui.button("Stop") {
|
||||
gui_state.is_running = false;
|
||||
println!("STOPPING THE SYSTEM");
|
||||
}
|
||||
ui.same_line();
|
||||
if ui.button("Reset") {
|
||||
*system_to_control = Chip8Computer::new();
|
||||
}
|
||||
if ui.button("Dump Video Memory") {
|
||||
println!("{}", system_to_control.dump_video_to_string());
|
||||
}
|
||||
ui.same_line();
|
||||
if ui.button("Dump Keypad State") {
|
||||
println!("{}", system_to_control.dump_keypad_to_string());
|
||||
}
|
||||
ui.same_line();
|
||||
if ui.button("Dump Registers") {
|
||||
println!("{}", system_to_control.dump_registers_to_string());
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
ui.checkbox("Show Memory", &mut gui_state.show_memory);
|
||||
ui.same_line();
|
||||
ui.checkbox("Show Video", &mut gui_state.show_video);
|
||||
ui.same_line();
|
||||
ui.checkbox("Show Registers", &mut gui_state.show_registers);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn registers_view(system: &Chip8Computer, ui: &Ui) {
|
||||
ui.window("Registers")
|
||||
.size([400.0, 500.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
ui.text("Registers");
|
||||
for i in 1..0x10 {
|
||||
ui.text(format!("V{:X}: {}", i, system.registers.peek(i)));
|
||||
if i != 7 {
|
||||
ui.same_line();
|
||||
}
|
||||
}
|
||||
ui.text("");
|
||||
ui.text(format!("I: {:03X}", system.registers.peek_i()));
|
||||
ui.same_line();
|
||||
ui.text(format!("ST: {:02X}", system.sound_timer.current()));
|
||||
ui.same_line();
|
||||
ui.text(format!("DT: {:02X}", system.delay_timer.current()));
|
||||
ui.text(format!("PC: {:02X}", system.registers.peek_pc()));
|
||||
ui.text(format!("SP: {:02X}", system.stack.depth()));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn hex_memory_display(bytes: Chip8SystemMemory, position: (i32, i32), active: i16, ui: &Ui) {
|
||||
let rows = position.0;
|
||||
let cols = position.1;
|
||||
ui.window("System Memory")
|
||||
.size([400.0, 300.0], Condition::FirstUseEver)
|
||||
.position([500.0, 300.0], Condition::Always)
|
||||
.build(|| {
|
||||
let mut current_x_hover: i32 = 0;
|
||||
let mut current_y_hover: i32 = 0;
|
||||
// display a block of data
|
||||
for current_row in 0..rows {
|
||||
ui.text(format!("{:02x}", current_row * cols));
|
||||
ui.same_line();
|
||||
for current_column in 0..cols {
|
||||
let data_offset = current_row * cols + current_column;
|
||||
let formatted_text = format!("{:02x}", bytes.peek(data_offset as u16));
|
||||
let text_location = ui.cursor_screen_pos();
|
||||
let text_size = ui.calc_text_size(formatted_text.clone());
|
||||
let bounding_box = imgui::sys::ImVec2 {
|
||||
x: text_location[0] + text_size[0],
|
||||
y: text_location[1] + text_size[1],
|
||||
};
|
||||
|
||||
let hovering = ui.is_mouse_hovering_rect(text_location, [bounding_box.x, bounding_box.y]);
|
||||
let is_active = data_offset == active as i32;
|
||||
|
||||
ui.text_colored(if hovering {
|
||||
[0., 1., 1., 1.]
|
||||
} else if is_active {
|
||||
[1., 0., 1., 1.]
|
||||
} else {
|
||||
[1., 1., 0., 1.]
|
||||
}, formatted_text.clone());
|
||||
|
||||
|
||||
// if we are hovering show that at the bottom...
|
||||
if hovering {
|
||||
// Optionally change the text color to indicate it's interactable
|
||||
current_x_hover = current_column;
|
||||
current_y_hover = current_row;
|
||||
|
||||
// Check if the left mouse button is clicked while hovering over the text
|
||||
if ui.is_mouse_clicked(imgui::MouseButton::Left) {
|
||||
println!("Offset: [{}] [0x{:02x}] Value: [{}]", data_offset, data_offset, formatted_text.clone());
|
||||
// Perform any action here, e.g., call a function, trigger an event, etc.
|
||||
}
|
||||
}
|
||||
|
||||
// are we on the same line?
|
||||
if current_column != (cols - 1) {
|
||||
ui.same_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.text(format!("Offset 0x{:03x}", current_x_hover * cols + current_y_hover));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn system_memory_render(memory: Chip8SystemMemory, ui: &Ui) {
|
||||
ui.window("System Memory")
|
||||
.size([300.0, 100.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
ui.text("Rendering System Memory Here");
|
||||
let draw_list = ui.get_foreground_draw_list();
|
||||
let mut idx = 0;
|
||||
for row in 0..CHIP8_VIDEO_HEIGHT {
|
||||
for column in 0..CHIP8_VIDEO_WIDTH {
|
||||
let x_offset = column * CELL_WIDTH;
|
||||
let y_offset = row * CELL_WIDTH;
|
||||
let start_point = [x_offset as f32, y_offset as f32];
|
||||
let end_point = [(x_offset + CELL_WIDTH) as f32,
|
||||
(y_offset + CELL_HEIGHT) as f32
|
||||
];
|
||||
let memory_offset = (row * CHIP8_VIDEO_WIDTH) + column;
|
||||
let target_color = if memory.peek(memory_offset as u16) == 0 {
|
||||
ImColor32::BLACK
|
||||
} else {
|
||||
ImColor32::WHITE
|
||||
};
|
||||
draw_list.add_rect([x_offset as f32, y_offset as f32],
|
||||
[(x_offset + CELL_WIDTH) as f32, (y_offset + CELL_HEIGHT) as f32],
|
||||
target_color).build();
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
use glium::glutin::surface::WindowSurface;
|
||||
use glium::{Display, Surface};
|
||||
use imgui::{Context, FontConfig, FontGlyphRanges, FontSource, Ui};
|
||||
use imgui_glium_renderer::Renderer;
|
||||
use imgui_winit_support::winit::dpi::LogicalSize;
|
||||
use imgui_winit_support::winit::event::{Event, WindowEvent};
|
||||
use imgui_winit_support::winit::event_loop::EventLoop;
|
||||
use imgui_winit_support::winit::window::WindowBuilder;
|
||||
use imgui_winit_support::{HiDpiMode, WinitPlatform};
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
pub mod emmagui_support;
|
||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
use imgui::ClipboardBackend;
|
||||
|
||||
pub struct ClipboardSupport(pub ClipboardContext);
|
||||
|
||||
pub fn clipboard_init() -> Option<ClipboardSupport> {
|
||||
ClipboardContext::new().ok().map(ClipboardSupport)
|
||||
}
|
||||
|
||||
impl ClipboardBackend for ClipboardSupport {
|
||||
fn get(&mut self) -> Option<String> {
|
||||
self.0.get_contents().ok()
|
||||
}
|
||||
fn set(&mut self, text: &str) {
|
||||
// ignore errors?
|
||||
let _ = self.0.set_contents(text.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub const FONT_SIZE: f32 = 13.0;
|
||||
|
||||
#[allow(dead_code)] // annoyingly, RA yells that this is unusued
|
||||
pub fn simple_init<F: FnMut(&mut bool, &mut Ui) + 'static>(title: &str, run_ui: F) {
|
||||
init_with_startup(title, |_, _, _| {}, run_ui);
|
||||
}
|
||||
|
||||
pub fn init_with_startup<FInit, FUi>(title: &str, mut startup: FInit, mut run_ui: FUi)
|
||||
where
|
||||
FInit: FnMut(&mut Context, &mut Renderer, &Display<WindowSurface>) + 'static,
|
||||
FUi: FnMut(&mut bool, &mut Ui) + 'static,
|
||||
{
|
||||
let mut imgui = create_context();
|
||||
|
||||
let title = match Path::new(&title).file_name() {
|
||||
Some(file_name) => file_name.to_str().unwrap(),
|
||||
None => title,
|
||||
};
|
||||
let event_loop = EventLoop::new().expect("Failed to create EventLoop");
|
||||
|
||||
let builder = WindowBuilder::new()
|
||||
.with_maximized(true)
|
||||
.with_title(title);
|
||||
// .with_inner_size(LogicalSize::new(1024, 768));
|
||||
let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new()
|
||||
.set_window_builder(builder)
|
||||
.build(&event_loop);
|
||||
let mut renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer");
|
||||
|
||||
if let Some(backend) = clipboard_init() {
|
||||
imgui.set_clipboard_backend(backend);
|
||||
} else {
|
||||
eprintln!("Failed to initialize clipboard");
|
||||
}
|
||||
|
||||
let mut platform = WinitPlatform::init(&mut imgui);
|
||||
{
|
||||
let dpi_mode = if let Ok(factor) = std::env::var("IMGUI_EXAMPLE_FORCE_DPI_FACTOR") {
|
||||
// Allow forcing of HiDPI factor for debugging purposes
|
||||
match factor.parse::<f64>() {
|
||||
Ok(f) => HiDpiMode::Locked(f),
|
||||
Err(e) => panic!("Invalid scaling factor: {}", e),
|
||||
}
|
||||
} else {
|
||||
HiDpiMode::Default
|
||||
};
|
||||
|
||||
platform.attach_window(imgui.io_mut(), &window, dpi_mode);
|
||||
}
|
||||
|
||||
let mut last_frame = Instant::now();
|
||||
|
||||
startup(&mut imgui, &mut renderer, &display);
|
||||
|
||||
event_loop
|
||||
.run(move |event, window_target| match event {
|
||||
Event::NewEvents(_) => {
|
||||
let now = Instant::now();
|
||||
imgui.io_mut().update_delta_time(now - last_frame);
|
||||
last_frame = now;
|
||||
}
|
||||
Event::AboutToWait => {
|
||||
platform
|
||||
.prepare_frame(imgui.io_mut(), &window)
|
||||
.expect("Failed to prepare frame");
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
let ui = imgui.frame();
|
||||
|
||||
let mut run = true;
|
||||
run_ui(&mut run, ui);
|
||||
if !run {
|
||||
window_target.exit();
|
||||
}
|
||||
|
||||
let mut target = display.draw();
|
||||
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
|
||||
platform.prepare_render(ui, &window);
|
||||
let draw_data = imgui.render();
|
||||
renderer
|
||||
.render(&mut target, draw_data)
|
||||
.expect("Rendering failed");
|
||||
target.finish().expect("Failed to swap buffers");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
display.resize((new_size.width, new_size.height));
|
||||
}
|
||||
platform.handle_event(imgui.io_mut(), &window, &event);
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => window_target.exit(),
|
||||
event => {
|
||||
platform.handle_event(imgui.io_mut(), &window, &event);
|
||||
}
|
||||
})
|
||||
.expect("EventLoop error");
|
||||
}
|
||||
|
||||
/// Creates the imgui context
|
||||
pub fn create_context() -> imgui::Context {
|
||||
let mut imgui = Context::create();
|
||||
// Fixed font size. Note imgui_winit_support uses "logical
|
||||
// pixels", which are physical pixels scaled by the devices
|
||||
// scaling factor. Meaning, 13.0 pixels should look the same size
|
||||
// on two different screens, and thus we do not need to scale this
|
||||
// value (as the scaling is handled by winit)
|
||||
imgui.fonts().add_font(&[
|
||||
FontSource::TtfData {
|
||||
data: include_bytes!("../../../../resources/Roboto-Regular.ttf"),
|
||||
size_pixels: FONT_SIZE,
|
||||
config: Some(FontConfig {
|
||||
// As imgui-glium-renderer isn't gamma-correct with
|
||||
// it's font rendering, we apply an arbitrary
|
||||
// multiplier to make the font a bit "heavier". With
|
||||
// default imgui-glow-renderer this is unnecessary.
|
||||
rasterizer_multiply: 1.5,
|
||||
// Oversampling font helps improve text rendering at
|
||||
// expense of larger font atlas texture.
|
||||
oversample_h: 4,
|
||||
oversample_v: 4,
|
||||
..FontConfig::default()
|
||||
}),
|
||||
},
|
||||
FontSource::TtfData {
|
||||
data: include_bytes!("../../../../resources/mplus-1p-regular.ttf"),
|
||||
size_pixels: FONT_SIZE,
|
||||
config: Some(FontConfig {
|
||||
// Oversampling font helps improve text rendering at
|
||||
// expense of larger font atlas texture.
|
||||
oversample_h: 4,
|
||||
oversample_v: 4,
|
||||
// Range of glyphs to rasterize
|
||||
glyph_ranges: FontGlyphRanges::japanese(),
|
||||
..FontConfig::default()
|
||||
}),
|
||||
},
|
||||
]);
|
||||
imgui.set_ini_filename(None);
|
||||
|
||||
imgui
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use log::{debug, error};
|
||||
use crate::chip8::delay_timer::DelayTimer;
|
||||
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
use crate::chip8::keypad::Keypad;
|
||||
use crate::chip8::registers::Chip8Registers;
|
||||
use crate::chip8::sound_timer::SoundTimer;
|
||||
use crate::chip8::stack::Chip8Stack;
|
||||
use crate::chip8::util::InstructionUtil;
|
||||
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT};
|
||||
|
||||
use super::{
|
||||
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions, system_memory::Chip8SystemMemory, video::Chip8Video,
|
||||
};
|
||||
|
||||
const STACK_POINTER_DEFAULT: i16 = 0x100;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chip8Computer {
|
||||
pub memory: Chip8SystemMemory,
|
||||
pub registers: Chip8Registers,
|
||||
pub sound_timer: SoundTimer,
|
||||
pub delay_timer: DelayTimer,
|
||||
pub video_memory: Chip8Video,
|
||||
pub state: Chip8CpuStates,
|
||||
pub keypad: Keypad,
|
||||
pub stack: Chip8Stack
|
||||
}
|
||||
impl Default for Chip8Computer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
memory: Chip8SystemMemory::default(),
|
||||
video_memory: Chip8Video::default(),
|
||||
registers: Chip8Registers::default(),
|
||||
sound_timer: SoundTimer::new(),
|
||||
delay_timer: DelayTimer::new(),
|
||||
state: Chip8CpuStates::default(),
|
||||
keypad: Keypad::default(),
|
||||
stack: Chip8Stack::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Chip8Computer {
|
||||
pub fn reset(&mut self) -> Self{
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn dump_keypad_to_string(&self) -> String {
|
||||
self.keypad.format_as_string()
|
||||
}
|
||||
|
||||
pub fn dump_registers_to_string(&self) -> String {
|
||||
self.registers.format_as_string()
|
||||
}
|
||||
|
||||
pub fn dump_video_to_string(&self) -> String {
|
||||
self.video_memory.format_as_string()
|
||||
}
|
||||
|
||||
pub fn new_with_program(new_program: Box<Vec<u16>>) -> Self {
|
||||
let mut working = Chip8Computer::new();
|
||||
for i in 0..new_program.len() {
|
||||
let high_byte = (new_program[i as usize] >> 8) as u8;
|
||||
let low_byte = (new_program[i] & 0xff) as u8;
|
||||
let base_offset = i * 2;
|
||||
working.memory.poke(base_offset as u16, high_byte);
|
||||
working.memory.poke((base_offset + 1) as u16, low_byte);
|
||||
}
|
||||
working
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Chip8Computer::default()
|
||||
}
|
||||
|
||||
pub fn load_bytes_to_memory(&mut self, offset: u16, to_load: Box<Vec<u8>>) {
|
||||
let total_len = to_load.len() as u16;
|
||||
for current_index in 0..total_len {
|
||||
let new_value = to_load[current_index as usize];
|
||||
let new_location = current_index + offset;
|
||||
self.memory.poke(new_location, new_value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step_system(&mut self) -> &mut Chip8Computer {
|
||||
debug!("Stepping System 1 Step");
|
||||
// read the next instruction
|
||||
|
||||
let mut working_instruction: u16 = 0b0000000000000000;
|
||||
let start_pc = self.registers.peek_pc();
|
||||
let high_byte = (self.memory.clone().peek(start_pc) as u16).rotate_left(8);
|
||||
let low_byte = self.memory.clone().peek(start_pc + 1) as u16;
|
||||
let result = high_byte | low_byte;
|
||||
let decoded_instruction =
|
||||
Chip8CpuInstructions::decode(result);
|
||||
// todo: THIS IS BAD AND IS A SIDE EFFECT
|
||||
decoded_instruction.execute(self);
|
||||
|
||||
match self.state {
|
||||
Chip8CpuStates::WaitingForInstruction => {
|
||||
self.sound_timer.tick();
|
||||
self.delay_timer.tick();
|
||||
}
|
||||
Chip8CpuStates::WaitingForKey => {
|
||||
println!("waiting for a key press...");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::random;
|
||||
use crate::constants::CHIP8_VIDEO_MEMORY;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub enum Chip8CpuStates {
|
||||
#[default]
|
||||
WaitingForInstruction,
|
||||
WaitingForKey,
|
||||
ExecutingInstruction,
|
||||
Error,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DelayTimer {
|
||||
counter: i32
|
||||
}
|
||||
|
||||
impl DelayTimer {
|
||||
pub fn current(&self) -> i32 {
|
||||
self.counter
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
DelayTimer {
|
||||
counter: 0xff
|
||||
}
|
||||
}
|
||||
pub fn set_timer(&mut self, new_value: i32) {
|
||||
self.counter = new_value
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
if self.counter > 0 {
|
||||
self.counter -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::chip8::sound_timer::SoundTimer;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticks_reduce_time() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_ticks_works() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
use imgui::Key;
|
||||
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Keypad {
|
||||
keys: [bool; 0x10],
|
||||
}
|
||||
|
||||
impl Keypad {
|
||||
pub fn format_as_string(&self) -> String {
|
||||
let mut return_value = String::new();
|
||||
// draw a 4x4 grid showing the keys with * filling the cells that are depressed
|
||||
let keys = [
|
||||
[0x1, 0x2, 0x3, 0xc],[0x4, 0x5, 0x6, 0xd],[0x7, 0x8, 0x9, 0xe],[0xa, 0x0, 0xb, 0xf]
|
||||
];
|
||||
|
||||
for row in keys.iter() {
|
||||
for (index, key) in row.iter().enumerate() {
|
||||
let is_lit = if self.keys[*key] { "*".to_string() } else { char::from_digit(*key as u32, 16).unwrap_or(' ').to_string() };
|
||||
match index {
|
||||
3 => {
|
||||
// last in col
|
||||
return_value += format!("|{}|\n", is_lit).as_str();
|
||||
}
|
||||
_=> {
|
||||
return_value += format!("|{}", is_lit).as_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return_value
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Keypad {
|
||||
fn default() -> Self {
|
||||
Keypad {
|
||||
keys: [ false; 16]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Keypad {
|
||||
pub fn push_key(&mut self, key_index: u8) {
|
||||
self.keys[key_index as usize] = true;
|
||||
}
|
||||
|
||||
pub fn release_key(&mut self, key_index: u8) {
|
||||
self.keys[key_index as usize] = false;
|
||||
}
|
||||
|
||||
pub fn key_state(&self, key_index: u8) -> bool {
|
||||
self.keys[key_index as usize]
|
||||
}
|
||||
|
||||
pub fn new() -> Keypad {
|
||||
Keypad::default()
|
||||
}
|
||||
|
||||
pub fn pressed(&self, key: u8) -> bool {
|
||||
self.key_state(key)
|
||||
}
|
||||
pub fn released(&self, key: u8) -> bool {
|
||||
!self.key_state(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() { assert!(true) }
|
||||
|
||||
#[test]
|
||||
fn keys_check() {
|
||||
let mut k = Keypad::new();
|
||||
|
||||
for i in 0..16 {
|
||||
assert!(!k.key_state(i));
|
||||
}
|
||||
|
||||
// press a key
|
||||
k.push_key(1);
|
||||
k.push_key(2);
|
||||
assert!(k.pressed(1));
|
||||
assert!(k.pressed(2));
|
||||
k.release_key(1);
|
||||
assert!(k.released(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
use log::debug;
|
||||
|
||||
/// Registers. numbered 1-16 publicly.
|
||||
/// Privately using zero base array so -1 to shift from pub to priv.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Chip8Registers {
|
||||
registers: [u8; 16],
|
||||
i_register: u16,
|
||||
pc: u16,
|
||||
sp: u16,
|
||||
}
|
||||
|
||||
impl Chip8Registers {
|
||||
pub fn advance_pc(&mut self) {
|
||||
self.pc += 2;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Chip8Registers {
|
||||
fn default() -> Self {
|
||||
Chip8Registers {
|
||||
registers: [0x00; 16],
|
||||
i_register: 0x00,
|
||||
pc: 0x200,
|
||||
sp: 0x100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Chip8Registers {
|
||||
pub fn poke_pc(&mut self, new_pc: u16) {
|
||||
self.pc = new_pc
|
||||
}
|
||||
pub fn peek_i(&self) -> u16 {
|
||||
self.i_register
|
||||
}
|
||||
|
||||
pub fn poke_i(&mut self, new_value: u16) {
|
||||
self.i_register = new_value;
|
||||
}
|
||||
pub fn peek(&self, register_number: u8) -> u8 {
|
||||
self.registers[(register_number) as usize]
|
||||
}
|
||||
|
||||
pub fn poke(&mut self, register_number: u8, value: u8) {
|
||||
if register_number > 0xf {
|
||||
panic!("INVALID REGISTER");
|
||||
}
|
||||
self.registers[(register_number) as usize] = value;
|
||||
}
|
||||
|
||||
pub fn peek_pc(&self) -> u16 {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn set_pc(&mut self, new_pc: u16) {
|
||||
self.pc = new_pc
|
||||
}
|
||||
|
||||
pub fn format_as_string(&self) -> String {
|
||||
format!("Vx: 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}\n 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}\nI: 0x{:04x}\tPC: 0x{:04x}",
|
||||
self.registers[0x0],
|
||||
self.registers[0x1],
|
||||
self.registers[0x2],
|
||||
self.registers[0x3],
|
||||
self.registers[0x4],
|
||||
self.registers[0x5],
|
||||
self.registers[0x6],
|
||||
self.registers[0x7],
|
||||
self.registers[0x8],
|
||||
self.registers[0x9],
|
||||
self.registers[0xa],
|
||||
self.registers[0xb],
|
||||
self.registers[0xc],
|
||||
self.registers[0xd],
|
||||
self.registers[0xe],
|
||||
self.registers[0xf],
|
||||
self.i_register,
|
||||
self.pc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::chip8::registers::Chip8Registers;
|
||||
|
||||
#[test]
|
||||
fn smoke() { assert!(true) }
|
||||
|
||||
#[test]
|
||||
fn register_rw_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x0, 0xff);
|
||||
x.poke(0x1, 0xab);
|
||||
assert_eq!(x.peek(0x0), 0xff);
|
||||
assert_eq!(x.peek(0x1), 0xab);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pc_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.set_pc(0x300);
|
||||
assert_eq!(x.peek_pc(), 0x300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalid_register() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x10, 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_as_string_looks_right() {
|
||||
let mut x = Chip8Registers::default();
|
||||
for i in 0..0x10 {
|
||||
x.registers[i] = i as u8;
|
||||
}
|
||||
x.pc = 0xabc;
|
||||
x.i_register = 0xcab;
|
||||
let result_string = x.format_as_string();
|
||||
assert_eq!(result_string, String::from("Vx: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f\nI: 0x0cab\tPC: 0x0abc"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use std::{thread, time};
|
||||
use beep::beep;
|
||||
use log::trace;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SoundTimer {
|
||||
counter: i32
|
||||
}
|
||||
|
||||
impl SoundTimer {
|
||||
pub fn current(&self) -> i32 {
|
||||
self.counter
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
SoundTimer {
|
||||
counter: 0
|
||||
}
|
||||
}
|
||||
pub fn set_timer(&mut self, new_value: i32) {
|
||||
trace!("SETTING SOUND TIMER TO {new_value}");
|
||||
self.counter = new_value
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
trace!("TICKING SOUND FROM {} to {}", self.counter, self.counter - 1);
|
||||
if self.counter > 0 {
|
||||
self.counter -= 1;
|
||||
/*
|
||||
todo: this breaks tests. maybe its wrong?
|
||||
if let good_thread = thread::spawn(|| {
|
||||
beep(440).expect("Unable to beep");
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
}).join().unwrap().
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticks_reduce_time() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_ticks_works() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#[derive(Clone)]
|
||||
pub struct Chip8Stack {
|
||||
items: Vec<u16>
|
||||
}
|
||||
|
||||
impl Default for Chip8Stack {
|
||||
fn default() -> Self {
|
||||
Chip8Stack {
|
||||
items: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Chip8Stack {
|
||||
pub fn push(&mut self, new_value: &u16) {
|
||||
if self.depth() == 16 {
|
||||
panic!("Stack Overflow");
|
||||
}
|
||||
self.items.push(*new_value );
|
||||
}
|
||||
pub fn pop(&mut self) -> u16 {
|
||||
if self.items.is_empty() {
|
||||
panic!("Stack Underflow");
|
||||
}
|
||||
self.items.pop().unwrap()
|
||||
}
|
||||
pub fn depth(&self) -> u16 {
|
||||
self.items.len() as u16
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Chip8Stack {
|
||||
items: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::random;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() { assert!(true) }
|
||||
|
||||
#[test]
|
||||
fn push_pop_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
|
||||
// lets see if we can push and pop a bunch
|
||||
x.push(&0xabcu16);
|
||||
x.push(&0xcdeu16);
|
||||
x.pop();
|
||||
assert_eq!(x.depth(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_overflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
for i in 0..17 {
|
||||
x.push(&i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_underflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
x.pop();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lots_of_subs() {
|
||||
let mut x = Chip8Stack::new();
|
||||
let stack_contents = [0x123, 0x321, 0xabc, 0xdef,
|
||||
0xbad, 0xbef, 0xfed, 0xcab,
|
||||
0xbed, 0xcad, 0xfeb, 0xcab,
|
||||
0xfff, 0x000, 0x001];
|
||||
for i in stack_contents {
|
||||
x.push(&i);
|
||||
}
|
||||
|
||||
assert_eq!(x.depth(), 15);
|
||||
|
||||
// up to 50 random loops
|
||||
let num_loops: u8 = random::<u8>() % 50;
|
||||
for i in 0..num_loops {
|
||||
let start_count = x.depth();
|
||||
let num_pop = random::<u8>() % x.depth() as u8;
|
||||
for current_pop in 0..num_pop {
|
||||
x.pop();
|
||||
}
|
||||
|
||||
let post_pop_count = x.depth();
|
||||
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
|
||||
for current_push in 0..num_pop {
|
||||
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
|
||||
}
|
||||
assert_eq!(x.depth(), 15);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use glium::RawUniformValue::Vec2;
|
||||
use image::load;
|
||||
use imgui::sys::ImColor;
|
||||
use imgui::{ImColor32, Ui};
|
||||
use log::{debug, trace};
|
||||
use ratatui::{style::Style, widgets::Widget};
|
||||
|
||||
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
|
||||
|
||||
pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200;
|
||||
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
|
||||
pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70];
|
||||
pub const CHIP8FONT_2: [u8; 5] = [0xF0, 0x10, 0xF0, 0x80, 0xF0];
|
||||
pub const CHIP8FONT_3: [u8; 5] = [0xF0, 0x10, 0xF0, 0x10, 0xF0];
|
||||
pub const CHIP8FONT_4: [u8; 5] = [0x90, 0x90, 0xF0, 0x10, 0x10];
|
||||
pub const CHIP8FONT_5: [u8; 5] = [0xF0, 0x80, 0xF0, 0x10, 0xF0];
|
||||
pub const CHIP8FONT_6: [u8; 5] = [0xF0, 0x80, 0xF0, 0x90, 0xF0];
|
||||
pub const CHIP8FONT_7: [u8; 5] = [0xF0, 0x10, 0x20, 0x40, 0x40];
|
||||
pub const CHIP8FONT_8: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0xF0];
|
||||
pub const CHIP8FONT_9: [u8; 5] = [0xF0, 0x90, 0xF0, 0x10, 0xF0];
|
||||
pub const CHIP8FONT_A: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0x90];
|
||||
pub const CHIP8FONT_B: [u8; 5] = [0xE0, 0x90, 0xE0, 0x90, 0xE0];
|
||||
pub const CHIP8FONT_C: [u8; 5] = [0xF0, 0x80, 0x80, 0x80, 0xF0];
|
||||
pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0];
|
||||
pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0];
|
||||
pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Chip8SystemMemory {
|
||||
memory: [u8; CHIP8_MEMORY_SIZE as usize],
|
||||
}
|
||||
|
||||
impl Default for Chip8SystemMemory {
|
||||
fn default() -> Self {
|
||||
|
||||
let mut x = Chip8SystemMemory {
|
||||
memory: [0x00; CHIP8_MEMORY_SIZE as usize],
|
||||
};
|
||||
|
||||
x.load_fonts_to_memory();
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
const cell_width: i32 = 5i32;
|
||||
const cell_height: i32 = 5i32;
|
||||
|
||||
impl Chip8SystemMemory {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Chip8SystemMemory {
|
||||
memory: [0x00; CHIP8_MEMORY_SIZE as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(self, address: u16) -> u8 {
|
||||
trace!("PEEK: {} / {}", address, self.memory[address as usize].clone());
|
||||
self.memory[address as usize]
|
||||
}
|
||||
|
||||
pub fn poke(&mut self, address: u16, value: u8) {
|
||||
trace!("POKE: {} / {} to {}", address, self.memory[address as usize], value);
|
||||
self.memory[address as usize] = value;
|
||||
}
|
||||
|
||||
pub fn load_program(&mut self, program_to_load: Box<Vec<u8>>) {
|
||||
for load_index in 0..program_to_load.len() {
|
||||
self.poke((load_index + 0x200) as u16, program_to_load[load_index]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_fonts_to_memory(&mut self) {
|
||||
let all_font_characters = [
|
||||
CHIP8FONT_0,
|
||||
CHIP8FONT_1,
|
||||
CHIP8FONT_2,
|
||||
CHIP8FONT_3,
|
||||
CHIP8FONT_4,
|
||||
CHIP8FONT_5,
|
||||
CHIP8FONT_6,
|
||||
CHIP8FONT_7,
|
||||
CHIP8FONT_8,
|
||||
CHIP8FONT_9,
|
||||
CHIP8FONT_A,
|
||||
CHIP8FONT_B,
|
||||
CHIP8FONT_C,
|
||||
CHIP8FONT_D,
|
||||
CHIP8FONT_E,
|
||||
CHIP8FONT_F,
|
||||
];
|
||||
|
||||
for (font_index, current_font) in all_font_characters.iter().enumerate() {
|
||||
for font_mem_offset in 0..=4 {
|
||||
let real_offset = font_index * 5 + font_mem_offset;
|
||||
self.poke(real_offset as u16, current_font[font_mem_offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_smoke() {
|
||||
let m = Chip8SystemMemory::default();
|
||||
for i in 0..5 {
|
||||
assert_eq!(m.peek(i), CHIP8FONT_0[i as usize]);
|
||||
}
|
||||
|
||||
assert_eq!(m.peek((CHIP8_MEMORY_SIZE - 1) as u16), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_data_loaded_correctly() {
|
||||
let to_load = [ 0x01, 0x02, 0x03, 0x04, 0x05 , 0x06 ];
|
||||
let mut x = Chip8SystemMemory::default();
|
||||
|
||||
for (index, value) in [1..10].iter().enumerate() {
|
||||
assert_ne!(x.peek(0), 0x01);
|
||||
x.poke(0, 0x01);
|
||||
assert_eq!(x.peek(0), 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_load_program() {
|
||||
// first line of 1-chip-logo.ch8
|
||||
let program_to_load = [0x00e0, 0x6101, 0x6008, 0xa250, 0xd01f, 0x6010, 0xa25f, 0xd01f];
|
||||
|
||||
let mut x = Chip8SystemMemory::new();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
pub struct InstructionUtil {}
|
||||
|
||||
impl InstructionUtil {
|
||||
pub fn byte_to_bools(to_convert: u8) -> [bool; 8] {
|
||||
let mut return_values = [false; 8];
|
||||
for i in 0..8 {
|
||||
let new_value = to_convert >> i & 0x1 == 1;
|
||||
return_values[i as usize] = new_value;
|
||||
}
|
||||
return_values
|
||||
}
|
||||
|
||||
pub fn bools_to_byte(to_convert: [bool; 8]) -> u8 {
|
||||
let mut return_value = 0u8;
|
||||
for i in 0..to_convert.len() {
|
||||
let new_bit = 0x1 << i;
|
||||
if to_convert[i] {
|
||||
return_value = return_value | new_bit
|
||||
}
|
||||
}
|
||||
return_value
|
||||
}
|
||||
|
||||
pub fn split_bytes(to_split: u16) -> (u8, u8) {
|
||||
let high = to_split.rotate_left(8) as u8;
|
||||
let low = (to_split & 0xff) as u8;
|
||||
|
||||
(high, low)
|
||||
}
|
||||
|
||||
pub fn join_bytes(high: u8, low: u8) -> u16 {
|
||||
let result = (high as u16) << 8 | low as u16;
|
||||
result
|
||||
}
|
||||
|
||||
// nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
|
||||
pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0x0FFF
|
||||
}
|
||||
|
||||
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
|
||||
pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0x000F
|
||||
}
|
||||
|
||||
// x - A 4-bit value, the lower 4 bits of the high byte of the instruction
|
||||
pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x0F00).rotate_right(8)
|
||||
}
|
||||
|
||||
// y - A 4-bit value, the upper 4 bits of the low byte of the instruction
|
||||
pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x00F0).rotate_right(4)
|
||||
}
|
||||
|
||||
// kk or byte - An 8-bit value, the lowest 8 bits of the instruction
|
||||
pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x00FF)
|
||||
}
|
||||
|
||||
pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 {
|
||||
(to_read_from & 0x0f00) >> 8
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_bytes() {
|
||||
// from 0xABCD we should have AB high, CD low
|
||||
let (low, high) = InstructionUtil::split_bytes(0xabcd);
|
||||
assert_eq!(low, 0xAB);
|
||||
assert_eq!(high, 0xCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_bytes() {
|
||||
// from 0xAB low and 0xCD high we get 0xABCD
|
||||
let merged = InstructionUtil::join_bytes(0xcd, 0xab);
|
||||
assert_eq!(merged, 0xcdab);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_from_instruction() {
|
||||
// from 0xABCD
|
||||
let source = 0xABCD;
|
||||
assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD);
|
||||
assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD);
|
||||
assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB);
|
||||
assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC);
|
||||
assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ubln() {
|
||||
// from 0xABCD we should see B
|
||||
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB);
|
||||
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0123), 0x1);
|
||||
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0000), 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_bool_changes() {
|
||||
assert_eq!(InstructionUtil::byte_to_bools(0b00000000), [false, false, false, false, false, false, false, false]);
|
||||
assert_eq!(InstructionUtil::byte_to_bools(0b11111111), [true, true, true, true, true, true, true, true]);
|
||||
assert_eq!(InstructionUtil::byte_to_bools(0b11001100), [false, false, true, true, false, false, true, true]);
|
||||
assert_eq!(InstructionUtil::byte_to_bools(0b11110000), [false, false, false, false, true, true, true, true]);
|
||||
assert_eq!(InstructionUtil::bools_to_byte([false, false, false, false, false, false, false, false]), 0b00000000);
|
||||
assert_eq!(InstructionUtil::bools_to_byte([true, true, true, true, true, true, true, true]), 0b11111111);
|
||||
assert_eq!(InstructionUtil::bools_to_byte([false, false, true, true, false, false, true, true]), 0b11001100);
|
||||
assert_eq!(InstructionUtil::bools_to_byte([false, false, false, false, true, true, true, true]), 0b11110000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
use log::{debug, trace};
|
||||
use crate::constants::CHIP8_VIDEO_MEMORY;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Chip8Video {
|
||||
memory: [bool; CHIP8_VIDEO_MEMORY],
|
||||
pub has_frame_changed: bool
|
||||
}
|
||||
|
||||
impl Chip8Video {
|
||||
pub fn cls(&mut self) {
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
self.memory[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_frame(&mut self) {
|
||||
self.has_frame_changed = false;
|
||||
}
|
||||
|
||||
pub fn new(initial_configuration: [bool; CHIP8_VIDEO_MEMORY]) -> Self {
|
||||
Self {
|
||||
memory: initial_configuration,
|
||||
has_frame_changed: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(self, address: u16) -> bool {
|
||||
self.memory[address as usize]
|
||||
}
|
||||
|
||||
pub fn poke(&mut self, address: u16, new_value: bool) -> Self {
|
||||
trace!("OFFSET: {address} - POKING {new_value}");
|
||||
let old_value = self.memory[address as usize];
|
||||
if old_value != new_value {
|
||||
trace!("**VIDEO** TOGGLING");
|
||||
self.has_frame_changed = true;
|
||||
} else {
|
||||
trace!("NOT TOGGLING");
|
||||
}
|
||||
self.memory[address as usize] = new_value;
|
||||
self.to_owned()
|
||||
}
|
||||
|
||||
pub fn poke_byte(&mut self, first_address: u16, to_write: u8) -> Self {
|
||||
for i in (0..8).rev() {
|
||||
let shifted = ((1 << i) & to_write) >> i;
|
||||
//
|
||||
let target_address = first_address + (7 - i);
|
||||
let is_set = shifted == 1;
|
||||
self.poke(target_address, is_set);
|
||||
}
|
||||
self.to_owned()
|
||||
}
|
||||
|
||||
pub fn poke_sprite(&mut self, first_address: u16, to_write: Vec<u8>) -> Self {
|
||||
let sprite_length = to_write.len();
|
||||
|
||||
for (index, byte) in to_write.iter().enumerate() {
|
||||
let real_address = index * 64;
|
||||
self.poke_byte(real_address as u16, *byte);
|
||||
}
|
||||
self.to_owned()
|
||||
}
|
||||
|
||||
pub fn format_as_string(self) -> String {
|
||||
let mut output = String::new();
|
||||
for row in 0..32 {
|
||||
for column in 0..64 {
|
||||
let data_offset = row * 64 + column;
|
||||
debug!("Rendering {data_offset} with value {}", self.memory[data_offset]);
|
||||
if self.memory[data_offset] {
|
||||
output += "*"
|
||||
} else {
|
||||
output += " "
|
||||
}
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Chip8Video {
|
||||
fn default() -> Self {
|
||||
Self { memory: [false; CHIP8_VIDEO_MEMORY as usize], has_frame_changed: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_3, CHIP8FONT_4, CHIP8FONT_5, CHIP8FONT_6, CHIP8FONT_7};
|
||||
use crate::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() { assert!(true) }
|
||||
|
||||
#[test]
|
||||
fn default_test() {
|
||||
let mut x = Chip8Video::default();
|
||||
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!x.peek(i as u16));
|
||||
// then flip the value and test again.
|
||||
&x.poke(i as u16, true);
|
||||
assert!(x.peek(i as u16));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_test_1() {
|
||||
let mut x = Chip8Video::default();
|
||||
let mut working_string = String::new();
|
||||
for i in 0..32 {
|
||||
working_string += &*(" ".repeat(64) + "\n");
|
||||
}
|
||||
|
||||
assert_eq!(working_string, x.format_as_string());
|
||||
|
||||
let mut working_string = String::new();
|
||||
// set a checkerboard...
|
||||
for cb_row in 0..32 {
|
||||
for cb_col in 0..64 {
|
||||
let data_offset = cb_row * 64 + cb_col;
|
||||
if data_offset % 2 == 0 {
|
||||
x.poke(data_offset, true);
|
||||
working_string += "*";
|
||||
} else {
|
||||
x.poke(data_offset, false);
|
||||
working_string += " ";
|
||||
}
|
||||
}
|
||||
working_string += "\n";
|
||||
}
|
||||
|
||||
assert_eq!(working_string, x.format_as_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_initial_memory() {
|
||||
let mut x = Chip8Video::default();
|
||||
// let mut initial_memory = [false; CHIP8_VIDEO_MEMORY];
|
||||
let mut ws = String::new();
|
||||
// set our checkerboard
|
||||
for cbr in 0..32 {
|
||||
for cbc in 0..64 {
|
||||
let dof = cbr * 64 + cbc;
|
||||
if (dof as i32 % 2) == 0 {
|
||||
x.poke(dof, true);
|
||||
ws += "*";
|
||||
} else {
|
||||
ws += " ";
|
||||
}
|
||||
}
|
||||
ws += "\n";
|
||||
}
|
||||
assert_eq!(x.format_as_string(), ws);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_byte() {
|
||||
let to_poke = 0b11001111;
|
||||
let mut x = Chip8Video::default();
|
||||
x.poke_byte(0x05, to_poke);
|
||||
let mut expected = String::new();
|
||||
expected = " ** **** \n".to_string();
|
||||
for i in 0..31 {
|
||||
expected += &*(" ".repeat(64) + "\n");
|
||||
}
|
||||
assert_eq!(x.format_as_string(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cls() {
|
||||
let mut initial_memory = [false; CHIP8_VIDEO_MEMORY];
|
||||
let mut ws = String::new();
|
||||
// set our checkerboard
|
||||
for cbr in 0..32 {
|
||||
for cbc in 0..64 {
|
||||
let dof = cbr * 64 + cbc;
|
||||
if (dof as i32 % 2) == 0 {
|
||||
initial_memory[dof] = true;
|
||||
}
|
||||
ws += " ";
|
||||
}
|
||||
ws += "\n";
|
||||
}
|
||||
let mut set_x = Chip8Video::new(initial_memory);
|
||||
set_x.cls();
|
||||
assert_eq!(set_x.format_as_string(), ws);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_byte_test() {
|
||||
let to_poke = 0b10101010;
|
||||
let mut v = Chip8Video::default();
|
||||
v.poke_byte(0x00, to_poke);
|
||||
assert!(v.peek(0x00));
|
||||
assert!(v.peek(0x02));
|
||||
assert!(v.peek(0x04));
|
||||
assert!(v.peek(0x06));
|
||||
assert!(!v.peek(0x01));
|
||||
assert!(!v.peek(0x03));
|
||||
assert!(!v.peek(0x05));
|
||||
assert!(!v.peek(0x07));
|
||||
for i in 0x8..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!v.peek(i as u16));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_multi_line_test() {
|
||||
let mut v = Chip8Video::default();
|
||||
let to_poke = [
|
||||
0b00000000,
|
||||
0b11111111,
|
||||
0b10101010,
|
||||
0b01010101
|
||||
];
|
||||
|
||||
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
|
||||
let base_offset = byte_in_set * 64;
|
||||
v.poke_byte(base_offset as u16, *byte_to_poke);
|
||||
}
|
||||
|
||||
// row 2 column 1
|
||||
{
|
||||
assert!(v.peek(0x40));
|
||||
assert!(v.peek(0x41));
|
||||
assert!(v.peek(0x42));
|
||||
assert!(v.peek(0x43));
|
||||
assert!(v.peek(0x44));
|
||||
assert!(v.peek(0x45));
|
||||
assert!(v.peek(0x46));
|
||||
assert!(v.peek(0x47));
|
||||
|
||||
// row 3 column 1
|
||||
assert!(!v.peek(0xC0));
|
||||
assert!(v.peek(0xC1));
|
||||
assert!(!v.peek(0xC2));
|
||||
assert!(v.peek(0xC3));
|
||||
assert!(!v.peek(0xC4));
|
||||
assert!(v.peek(0xC5));
|
||||
assert!(!v.peek(0xC6));
|
||||
assert!(v.peek(0xC7));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moved_poke_test() {
|
||||
let mut v = Chip8Video::default();
|
||||
let to_poke = [
|
||||
0b00000000,
|
||||
0b11111111,
|
||||
0b10101010,
|
||||
0b01010101
|
||||
];
|
||||
|
||||
let x_offset = 20;
|
||||
let y_offset = 5;
|
||||
|
||||
|
||||
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
|
||||
let base_offset = (x_offset + byte_in_set) * 64 + y_offset;
|
||||
v.poke_byte(base_offset as u16, *byte_to_poke);
|
||||
}
|
||||
|
||||
let test_offset = (x_offset * 64 + y_offset) as u16;
|
||||
assert!(!v.peek(test_offset));
|
||||
assert!(!v.peek(test_offset + 1));
|
||||
assert!(!v.peek(test_offset + 2));
|
||||
assert!(!v.peek(test_offset + 3));
|
||||
assert!(!v.peek(test_offset + 4));
|
||||
assert!(!v.peek(test_offset + 5));
|
||||
assert!(!v.peek(test_offset + 6));
|
||||
assert!(!v.peek(test_offset + 7));
|
||||
|
||||
let test_offset= test_offset + 0x40;
|
||||
assert!(v.peek(test_offset));
|
||||
assert!(v.peek(test_offset + 1));
|
||||
assert!(v.peek(test_offset + 2));
|
||||
assert!(v.peek(test_offset + 3));
|
||||
assert!(v.peek(test_offset + 4));
|
||||
assert!(v.peek(test_offset + 5));
|
||||
assert!(v.peek(test_offset + 6));
|
||||
assert!(v.peek(test_offset + 7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_sprite_test() {
|
||||
let mut v = Chip8Video::default();
|
||||
let to_poke = [
|
||||
0b00000000,
|
||||
0b11111111,
|
||||
0b10101010,
|
||||
0b01010101
|
||||
];
|
||||
|
||||
v.poke_sprite(0x00, to_poke.into());
|
||||
|
||||
assert!(v.peek(0x40));
|
||||
assert!(v.peek(0x41));
|
||||
assert!(v.peek(0x42));
|
||||
assert!(v.peek(0x43));
|
||||
assert!(v.peek(0x44));
|
||||
assert!(v.peek(0x45));
|
||||
assert!(v.peek(0x46));
|
||||
assert!(v.peek(0x47));
|
||||
|
||||
// row 3 column 1
|
||||
assert!(!v.peek(0xC0));
|
||||
assert!(v.peek(0xC1));
|
||||
assert!(!v.peek(0xC2));
|
||||
assert!(v.peek(0xC3));
|
||||
assert!(!v.peek(0xC4));
|
||||
assert!(v.peek(0xC5));
|
||||
assert!(!v.peek(0xC6));
|
||||
assert!(v.peek(0xC7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_change_registered() {
|
||||
let mut v = Chip8Video::default();
|
||||
v.poke(0x01, true);
|
||||
assert!(v.has_frame_changed);
|
||||
|
||||
v.start_frame();
|
||||
assert!(!v.has_frame_changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_checkboard() {
|
||||
let mut v = Chip8Video::default();
|
||||
for current_row in 0..CHIP8_VIDEO_WIDTH {
|
||||
for current_col in 0..(CHIP8_VIDEO_HEIGHT / 8){
|
||||
let offset = current_row * CHIP8_VIDEO_HEIGHT + current_col;
|
||||
println!("CHECKBOARD OFFSET = {offset}");
|
||||
v.poke(offset as u16, offset % 2 == 0);
|
||||
}
|
||||
}
|
||||
println!("{}", v.format_as_string());
|
||||
println!("fsck is a cool tool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_test() {
|
||||
let mut x = Chip8Video::default();
|
||||
|
||||
x.poke_byte(0x00, CHIP8FONT_0[0]);
|
||||
x.poke_byte(0x40, CHIP8FONT_0[1]);
|
||||
x.poke_byte(0x80, CHIP8FONT_0[2]);
|
||||
x.poke_byte(0xC0, CHIP8FONT_0[3]);
|
||||
x.poke_byte(0x100, CHIP8FONT_0[4]);
|
||||
|
||||
assert_eq!(std::fs::read_to_string("../resources/test/test_video_zero.asc")
|
||||
.unwrap(),
|
||||
x.format_as_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_sprite_test() {
|
||||
let mut x = Chip8Video::default();
|
||||
// draw a row of digits 01234567
|
||||
|
||||
let start_offset = 0x00;
|
||||
let per_row_skip = 0x40;
|
||||
let to_draw = [CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_3, CHIP8FONT_4, CHIP8FONT_5, CHIP8FONT_6, CHIP8FONT_7];
|
||||
for (index, sprite) in to_draw.iter().enumerate() {
|
||||
let data_base_offset = index * 0x8;
|
||||
println!("STARTING {index} at 0x{data_base_offset:04x} ({data_base_offset})");
|
||||
x.poke_byte(data_base_offset as u16, sprite[0]);
|
||||
x.poke_byte((data_base_offset + 0x40) as u16, sprite[1]);
|
||||
x.poke_byte((data_base_offset + 0x80) as u16, sprite[2]);
|
||||
x.poke_byte((data_base_offset + 0xC0) as u16, sprite[3]);
|
||||
x.poke_byte((data_base_offset + 0x100) as u16, sprite[4]);
|
||||
}
|
||||
|
||||
|
||||
assert_eq!(std::fs::read_to_string("../resources/test/test_multi_sprite.asc")
|
||||
.unwrap(),
|
||||
x.format_as_string()); }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub const CHIP8_REGISTER_COUNT: i32 = 16;
|
||||
pub const CHIP8_MEMORY_SIZE: i32 = 4096i32;
|
||||
pub const CHIP8_VIDEO_WIDTH: i32 = 64i32;
|
||||
pub const CHIP8_VIDEO_HEIGHT: i32 = 32i32;
|
||||
pub const CHIP8_VIDEO_MEMORY: usize = (CHIP8_VIDEO_HEIGHT * CHIP8_VIDEO_WIDTH) as usize;
|
||||
pub const CHIP8_ROM_SIZE: usize = 512;
|
||||
@@ -0,0 +1,16 @@
|
||||
pub mod chip8 {
|
||||
pub mod video;
|
||||
pub mod sound_timer;
|
||||
pub mod delay_timer;
|
||||
pub mod keypad;
|
||||
pub mod computer;
|
||||
pub mod system_memory;
|
||||
pub mod instructions;
|
||||
pub mod cpu_states;
|
||||
pub mod util;
|
||||
pub mod registers;
|
||||
|
||||
pub mod stack;
|
||||
}
|
||||
|
||||
pub mod constants;
|
||||
@@ -0,0 +1,11 @@
|
||||
use emmaemu::chip8::computer::Chip8Computer;
|
||||
|
||||
#[test]
|
||||
fn smoke() { assert!(true) }
|
||||
|
||||
#[test]
|
||||
fn test_rom_1_works() {
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user