adds 'default.oc8' adds control window size to state for load/save of settings maybe? moves TestCompressionTool code into InstructionUtil remove gemmautil and moves into gemma
243 lines
7.5 KiB
Rust
243 lines
7.5 KiB
Rust
use crate::chip8::video::Chip8VideoModes::{HighRes, LowRes};
|
|
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, Serialize, Deserialize, Debug, PartialEq)]
|
|
pub enum Chip8VideoModes {
|
|
LowRes,
|
|
HighRes,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
|
pub struct Chip8Video {
|
|
memory: Vec<bool>,
|
|
pub has_frame_changed: bool,
|
|
current_res: Chip8VideoModes,
|
|
}
|
|
|
|
impl Chip8Video {
|
|
pub fn reset(&mut self) {
|
|
self.cls();
|
|
self.set_lowres();
|
|
self.start_frame();
|
|
}
|
|
|
|
pub fn is_highres(&self) -> bool {
|
|
matches!(self.current_res, HighRes)
|
|
}
|
|
|
|
pub fn set_highres(&mut self) {
|
|
self.current_res = HighRes;
|
|
self.cls();
|
|
}
|
|
|
|
pub fn set_lowres(&mut self) {
|
|
self.current_res = LowRes;
|
|
self.cls();
|
|
}
|
|
|
|
pub fn get_screen_resolution(&mut self) -> Chip8VideoModes {
|
|
self.current_res
|
|
}
|
|
|
|
pub fn cls(&mut self) {
|
|
self.memory.clear();
|
|
let num_loops = match self.current_res {
|
|
LowRes => CHIP8_VIDEO_MEMORY,
|
|
HighRes => SCHIP_VIDE_MEMORY,
|
|
};
|
|
for i in 0..num_loops {
|
|
self.memory.push(false);
|
|
}
|
|
}
|
|
|
|
pub fn start_frame(&mut self) {
|
|
self.has_frame_changed = false;
|
|
}
|
|
|
|
pub fn new(initial_configuration: Box<Vec<bool>>) -> Self {
|
|
Self {
|
|
memory: *initial_configuration,
|
|
has_frame_changed: false,
|
|
current_res: Chip8VideoModes::LowRes,
|
|
}
|
|
}
|
|
|
|
pub fn peek(&self, address: u16) -> bool {
|
|
let loop_value: u16 = if self.is_highres() {
|
|
SCHIP_VIDE_MEMORY as u16
|
|
} else {
|
|
CHIP8_VIDEO_MEMORY as u16
|
|
};
|
|
let effective_address = if address >= loop_value {
|
|
address % loop_value
|
|
} else {
|
|
address
|
|
};
|
|
self.memory[effective_address as usize]
|
|
}
|
|
|
|
pub fn poke(&mut self, address: u16, new_value: bool) {
|
|
let loop_value: u16 = self.get_memory_size() as u16;
|
|
// Loop the address
|
|
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.
|
|
if !self.has_frame_changed && old_value != xored_value {
|
|
self.has_frame_changed = true
|
|
}
|
|
|
|
// println!("VIDEO POKE COMPLETE WITH {effective_address} SET TO {xored_value}");
|
|
self.memory[effective_address as usize] = xored_value;
|
|
}
|
|
|
|
pub fn poke_byte(&mut self, first_address: u16, to_write: u8) {
|
|
for i in (0..8).rev() {
|
|
self.poke(first_address + (7 - i), (to_write & (1 << i)) != 0);
|
|
}
|
|
}
|
|
|
|
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,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn format_as_string(&self) -> String {
|
|
let (width, height) = self.get_resolution();
|
|
println!("FORMATTING {width}x{height}");
|
|
let mut output = String::new();
|
|
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]
|
|
);
|
|
if self.memory[data_offset as usize] {
|
|
output += "*"
|
|
} else {
|
|
output += " "
|
|
}
|
|
}
|
|
output += "\n";
|
|
}
|
|
output
|
|
}
|
|
|
|
pub fn get_resolution(&self) -> (i32, i32) {
|
|
if self.is_highres() {
|
|
(SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT)
|
|
} else {
|
|
(CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT)
|
|
}
|
|
}
|
|
fn get_memory_size(&self) -> i32 {
|
|
let (width, height) = self.get_resolution();
|
|
width * height
|
|
}
|
|
|
|
pub fn tick(&mut self) {
|
|
self.has_frame_changed = false;
|
|
}
|
|
|
|
pub fn scroll_right(&mut self) {
|
|
let (width, height) = self.get_resolution();
|
|
|
|
for current_row in 0..height {
|
|
let row_offset: usize = (current_row * width) as usize;
|
|
|
|
// Shift pixels to the right by 4 in the current row
|
|
for current_column in (0..(width - 4)).rev() {
|
|
let source_address = row_offset + current_column as usize;
|
|
let target_address = source_address + 4;
|
|
self.memory[target_address] = self.memory[source_address];
|
|
}
|
|
|
|
// Clear the first 4 pixels in the current row
|
|
self.memory[row_offset..row_offset + 4].fill(false);
|
|
}
|
|
}
|
|
|
|
pub fn scroll_left(&mut self) {
|
|
let (width, height) = self.get_resolution();
|
|
|
|
for current_row in 0..height {
|
|
let row_offset = current_row * width;
|
|
for current_column in (0..width - 4) {
|
|
let target: usize = (row_offset + current_column) as usize;
|
|
let source: usize = target + 4;
|
|
self.memory[target] = self.memory[source];
|
|
}
|
|
|
|
let clear_start: usize = (row_offset + width - 4) as usize;
|
|
let clear_end: usize = clear_start + 4;
|
|
|
|
self.memory[clear_start..clear_end].fill(false);
|
|
}
|
|
}
|
|
|
|
pub fn scroll_up(&mut self, how_far: &u8) {
|
|
let how_far = *how_far as i32;
|
|
let (width, height) = self.get_resolution();
|
|
let row_shift = how_far * width;
|
|
|
|
for current_source_row in how_far..height {
|
|
let current_source_offset = current_source_row * width;
|
|
for current_source_column in 0..width {
|
|
let base_offset: usize = (current_source_offset + current_source_column) as usize;
|
|
let shifted_offset: usize = base_offset - row_shift as usize;
|
|
self.memory[shifted_offset] = self.memory[base_offset];
|
|
}
|
|
}
|
|
|
|
// Clear the new bottom rows after shifting
|
|
let clear_start = ((height - how_far) * width) as usize;
|
|
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;
|
|
|
|
let max_source_row = height - how_far;
|
|
for current_source_row in (0..max_source_row).rev() {
|
|
let current_source_offset = current_source_row * width;
|
|
for current_source_column in 0..width {
|
|
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
|
|
let clear_end = (how_far * width) as usize;
|
|
self.memory[0..clear_end].fill(false);
|
|
}
|
|
}
|
|
|
|
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: LowRes,
|
|
}
|
|
}
|
|
}
|