use crate::test_utils::load_compressed_result; use gemma::chip8::computer::Chip8Computer; use gemma::chip8::computer_manager::Chip8ComputerManager; use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction; use gemma::chip8::delay_timer::DelayTimer; use gemma::chip8::instructions::Chip8CpuInstructions; use gemma::chip8::instructions::Chip8CpuInstructions::JPX; use gemma::chip8::keypad::Keypad; use gemma::chip8::quirk_modes::QuirkMode::{self, Chip8, SChipModern, XOChip}; use gemma::chip8::registers::Chip8Registers; use gemma::chip8::sound_timer::SoundTimer; use gemma::chip8::stack::Chip8Stack; use gemma::chip8::system_memory::Chip8SystemMemory; use gemma::chip8::util::InstructionUtil; use gemma::chip8::video::{Chip8Video, Chip8VideoModes}; use gemma::constants::*; use log::debug; use rand::random; use serde::Serialize; use std::fs::File; use std::io::Read; use test_utils::read_compressed_test_result; mod test_utils; #[test] fn smoke() { assert!(true) } #[test] #[ignore] fn decoder_test_invalid_instructions() { let invalid_to_encode = [ 0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf, 0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0, 0xeaa2, 0xf006, 0xf008, ]; for i in invalid_to_encode { assert_eq!( Chip8CpuInstructions::decode(i, &Chip8).encode(), XXXXERRORINSTRUCTION_ENCODED ); assert!(matches!( Chip8CpuInstructions::decode(i, &Chip8), Chip8CpuInstructions::XXXXERRORINSTRUCTION )); } } #[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")); } #[test] fn stack_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] fn video_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 video_join_bytes() { // from 0xAB low and 0xCD high we get 0xABCD let merged = InstructionUtil::join_bytes(0xcd, 0xab); assert_eq!(merged, 0xcdab); } #[test] fn instruction_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 instruction_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 instruction_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 ); } fn real_build_checkboard(in_hd: bool) -> Chip8Video { let mut r = Chip8Video::default(); let (width, height) = if in_hd { r.set_highres(); (SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT) } else { (CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT) }; println!("BUILDING BOARD WITH SIZE OF {width}x{height}"); for row in 0..height { let data_offset = row * width; for col in 0..width { // XOR row and column indices to alternate in a checkerboard pattern let to_poke = (row % 2) ^ (col % 2) == 1; let local_offset: u16 = (data_offset + col) as u16; r.poke(local_offset, to_poke); } } r } fn build_checkboard_hd() -> Chip8Video { real_build_checkboard(true) } fn build_checkerboard() -> Chip8Video { real_build_checkboard(false) } #[test] fn video_default_test() { let mut x = Chip8Video::default(); for i in 0..CHIP8_VIDEO_MEMORY { assert!(!x.clone().peek(i as u16)); // then flip the value and test again. x.poke(i as u16, true); assert!(x.clone().peek(i as u16)); } } #[test] fn video_set_initial_memory_sd() { 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 video_poke_byte_test() { 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 video_poke_2byte_test() { let to_poke: [u8; 2] = [0b11001111, 0b00111100]; let mut x = Chip8Video::default(); x.poke_2byte(0x00, to_poke); let mut expected = String::new(); expected = "** **** **** ".to_string() + &*" ".repeat(64 - 16).to_string() + "\n"; for i in 0..31 { expected += &*((*" ".repeat(64)).to_string() + "\n"); } assert_eq!(expected, x.format_as_string()); } #[test] fn video_poke_multirow_2_byte_sprite() { // take 2 rows of 16bits and write them to memory } #[test] fn video_cls_stddef() { let width = 64; let height = 32; let initial_memory = vec![]; let mut ws = String::new(); let mut set_x = Chip8Video::new(initial_memory.into()); for cbr in 0..32 { ws += &*" ".repeat(width); ws += "\n"; } set_x.cls(); assert_eq!(set_x.format_as_string(), ws); } #[test] fn video_poke_byte_test_2() { let to_poke = 0b10101010; let mut v = Chip8Video::default(); v.poke_byte(0x00, to_poke); assert!(v.clone().peek(0x00)); assert!(!v.clone().peek(0x01)); assert!(v.clone().peek(0x02)); assert!(!v.clone().peek(0x03)); assert!(v.clone().peek(0x04)); assert!(!v.clone().peek(0x05)); assert!(v.clone().peek(0x06)); assert!(!v.clone().peek(0x07)); for i in 0x8..CHIP8_VIDEO_MEMORY { assert!(!v.clone().peek(i as u16)); } } #[test] fn video_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.clone().peek(0x40)); assert!(v.clone().peek(0x41)); assert!(v.clone().peek(0x42)); assert!(v.clone().peek(0x43)); assert!(v.clone().peek(0x44)); assert!(v.clone().peek(0x45)); assert!(v.clone().peek(0x46)); assert!(v.clone().peek(0x47)); // row 3 column 1 assert!(!v.clone().peek(0xC0)); assert!(v.clone().peek(0xC1)); assert!(!v.clone().peek(0xC2)); assert!(v.clone().peek(0xC3)); assert!(!v.clone().peek(0xC4)); assert!(v.clone().peek(0xC5)); assert!(!v.clone().peek(0xC6)); assert!(v.clone().peek(0xC7)); } } #[test] fn video_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.clone().peek(test_offset)); assert!(!v.clone().peek(test_offset + 1)); assert!(!v.clone().peek(test_offset + 2)); assert!(!v.clone().peek(test_offset + 3)); assert!(!v.clone().peek(test_offset + 4)); assert!(!v.clone().peek(test_offset + 5)); assert!(!v.clone().peek(test_offset + 6)); assert!(!v.clone().peek(test_offset + 7)); let test_offset = test_offset + 0x40; assert!(v.clone().peek(test_offset)); assert!(v.clone().peek(test_offset + 1)); assert!(v.clone().peek(test_offset + 2)); assert!(v.clone().peek(test_offset + 3)); assert!(v.clone().peek(test_offset + 4)); assert!(v.clone().peek(test_offset + 5)); assert!(v.clone().peek(test_offset + 6)); assert!(v.clone().peek(test_offset + 7)); } #[test] fn video_verify_change_registered() { let mut v = Chip8Video::default(); v.poke(0x01, true); v.poke(0x01, true); assert!(v.has_frame_changed); v.start_frame(); assert!(!v.has_frame_changed); } #[test] fn video_write_checkboard() { let v = build_checkerboard(); assert_eq!( v.clone().format_as_string(), load_compressed_result("test_video_write_checkerboard") ); } #[test] #[ignore] fn video_zero_test() { let mut x = Chip8Video::default(); for (byte_index, data_offset) in (0..=0x100).step_by(0x40).enumerate() { x.poke_byte(data_offset as u16, CHIP8FONT_0[byte_index]); } assert_eq!( load_compressed_result("test_video_zero"), x.format_as_string() ); } #[test] fn video_multi_sprite_test() { let mut x = Chip8Video::default(); // draw a row of digits 01234567 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; for (index, offset) in (0..=0x100).step_by(0x40).enumerate() { x.poke_byte((data_base_offset + offset) as u16, sprite[index]); } } assert_eq!( load_compressed_result("test_multi_sprite"), x.format_as_string() ); } #[test] fn video_reset_test() { let mut x = build_checkerboard(); x.reset(); assert_eq!( x.format_as_string(), load_compressed_result("test_reset_clears_video") ); } #[test] fn video_collision_test() { // Setup: Set 0xFF to 0x00 with a new frame ready // Action: Run Poke to the same area // Test: Verify the 'changed' flag is tripped let mut x = Chip8Video::default(); x.poke_byte(0x00, 0xff); x.tick(); // set the cell thats already set... x.poke(0x00, true); // it becomes unset and theres a frame changed assert!(!x.peek(0x00)); assert!(x.clone().has_frame_changed); } #[test] fn video_collision_test2() { let mut x = Chip8Video::default(); x.poke_byte(0x00, 0b11110000); assert!(x.has_frame_changed); x.tick(); assert!(!x.has_frame_changed); // clear the 'has changed' flag // now set a no-collision value x.poke_byte(0x00, 0b00001111); assert!(x.has_frame_changed); } #[test] fn video_collision_test3() { // draw a couple sprites that do not overlap. // goal being drawing without triggering the collision // detection. let mut x = Chip8Video::default(); x.poke_byte(0x00, 0b11110000); } #[test] fn video_peek_out_of_bounds_doesnt_panic() { let x = Chip8Video::default(); let y = x.clone().peek(2049); let y = x.clone().peek(0); // if we got here we didn't panic assert!(true); } #[test] fn video_scroll_down_1_row_test() { let mut x = build_checkerboard(); x.scroll_down(1); assert_eq!( load_compressed_result("test_video_scroll_down_1"), x.format_as_string() ); } #[test] fn video_scroll_down_10_row_test() { let mut x = build_checkerboard(); x.scroll_down(10); assert_eq!( load_compressed_result("test_video_scroll_down_10"), x.format_as_string() ); } #[test] fn video_high_res_has_right_resolution() { let x = build_checkboard_hd(); println!("[{}]", x.format_as_string()); assert_eq!( load_compressed_result("test_video_highdef"), x.format_as_string() ); } #[test] fn video_scroll_down_1_row_test_schip() { let mut x = build_checkboard_hd(); x.scroll_down(1); assert_eq!( load_compressed_result("test_scroll_down_1_hd"), x.format_as_string() ); } #[test] fn video_scroll_down_10_row_test_schip() { let mut x = build_checkboard_hd(); x.scroll_down(10); assert_eq!( load_compressed_result("test_scroll_down_10_hd"), x.format_as_string() ); } #[test] fn video_scroll_left_4_row_test_std_def() { let mut x = build_checkerboard(); x.scroll_left(); assert_eq!( load_compressed_result("test_scroll_left_4"), x.format_as_string() ); } #[test] fn video_scroll_left_4_row_test_high_def() { let mut x = build_checkboard_hd(); x.scroll_left(); assert_eq!( load_compressed_result("test_scroll_left_4_hd"), x.format_as_string() ); } #[test] fn video_scroll_right_4_row_test_std_def() { let mut x = build_checkerboard(); x.scroll_right(); assert_eq!( load_compressed_result("test_scroll_right_4"), x.format_as_string() ); } #[test] fn video_scroll_right_4_row_test_high_def() { let mut x = build_checkboard_hd(); x.scroll_right(); assert_eq!( load_compressed_result("test_scroll_right_4_hd"), x.format_as_string() ); } #[test] fn instructions_operands_tests() { assert_eq!(Chip8CpuInstructions::SYS(0x000).operands(), "0x0000"); assert_eq!(Chip8CpuInstructions::JPI(0x123).operands(), "0x0123"); assert_eq!(Chip8CpuInstructions::JPA(0x234).operands(), "0x0234"); assert_eq!(Chip8CpuInstructions::LDIA(0x345).operands(), "0x0345"); assert_eq!(Chip8CpuInstructions::CALL(0x456).operands(), "0x0456"); } #[test] fn instruction_ena_dis_tests() { let mut x = Chip8Computer::new(); for quirk in [SChipModern, XOChip] { x.quirk_mode = quirk; assert!(!x.video_memory.is_highres()); Chip8CpuInstructions::HIGH.execute(&mut x); assert!(x.video_memory.is_highres()); Chip8CpuInstructions::HIGH.execute(&mut x); assert!(x.video_memory.is_highres()); Chip8CpuInstructions::LOW.execute(&mut x); assert!(!x.video_memory.is_highres()); } } #[test] fn instruction_test_scrolling_lowres() { for quirk in [SChipModern, XOChip] { let mut x = Chip8Computer::new(); x.video_memory = build_checkerboard(); x.quirk_mode = quirk.clone(); Chip8CpuInstructions::SCR.execute(&mut x); assert_eq!( load_compressed_result("test_scroll_right_4"), x.dump_video_to_string() ); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); x.quirk_mode = quirk.clone(); Chip8CpuInstructions::SCL.execute(&mut x); assert_eq!( load_compressed_result("test_scroll_left_4"), x.dump_video_to_string() ); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); x.quirk_mode = quirk.clone(); Chip8CpuInstructions::SCD(0x01).execute(&mut x); assert_eq!( load_compressed_result("test_video_scroll_down_1"), x.dump_video_to_string() ); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); x.quirk_mode = quirk.clone(); Chip8CpuInstructions::SCD(0xA).execute(&mut x); assert_eq!( load_compressed_result("test_video_scroll_down_10"), x.dump_video_to_string() ); } } #[test] fn computer_dump_keypad_to_string() { let mut x = Chip8Computer::new(); x.keypad.push_key(0x1); x.keypad.push_key(0x2); assert_eq!( load_compressed_result("test_keypad_to_string"), x.dump_keypad_to_string() ); } #[test] fn computer_dump_registers_to_string() { let mut x = Chip8Computer::new(); let values_to_set = [ 0x0b, 0xad, 0xbe, 0xef, 0xca, 0xb0, 0x7a, 0xc0, 0xca, 0x70, 0xba, 0xdb, 0xed, 0x00, 0x00, 0x00, ]; let expected_value = "Vx: 0x0b 0xad 0xbe 0xef 0xca 0xb0 0x7a 0xc0\n 0xca 0x70 0xba 0xdb 0xed 0x00 0x00 0x00\nI: 0x0000\tPC: 0x0200"; for i in 0..16 { x.registers.poke(i, values_to_set[i as usize]); } // now verify. assert_eq!(expected_value, x.dump_registers_to_string()); } #[test] fn video_scroll_up_tests_sd() { let mut x = build_checkerboard(); let distance = 1u8; x.scroll_up(&distance); assert_eq!( load_compressed_result("test_video_scroll_up_test_sd"), x.format_as_string() ); } #[test] fn video_scroll_up_tests_hd() { let mut x = build_checkboard_hd(); let distance = 1u8; x.scroll_up(&distance); assert_eq!( load_compressed_result("test_video_scroll_up_test_hd"), x.format_as_string() ); } #[test] fn video_resolution_changing() { let mut x = Chip8Video::default(); x.set_highres(); assert_eq!(x.get_resolution(), (SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT)); assert!(matches!( x.get_screen_resolution(), Chip8VideoModes::HighRes )); x.set_lowres(); assert_eq!(x.get_resolution(), (CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT)); assert!(matches!(x.get_screen_resolution(), Chip8VideoModes::LowRes)); } #[test] fn delay_timer_default() { let t = DelayTimer::default(); assert_eq!(t.current(), 0xff); } #[test] fn instruction_jpx() { // JPX -> 0xBXnn // Jump to Xnn+Vx let mut x = Chip8Computer::new(); // set X1 to 4... x.registers.poke(0x01, 0x04); // ...use (x1)+0x134 let to_execute = JPX(0x01, 0x34); // expect to set PC to 0x834 to_execute.execute(&mut x); assert_eq!(x.registers.peek_i(), 0x834); } #[test] fn instruction_ldrd() { let mut x = Chip8Computer::new(); x.state = WaitingForInstruction; x.delay_timer.set_timer(0x01); Chip8CpuInstructions::LDRD(0x01).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x01); x.delay_timer.set_timer(0xff); Chip8CpuInstructions::LDRD(0x0).execute(&mut x); assert_eq!(x.registers.peek(0x00), 0xff); x.step_system(); x.step_system(); x.step_system(); x.step_system(); Chip8CpuInstructions::LDRD(0x0).execute(&mut x); assert_eq!(x.registers.peek(0x0), 0xfb); } #[test] fn video_hires_loop_check() { let max_address = SCHIP_VIDE_MEMORY; let x = build_checkboard_hd(); x.peek((max_address + 1) as u16); // if we got here we didn't explode! assert!(true); } #[test] fn system_memory_new() { let x = Chip8SystemMemory::new(); // check its empty. for i in 0..CHIP8_MEMORY_SIZE { assert_eq!(x.peek(i as u16), 0x00); } } #[test] #[ignore] fn video_lowres_schip_draw_chip8_sprite() { let mut x = Chip8Computer::new(); x.quirk_mode = QuirkMode::SChipModern; x.video_memory.set_lowres(); // point at the 1 from chip8 x.registers.poke_i(0x0005); // point to 1,2 for the drawing x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x02); Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x); let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_chip8_sprite_result"); let actual_state = x.dump_state_to_json(); assert_eq!(expected_state, actual_state); } #[test] #[ignore] fn video_lowres_schip_draw_schip_sprite() { let mut x = Chip8Computer::new(); x.quirk_mode = SChipModern; x.video_memory.set_lowres(); x.registers.poke_i(0x0005); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x02); Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x); let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_schip_sprite"); let actual_state = x.dump_state_to_json(); assert_eq!(expected_state, actual_state); } #[test] fn video_highres_schip_draw_chip8_sprite() {} #[test] fn video_highres_schip_draw_schip_sprite() {} #[test] fn quirk_mode_labels() { assert_eq!(format!("{}", Chip8), LABEL_QUIRK_CHIP8); assert_eq!(format!("{}", XOChip), LABEL_QUIRK_XOCHIP); assert_eq!(format!("{}", SChipModern), LABEL_QUIRK_SCHIP); }