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, 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>) -> 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, } } }