weekend work

This commit is contained in:
2024-11-04 10:38:46 -05:00
parent 5663123f81
commit 434cf92414
29 changed files with 564 additions and 654 deletions
+6 -1
View File
@@ -6,8 +6,13 @@ autobenches = true
[dependencies]
pretty_env_logger.workspace = true
rand.workspace = true
rand.workspace = true
log.workspace = true
# beep = "0.3.0"
chrono.workspace = true
dimensioned.workspace = true
serde.workspace = true
serde_json.workspace = true
flate2 = "1.0"
clap = { version = "4.5.20", features = ["derive"] }
hex = "0.4.3"
+57
View File
@@ -0,0 +1,57 @@
use clap::{Parser, Subcommand};
use flate2::{write::{GzEncoder, GzDecoder}, Compression};
use std::io::prelude::*;
use std::io::{self, Write};
#[derive(Parser)]
#[command(author, version, about = "Compress or decompress a string", long_about = None)]
struct Cli {
/// Subcommand: either 'compress' or 'decompress'
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Compress the input string
Compress {
/// The string to compress
input: String,
},
/// Decompress the input string (must be compressed format)
Decompress {
/// The compressed string to decompress, in hex format
input: String,
},
}
fn compress_string(input: &str) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(input.as_bytes()).expect("Compression failed");
encoder.finish().expect("Failed to finish compression")
}
fn decompress_string(input: &[u8]) -> String {
let mut decoder = GzDecoder::new(Vec::new());
decoder.write_all(input).expect("Decompression failed");
let decompressed_data = decoder.finish().expect("Failed to finish decompression");
String::from_utf8(decompressed_data).expect("Invalid UTF-8")
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Compress { input } => {
let compressed = compress_string(&input);
// Convert to hex format for easier readability in the terminal
println!("Compressed (hex): {:?} / from {}b to {}b", hex::encode(compressed.clone()), input.len(), compressed.len());
}
Commands::Decompress { input } => {
// Decode hex string back to bytes
let compressed_bytes = hex::decode(input).expect("Invalid hex input");
let decompressed = decompress_string(&compressed_bytes);
println!("Decompressed: {}", decompressed);
}
}
}
+9 -8
View File
@@ -1,16 +1,18 @@
use log::{debug};
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::keypad::Keypad;
use crate::chip8::quirk_modes::QuirkMode;
use crate::chip8::registers::Chip8Registers;
use crate::chip8::sound_timer::SoundTimer;
use crate::chip8::stack::Chip8Stack;
use log::debug;
use serde::{Deserialize, Serialize};
use super::{
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions, system_memory::Chip8SystemMemory, video::Chip8Video,
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions,
system_memory::Chip8SystemMemory, video::Chip8Video,
};
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct Chip8Computer {
pub num_cycles: i32,
pub memory: Chip8SystemMemory,
@@ -21,7 +23,7 @@ pub struct Chip8Computer {
pub state: Chip8CpuStates,
pub keypad: Keypad,
pub stack: Chip8Stack,
pub quirk_mode: QuirkMode
pub quirk_mode: QuirkMode,
}
impl Default for Chip8Computer {
fn default() -> Self {
@@ -35,7 +37,7 @@ impl Default for Chip8Computer {
state: Chip8CpuStates::default(),
keypad: Keypad::default(),
stack: Chip8Stack::default(),
quirk_mode: QuirkMode::default()
quirk_mode: QuirkMode::default(),
}
}
}
@@ -95,13 +97,12 @@ impl Chip8Computer {
// read the next instruction
let local_memory = &self.memory;
// let mut working_instruction: u16 = 0b0000000000000000;
// let mut working_instruction: u16 = 0b0000000000000000;
let start_pc = self.registers.peek_pc();
let high_byte = (local_memory.peek(start_pc) as u16).rotate_left(8);
let low_byte = local_memory.peek(start_pc + 1) as u16;
let result = high_byte | low_byte;
let decoded_instruction =
Chip8CpuInstructions::decode(result, &self.quirk_mode);
let decoded_instruction = Chip8CpuInstructions::decode(result, &self.quirk_mode);
// todo: THIS IS BAD AND IS A SIDE EFFECT
decoded_instruction.execute(self);
+3 -2
View File
@@ -1,4 +1,6 @@
#[derive(Clone, Copy, Default)]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Default, Serialize, Deserialize)]
pub enum Chip8CpuStates {
#[default]
WaitingForInstruction,
@@ -6,4 +8,3 @@ pub enum Chip8CpuStates {
ExecutingInstruction,
Error,
}
+5 -5
View File
@@ -1,6 +1,8 @@
#[derive(Clone, Copy)]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct DelayTimer {
counter: u8
counter: u8,
}
impl Default for DelayTimer {
@@ -15,9 +17,7 @@ impl DelayTimer {
}
pub fn new() -> Self {
DelayTimer {
counter: 0xff
}
DelayTimer { counter: 0xff }
}
pub fn reset(&mut self) {
+2 -1
View File
@@ -6,6 +6,7 @@ use crate::chip8::util::InstructionUtil;
use crate::constants::*;
use log::debug;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};
use std::ops::BitAnd;
use std::time::Instant;
@@ -18,7 +19,7 @@ y - A 4-bit value, the upper 4 bits of the low byte of the instruction
kk or byte - An 8-bit value, the lowest 8 bits of the instruction
*/
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Chip8CpuInstructions {
/// 0nnn
/// Exit to System Call at nnn
+3 -4
View File
@@ -1,7 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::constants::CHIP8_KEYBOARD;
#[derive(Clone, Copy)]
#[derive(Default)]
#[derive(Clone, Copy, Default, Serialize, Deserialize)]
pub struct Keypad {
keys: [bool; 0x10],
}
@@ -20,7 +21,6 @@ impl Keypad {
if index == 3 {
return_value += format!("|{}|\n", is_lit).as_str();
} else {
return_value += format!("|{}", is_lit).as_str();
}
@@ -31,7 +31,6 @@ impl Keypad {
}
}
impl Keypad {
pub fn push_key(&mut self, key_index: u8) {
self.keys[key_index as usize] = true;
+4 -2
View File
@@ -1,7 +1,9 @@
#[derive(Default, Clone)]
use serde::{Deserialize, Serialize};
#[derive(Default, Clone, Serialize, Deserialize)]
pub enum QuirkMode {
#[default]
Chip8,
XOChip,
SChipModern
SChipModern,
}
+3 -1
View File
@@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize};
/// Registers. numbered 1-16 publicly.
/// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Chip8Registers {
pub registers: [u8; 16],
pub i_register: u16,
+9 -6
View File
@@ -1,8 +1,9 @@
use log::trace;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct SoundTimer {
counter: i32
counter: i32,
}
impl Default for SoundTimer {
@@ -16,9 +17,7 @@ impl SoundTimer {
self.counter
}
pub fn new() -> Self {
SoundTimer {
counter: 0
}
SoundTimer { counter: 0 }
}
pub fn set_timer(&mut self, new_value: i32) {
trace!("SETTING SOUND TIMER TO {new_value}");
@@ -26,7 +25,11 @@ impl SoundTimer {
}
pub fn tick(&mut self) {
trace!("TICKING SOUND FROM {} to {}", self.counter, self.counter - 1);
trace!(
"TICKING SOUND FROM {} to {}",
self.counter,
self.counter - 1
);
if self.counter > 0 {
self.counter -= 1;
/*
+9 -11
View File
@@ -1,17 +1,17 @@
#[derive(Clone)]
#[derive(Default)]
pub struct Chip8Stack {
items: Vec<u16>
}
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct Chip8Stack {
items: Vec<u16>,
}
impl Chip8Stack {
pub fn push(&mut self, new_value: &u16) {
if self.depth() == 16 {
// println!("Deep deep stack?");
panic!("Stack Overflow");
// println!("Deep deep stack?");
panic!("Stack Overflow");
}
self.items.push(*new_value );
self.items.push(*new_value);
}
pub fn pop(&mut self) -> u16 {
if self.items.is_empty() {
@@ -24,9 +24,7 @@ impl Chip8Stack {
}
pub fn new() -> Self {
Chip8Stack {
items: vec![]
}
Chip8Stack { items: vec![] }
}
pub fn reset(&mut self) {
+47 -19
View File
@@ -1,18 +1,17 @@
use log::{trace};
use log::trace;
use crate::constants::*;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Copy)]
#[derive(Clone, Serialize, Deserialize)]
pub struct Chip8SystemMemory {
memory: [u8; CHIP8_MEMORY_SIZE as usize],
memory: Vec<u8>,
}
impl Default for Chip8SystemMemory {
fn default() -> Self {
let mut x = Chip8SystemMemory {
memory: [0x00; CHIP8_MEMORY_SIZE as usize],
};
let mut x = Chip8SystemMemory::new();
x.load_fonts_to_memory();
x.load_schip_fonts_to_memory();
@@ -20,27 +19,44 @@ impl Default for Chip8SystemMemory {
}
}
impl Chip8SystemMemory {
fn empty_memory() -> Vec<u8> {
let mut working_memory = vec![];
pub fn reset(&mut self){
self.memory = [0x00; CHIP8_MEMORY_SIZE as usize];
for _ in 0..CHIP8_MEMORY_SIZE {
working_memory.push(0x00);
}
working_memory
}
pub fn reset(&mut self) {
self.memory = Chip8SystemMemory::empty_memory();
self.load_fonts_to_memory();
self.load_schip_fonts_to_memory();
}
pub fn new() -> Self {
Chip8SystemMemory {
memory: [0x00; CHIP8_MEMORY_SIZE as usize],
memory: Chip8SystemMemory::empty_memory(),
}
}
pub fn peek(&self, address: u16) -> u8 {
trace!("PEEK: {} / {}", address, self.memory[address as usize].clone());
trace!(
"PEEK: {} / {}",
address,
self.memory[address as usize].clone()
);
let effective = address as i32 % CHIP8_MEMORY_SIZE;
self.memory[effective as usize]
}
pub fn poke(&mut self, address: u16, value: u8) {
trace!("POKE: {} / {} to {}", address, self.memory[address as usize], value);
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;
}
@@ -80,10 +96,22 @@ impl Chip8SystemMemory {
pub fn load_schip_fonts_to_memory(&mut self) {
let all_font_characters = [
SCHIPFONT_0, SCHIPFONT_1, SCHIPFONT_2, SCHIPFONT_3,
SCHIPFONT_4, SCHIPFONT_5, SCHIPFONT_6, SCHIPFONT_7,
SCHIPFONT_8, SCHIPFONT_9, SCHIPFONT_A, SCHIPFONT_B,
SCHIPFONT_C, SCHIPFONT_D, SCHIPFONT_E, SCHIPFONT_F
SCHIPFONT_0,
SCHIPFONT_1,
SCHIPFONT_2,
SCHIPFONT_3,
SCHIPFONT_4,
SCHIPFONT_5,
SCHIPFONT_6,
SCHIPFONT_7,
SCHIPFONT_8,
SCHIPFONT_9,
SCHIPFONT_A,
SCHIPFONT_B,
SCHIPFONT_C,
SCHIPFONT_D,
SCHIPFONT_E,
SCHIPFONT_F,
];
for (font_index, current_font) in all_font_characters.iter().enumerate() {
let base_offset = 0x100;
@@ -93,4 +121,4 @@ impl Chip8SystemMemory {
}
}
}
}
}
+32 -19
View File
@@ -1,14 +1,18 @@
use log::{debug};
use crate::chip8::video::Chip8VideoModes::{HighRes, LowRes};
use crate::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_MEMORY, CHIP8_VIDEO_WIDTH, SCHIP_VIDE_MEMORY, SCHIP_VIDEO_HEIGHT, SCHIP_VIDEO_WIDTH};
use crate::constants::{
CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_MEMORY, CHIP8_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT,
SCHIP_VIDEO_WIDTH, SCHIP_VIDE_MEMORY,
};
use log::debug;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Serialize, Deserialize)]
pub enum Chip8VideoModes {
LowRes,
HighRes,
}
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct Chip8Video {
memory: Vec<bool>,
pub has_frame_changed: bool,
@@ -42,12 +46,8 @@ impl Chip8Video {
pub fn cls(&mut self) {
self.memory.clear();
let num_loops = match self.current_res {
LowRes => {
CHIP8_VIDEO_MEMORY
}
HighRes => {
SCHIP_VIDE_MEMORY
}
LowRes => CHIP8_VIDEO_MEMORY,
HighRes => SCHIP_VIDE_MEMORY,
};
for i in 0..num_loops {
self.memory.push(false);
@@ -74,7 +74,9 @@ impl Chip8Video {
};
let effective_address = if address >= loop_value {
address % loop_value
} else { address };
} else {
address
};
self.memory[effective_address as usize]
}
@@ -84,8 +86,8 @@ impl Chip8Video {
let effective_address = address % loop_value;
let old_value = self.memory[effective_address as usize];
let xored_value = new_value ^ old_value; // XOR of the video
// if the frame has already changed we dont care if it changed again.
let xored_value = new_value ^ old_value; // XOR of the video
// if the frame has already changed we dont care if it changed again.
if !self.has_frame_changed && old_value != xored_value {
self.has_frame_changed = true
}
@@ -103,7 +105,10 @@ impl Chip8Video {
pub fn poke_2byte(&mut self, first_address: u16, to_write: [u8; 2]) {
for (idx, _) in to_write.iter().enumerate() {
for i in (0..8).rev() {
self.poke(first_address + (idx * 8) as u16 + (7 - i), (to_write[idx] & (1 << i)) != 0);
self.poke(
first_address + (idx * 8) as u16 + (7 - i),
(to_write[idx] & (1 << i)) != 0,
);
}
}
}
@@ -115,7 +120,10 @@ impl Chip8Video {
for row in 0..height {
for column in 0..width {
let data_offset = row * width + column;
debug!("Rendering {data_offset} with value {}", self.memory[data_offset as usize]);
debug!(
"Rendering {data_offset} with value {}",
self.memory[data_offset as usize]
);
if self.memory[data_offset as usize] {
output += "*"
} else {
@@ -200,7 +208,6 @@ impl Chip8Video {
self.memory[clear_start..].fill(false);
}
pub fn scroll_down(&mut self, how_far: i32) {
let (width, height) = self.get_resolution();
let row_shift = how_far * width;
@@ -212,7 +219,7 @@ impl Chip8Video {
let base_offset: usize = (current_source_offset + current_source_column) as usize;
let extended_offset: usize = base_offset + row_shift as usize;
self.memory[extended_offset] = self.memory[base_offset];
};
}
}
// Clear the new top rows after shifting
@@ -224,7 +231,13 @@ impl Chip8Video {
impl Default for Chip8Video {
fn default() -> Self {
let mut mem = vec![];
for _ in 0..CHIP8_VIDEO_MEMORY { mem.push(false); }
Chip8Video { memory: mem, has_frame_changed: false, current_res: Chip8VideoModes::LowRes }
for _ in 0..CHIP8_VIDEO_MEMORY {
mem.push(false);
}
Chip8Video {
memory: mem,
has_frame_changed: false,
current_res: Chip8VideoModes::LowRes,
}
}
}
+40
View File
@@ -0,0 +1,40 @@
use flate2::write::{GzEncoder, GzDecoder};
use flate2::Compression;
use std::io::prelude::*;
use gemma::chip8::computer::Chip8Computer;
fn load_result(to_load: &str) -> String {
std::fs::read_to_string(format!("resources/test/state/{}", to_load)).unwrap()
}
fn load_compressed_result(to_load: &str) -> String {
// load the file...
// ...then uncompress the string.
let mut decoder = GzDecoder::new(Vec::new());
decoder.write_all(load_result(to_load).as_ref()).expect("Decompression failed");
let decompressed_data = decoder.finish().expect("Failed to finish decompression");
String::from_utf8(decompressed_data).expect("Invalid UTF-8")
}
fn load_rom(to_load: &str) -> Vec<u8> {
std::fs::read(format!("resources/roms/{}", to_load)).unwrap()
}
#[test]
fn smoke_round_trip_serialize_deserialize() {
let x = Chip8Computer::new();
let expected_string = load_result("smoke_001_round_trip_serialize_deserialize.json");
let serialized = serde_json::to_string(&x).unwrap();
let deserialized: Chip8Computer = serde_json::from_str(&expected_string).unwrap();
println!("SERIALIZED [{}]", serialized);
// TODO: using trim here is a hack to handle editors adding a newline at the end of a file...even a 1 line file
assert_eq!(serialized.trim(), expected_string.trim());
assert_eq!(deserialized, x);
}
#[test]
fn computer_001_system_zero_state() {
let x = Chip8Computer::new();
let expected_string = load_compressed_result("smoke_002_round_trip_serialize_deserialize.tflt");
let serialized = serde_json::to_string(&x).unwrap();
}