rename to gemma

add gemmaegui
This commit is contained in:
2024-10-09 15:16:47 -04:00
parent 067164c657
commit 4217571ded
31 changed files with 2240 additions and 84 deletions
+124
View File
@@ -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)
}
}
+9
View File
@@ -0,0 +1,9 @@
#[derive(Clone, Copy, Default)]
pub enum Chip8CpuStates {
#[default]
WaitingForInstruction,
WaitingForKey,
ExecutingInstruction,
Error,
}
+56
View File
@@ -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
+92
View File
@@ -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));
}
}
+125
View File
@@ -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"));
}
}
+67
View File
@@ -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);
}
}
+104
View File
@@ -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);
}
}
}
+141
View File
@@ -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();
}
}
+120
View File
@@ -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);
}
}
+386
View File
@@ -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()); }
}