use std::collections::{BTreeMap, BTreeSet}; use log::debug; use rand::random; use gemma::chip8::computer::Chip8Computer; use gemma::chip8::delay_timer::DelayTimer; use gemma::chip8::instructions::Chip8CpuInstructions; use gemma::chip8::instructions::Chip8CpuInstructions::JPA; use gemma::chip8::keypad::Keypad; use gemma::chip8::quirk_modes::QuirkMode::{Chip8, SChipModern, XOChip}; use gemma::chip8::registers::Chip8Registers; use gemma::chip8::sound_timer::SoundTimer; use gemma::chip8::stack::Chip8Stack; use gemma::chip8::util::InstructionUtil; use gemma::chip8::video::Chip8Video; use gemma::constants::*; const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/"; fn read_test_result(suffix: &str) -> String { std::fs::read_to_string(TEST_OUTPUT_SAMPLE_DIR.to_owned() + suffix) .unwrap() } #[test] fn smoke() { assert!(true) } #[test] fn encode_decode_test() { assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0); assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE); assert_eq!(Chip8CpuInstructions::SYS(0x123).encode(), 0x0123); assert_eq!(Chip8CpuInstructions::JPA(0x234).encode(), 0x1234); assert_eq!(Chip8CpuInstructions::CALL(0x345).encode(), 0x2345); assert_eq!(Chip8CpuInstructions::SEX(0x4, 0x56).encode(), 0x3456); assert_eq!(Chip8CpuInstructions::SNEB(0xa, 0xbc).encode(), 0x4abc); assert_eq!(Chip8CpuInstructions::SEY(0xa, 0xb).encode(), 0x5ab0); assert_eq!(Chip8CpuInstructions::LDR(0xa, 0xff).encode(), 0x6aff); assert_eq!(Chip8CpuInstructions::ADD(0xa, 0xbc).encode(), 0x7abc); assert_eq!(Chip8CpuInstructions::LDR_Y(0xa, 0xb).encode(), 0x8ab0); assert_eq!(Chip8CpuInstructions::OR(0xb, 0xa).encode(), 0x8ba1); assert_eq!(Chip8CpuInstructions::AND(0xc, 0xd).encode(), 0x8cd2); assert_eq!(Chip8CpuInstructions::ORY(0xd, 0xe).encode(), 0x8de3); assert_eq!(Chip8CpuInstructions::ADDR(0xe, 0xf).encode(), 0x8ef4); assert_eq!(Chip8CpuInstructions::SUB(0xf, 0x0).encode(), 0x8f05); assert_eq!(Chip8CpuInstructions::SHR(0x0, 0x1).encode(), 0x8016); assert_eq!(Chip8CpuInstructions::SUBC(0x1, 0x2).encode(), 0x8127); assert_eq!(Chip8CpuInstructions::SHL(0x3, 0x4).encode(), 0x834e); assert_eq!(Chip8CpuInstructions::SNEY(0xa, 0xb).encode(), 0x9ab0); assert_eq!(Chip8CpuInstructions::LDIA(0x123).encode(), 0xa123); assert_eq!(Chip8CpuInstructions::JPI(0x234).encode(), 0xb234); assert_eq!(Chip8CpuInstructions::RND(0xa, 0xca).encode(), 0xcaca); assert_eq!(Chip8CpuInstructions::DRW(0xa, 0xb, 0x4).encode(), 0xdab4); assert_eq!(Chip8CpuInstructions::SKP(0x1).encode(), 0xe19e); assert_eq!(Chip8CpuInstructions::SKNP(0x2).encode(), 0xe2a1); assert_eq!(Chip8CpuInstructions::LDRD(0x1).encode(), 0xf107); assert_eq!(Chip8CpuInstructions::LDRK(0x4).encode(), 0xf40a); assert_eq!(Chip8CpuInstructions::LDD(0x6).encode(), 0xf615); assert_eq!(Chip8CpuInstructions::LDIS(0xb).encode(), 0xfb18); assert_eq!(Chip8CpuInstructions::ADDI(0xd).encode(), 0xfd1e); assert_eq!(Chip8CpuInstructions::LDFX(0xc).encode(), 0xfc29); assert_eq!(Chip8CpuInstructions::BCD(0xd).encode(), 0xfd33); assert_eq!(Chip8CpuInstructions::LDIX(0xe).encode(), 0xfe55); assert_eq!(Chip8CpuInstructions::LDRI(0x3).encode(), 0xf365); assert_eq!(Chip8CpuInstructions::SDN(0x1).encode(), 0x00C1); assert_eq!(Chip8CpuInstructions::SLF.encode(), 0x00FC); assert_eq!(Chip8CpuInstructions::SRT.encode(), 0x00FB); assert_eq!(Chip8CpuInstructions::EXIT.encode(), 0x00FD); assert_eq!(Chip8CpuInstructions::ENA.encode(), 0x00FF); assert_eq!(Chip8CpuInstructions::DIS.encode(), 0x00FE); assert_eq!(Chip8CpuInstructions::LDF2(0).encode(), 0xF030); assert_eq!(Chip8CpuInstructions::STR(1).encode(), 0xF175); assert_eq!(Chip8CpuInstructions::LIDR(1).encode(), 0xF185); /* assert!(matches!(Chip8CpuInstructions::decode(0xF175), Chip8CpuInstructions::STR(1))); assert!(matches!(Chip8CpuInstructions::decode(0xF185), Chip8CpuInstructions::LIDR(1))); assert!(matches!(Chip8CpuInstructions::decode(0x00C1u16), Chip8CpuInstructions::SDN(0x01))); assert!(matches!(Chip8CpuInstructions::decode(0x00FCu16), Chip8CpuInstructions::SLF)); assert!(matches!(Chip8CpuInstructions::decode(0x00FBu16), Chip8CpuInstructions::SRT)); assert!(matches!(Chip8CpuInstructions::decode(0x00FDu16), Chip8CpuInstructions::EXIT)); assert!(matches!(Chip8CpuInstructions::decode(0x00FEu16), Chip8CpuInstructions::DIS)); assert!(matches!(Chip8CpuInstructions::decode(0x00FFu16), Chip8CpuInstructions::ENA)); assert!(matches!(Chip8CpuInstructions::decode(0xF030u16), Chip8CpuInstructions::LDF2(0))); assert!(matches!(Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS)); assert!(matches!(Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET)); assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SYS(0x123))); assert!(matches!(Chip8CpuInstructions::decode(0x0FFF), Chip8CpuInstructions::SYS(0xfff))); assert!(matches!(Chip8CpuInstructions::decode(0x1002), Chip8CpuInstructions::JPA(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x1FF0), Chip8CpuInstructions::JPA(0xFF0))); assert!(matches!(Chip8CpuInstructions::decode(0x2002), Chip8CpuInstructions::CALL(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x3123), Chip8CpuInstructions::SEX(0x1, 0x23))); assert!(matches!(Chip8CpuInstructions::decode(0x4abc), Chip8CpuInstructions::SNEB(0xa, 0xbc))); assert!(matches!(Chip8CpuInstructions::decode(0x5ab0), Chip8CpuInstructions::SEY(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0x6aff), Chip8CpuInstructions::LDR(0xa, 0xff))); assert!(matches!(Chip8CpuInstructions::decode(0x7abc), Chip8CpuInstructions::ADD(0xa, 0xbc))); assert!(matches!(Chip8CpuInstructions::decode(0x8ab0), Chip8CpuInstructions::LDR_Y(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0x8ba1), Chip8CpuInstructions::OR(0xb, 0xa))); assert!(matches!(Chip8CpuInstructions::decode(0x8cd2), Chip8CpuInstructions::AND(0xc, 0xd))); assert!(matches!(Chip8CpuInstructions::decode(0x8de3), Chip8CpuInstructions::ORY(0xd, 0xe))); assert!(matches!(Chip8CpuInstructions::decode(0x8ef4), Chip8CpuInstructions::ADDR(0xe, 0xf))); assert!(matches!(Chip8CpuInstructions::decode(0x8f05), Chip8CpuInstructions::SUB(0xf, 0x0))); assert!(matches!(Chip8CpuInstructions::decode(0x8016), Chip8CpuInstructions::SHR(0x0, 0x1))); assert!(matches!(Chip8CpuInstructions::decode(0x8127), Chip8CpuInstructions::SUBC(0x1, 0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x834e), Chip8CpuInstructions::SHL(0x3, 0x4))); assert!(matches!(Chip8CpuInstructions::decode(0x9ab0), Chip8CpuInstructions::SNEY(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0xa123), Chip8CpuInstructions::LDIA(0x123))); assert!(matches!(Chip8CpuInstructions::decode(0xb234), Chip8CpuInstructions::JPI(0x234))); assert!(matches!(Chip8CpuInstructions::decode(0xcaca), Chip8CpuInstructions::RND(0xa, 0xca))); assert!(matches!(Chip8CpuInstructions::decode(0xdab4), Chip8CpuInstructions::DRW(0xa, 0xb, 0x4))); assert!(matches!(Chip8CpuInstructions::decode(0xe19e), Chip8CpuInstructions::SKP(0x1))); assert!(matches!(Chip8CpuInstructions::decode(0xe2a1), Chip8CpuInstructions::SKNP(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0xf107), Chip8CpuInstructions::LDRD(0x1))); assert!(matches!(Chip8CpuInstructions::decode(0xf40a), Chip8CpuInstructions::LDRK(0x4))); assert!(matches!(Chip8CpuInstructions::decode(0xf615), Chip8CpuInstructions::LDD(0x6))); assert!(matches!(Chip8CpuInstructions::decode(0xfb18), Chip8CpuInstructions::LDIS(0xb))); assert!(matches!(Chip8CpuInstructions::decode(0xfd1e), Chip8CpuInstructions::ADDI(0xd))); assert!(matches!(Chip8CpuInstructions::decode(0xfc29), Chip8CpuInstructions::LDFX(0xc))); assert!(matches!(Chip8CpuInstructions::decode(0xfd33), Chip8CpuInstructions::BCD(0xd))); assert!(matches!(Chip8CpuInstructions::decode(0xfe55), Chip8CpuInstructions::LDIX(0xe))); assert!(matches!(Chip8CpuInstructions::decode(0xf365), Chip8CpuInstructions::LDRI(0x3))); */ } #[test] 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(), 0xffff); assert!(matches!(Chip8CpuInstructions::decode(i, &Chip8), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); } } /// START OF THE EXECUTION TESTS #[test] fn instruction_tests() { // 0x0nnn Exit to System Call let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0xFA0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xFA0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0x0AF).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x0AF); // 0x1nnn Jump to Address let mut x = Chip8Computer::new(); Chip8CpuInstructions::JPA(0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::JPA(0xABC).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xABC); // 0x6xkk Set Vx = kk let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x); assert_eq!(x.registers.peek(1), 0x12); let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x); assert_eq!(x.registers.peek(2), 0x21); // 0x3xkk Skip next instruction if Vx = kk. // The interpreter compares register Vx to kk, // and if they are equal, increments the program counter by 2. // test setup: Load value 0x84 into V1 let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); // 0x4xkk Skip next instruction if Vx != kk let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x84); // skip, compare 0x84 to 0x84 Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x48); Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); // 0x8xy0 Set value of Vy in Vx let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x02); Chip8CpuInstructions::LDR_Y(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(1), 0x02); // 0x8xy1 Set Vx = Vx OR Vy // 0b0101 0000 (0x50) // | 0b0000 1010 (0x0A) // 0b0101 1010 (0x5A) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b01010000); x.registers.poke(0x02, 0b00001010); Chip8CpuInstructions::OR(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b01011010); // 0x8xy2 Set Vx = Vx AND Vy // 0b1111 1100 (0xFC) // & 0b1100 1010 (0xCA) // 0b1100 1000 (0xC8) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xFC); x.registers.poke(0x02, 0xCA); Chip8CpuInstructions::AND(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0xC8); // 0x8xy3 Set Vx = Vx XOR Vy // 0b1111 1100 (0xFC) // ^ 0b1100 1010 (0xCA) // 0b0011 0110 (0x36) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b11111100); x.registers.poke(0x02, 0b11001010); Chip8CpuInstructions::ORY(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00110110); // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) // T1 T2: Judgement Test // 0x01 0xFF // + 0x01 0x01 // 0x02 F0 0x00 F1 let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 00); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 0x00); assert_eq!(x.registers.peek(0x01), 0x02); let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(1, 2).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(1), 0); /* Set Vx = Vx SHR 1. If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2. */ let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001000); x.registers.poke(0x02, 0b00000000); Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set) assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 0); x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001001); Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 1); let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDIA(0x123).execute(&mut x); assert_eq!(x.registers.peek_i(), 0x123); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn jp_v0addr_test() { let mut x = Chip8Computer::new(); /// jump to I + nnn x.registers.poke(0x0, 0xff); Chip8CpuInstructions::JPI(0x100).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x1FF); } #[test] fn cls_test() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::CLS.execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); for i in 0..CHIP8_VIDEO_MEMORY { assert_eq!(x.video_memory.peek(i as u16), false); } // draw some thing to the video memory x.video_memory.poke(0x01, true); x.video_memory.poke(0x03, true); x.video_memory.poke(0x05, true); Chip8CpuInstructions::CLS.execute(&mut x); for i in 0..CHIP8_VIDEO_MEMORY { assert_eq!(x.video_memory.peek(i as u16), false); } } #[test] fn skip_next_instruction_ne_text() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn addivx_test() { let mut x = Chip8Computer::new(); x.registers.poke_i(0xabc); x.registers.poke(0x0, 0x10); Chip8CpuInstructions::ADDI(0x0).execute(&mut x); assert_eq!(x.registers.peek_i(), 0xacc); } #[test] fn ldstvt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::LDIS(0x01).execute(&mut x); assert_eq!(x.sound_timer.current(), 0xf0); x.sound_timer.tick(); x.sound_timer.tick(); x.sound_timer.tick(); assert_eq!(x.sound_timer.current(), 0xed); } #[test] fn rnd_vx_byte_text() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x); let new_value = x.registers.peek(0x1); assert!(new_value < 0x10); } #[test] fn add_vx_byte_test() { let mut x = Chip8Computer::new(); // set a value in the register x.registers.poke(0x01, 0xab); // add 0x10 to register Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x); assert_eq!(x.registers.peek(1), 0xbb); } #[test] fn sub_vx_vy_test() { let mut x = Chip8Computer::new(); // load values in 2 registers x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x08); Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(0x1), 0x8); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn sne_vx_vy_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x10); Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x00); Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204) } #[test] fn ld_dt_vx_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); Chip8CpuInstructions::LDD(0x1).execute(&mut x); assert_eq!(x.delay_timer.current(), 0x10); for i in 0..0x20 { x.delay_timer.tick(); } assert_eq!(x.delay_timer.current(), 0); } #[test] fn ld_vx_dt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::LDD(0x1).execute(&mut x); x.delay_timer.tick(); x.delay_timer.tick(); x.delay_timer.tick(); assert_eq!(x.delay_timer.current(), 0xed); } #[test] fn subn_vx_vy_test() { // This instruction subtracts the value in // register Vx from the value in register Vy and stores the result in register Vx. // The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx, // the result will wrap around (due to the 8-bit nature of the registers). // The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater // than or equal to Vx), and it is set to 0 if there is a borrow. let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xa0); x.registers.poke(0x2, 0xab); Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x); // expect the result to be 0x0b assert_eq!(x.registers.peek(0x1), 0x0b); // expect the vf register to be set to 1 as there was overflow assert_eq!(x.registers.peek(0xf), 0x1); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xab); x.registers.poke(0x02, 0xa0); Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x); // expect the result to be 11110101, -0xB, -11, 245, 0xF5 assert_eq!(x.registers.peek(0x1), 0xf5); assert_eq!(x.registers.peek(0xf), 0x0); // 8xyE - SHL Vx {, Vy} // Set Vx = Vx SHL 1. // // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2. let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b00100000); Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01000000); assert_eq!(x.registers.peek(0xf), 0x0); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b10101010); Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01010100); assert_eq!(x.registers.peek(0xf), 0x1); // Fx29 - LD F, Vx // Set I = location of sprite for digit Vx. // // The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font. let mut x = Chip8Computer::new(); // target_sprite = 2 // target_offset = 0x5 x.registers.poke(0x1, 0x2); Chip8CpuInstructions::LDFX(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 10); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x06); Chip8CpuInstructions::LDFX(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 30); // Fx33 - LD B, Vx // Store BCD representation of Vx in memory locations I, I+1, and I+2. // // The interpreter takes the decimal value of Vx, and places the hundreds digit // in memory at location in I, the tens digit at location I+1, // and the ones digit at location I+2. let mut x = Chip8Computer::new(); // load the value 123 (0x7b) x.registers.poke(0x1, 0x7b); x.registers.poke_i(0x500); Chip8CpuInstructions::BCD(0x1).execute(&mut x); assert_eq!(x.memory.peek(0x500), 0x1); assert_eq!(x.memory.peek(0x501), 0x2); assert_eq!(x.memory.peek(0x502), 0x3); // Store registers V0 through Vx in memory starting at location I. // // The interpreter copies the values of registers V0 through Vx into memory, // starting at the address in I. let mut x = Chip8Computer::new(); // Load Registers. let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; for (idx, val) in to_load.iter().enumerate() { x.registers.poke(idx as u8, *val); } x.registers.poke_i(0x500); Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x); // Verify the values are in memory from 0x500 to 0x507 for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.memory.peek(0x500 + idx as u16), *value); } // Read registers V0 through Vx from memory starting at location I. // // The interpreter reads values from memory starting at location I into registers V0 through Vx. let mut x = Chip8Computer::new(); let base_offset = 0x500; let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; // start by setting values in memory for (idx, memory) in to_load.iter().enumerate() { let target_address = base_offset + idx; let target_value = *memory; x.memory.poke(target_address as u16, target_value); } // where to load from x.registers.poke_i(0x500); // how much to load x.registers.poke(0x6, to_load.len() as u8); // then copying them values memory to registers Chip8CpuInstructions::LDRI(0x6).execute(&mut x); // now check that we have the right values in our registers for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.registers.peek(idx as u8), *value); } // ExA1 - SKNP Vx // Skip next instruction if key with the value of Vx is not pressed. // // Checks the keyboard, // and if the key corresponding to the value of Vx is currently in the up position, // PC is increased by 2. let mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SKNP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); x.keypad.release_key(0x5); Chip8CpuInstructions::SKNP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); // Ex9E - SKP Vx // Skip next instruction if key with the value of Vx is pressed. // // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2. let mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SKP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); x.keypad.release_key(0x5); Chip8CpuInstructions::SKP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); } fn draw_nibble_vx_vy_n_test_hd() { let mut x = Chip8Computer::new(); let x_register = 0x01; let x_offset = 0x03; let y_register = 0x02; let y_offset = 0x04; let char_offset = 0x100; x.registers.poke(x_register, x_offset); x.registers.poke(y_register, y_offset); x.video_memory.set_highres(); x.registers.poke_i(char_offset); Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x); println!("[[{}]]", x.video_memory.format_as_string()); assert_eq!(read_test_result(""), x.video_memory.format_as_string()); } #[test] fn draw_nibble_vx_vy_n_test_sd() { let mut x = Chip8Computer::new(); let x_register = 0x1; let y_register = 0x2; let x_offset = 1; let y_offset = 2; let char_offset = 0x0A; // now lets set the X and Y to 1,2 x.registers.poke(x_register, x_offset); x.registers.poke(y_register, y_offset); x.registers.poke_i(char_offset); // we are using 5 rows. Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x); // now check that video memory has the values at // 1,2->1,9 // 2,2->2,9 // 3,2->3,9 // 4,2->4,9 // 5,2->5,9 // let byte_to_check = CHIP8FONT_0[0]; for row_in_sprite in 0..5 { let row_data = CHIP8FONT_2[row_in_sprite]; for bit_in_byte in 0..8 { let data_offset = (x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16; let real_bit_in_byte = 7 - bit_in_byte; let shifted_one = 0x01 << real_bit_in_byte; let one_shift_set = (shifted_one & row_data) > 0; debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]"); debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}", bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data); } } } #[test] fn sub_test() { // 2nnn // Call a subroutine at 2nnn let mut x = Chip8Computer::new(); Chip8CpuInstructions::CALL(0x124).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x124); assert_eq!(x.stack.depth(), 1); Chip8CpuInstructions::CALL(0x564).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x564); assert_eq!(x.stack.depth(), 2); // SETUP // Return from a subroutine. let mut x = Chip8Computer::new(); x.stack.push(&0x132); x.stack.push(&0xabc); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0xabc); assert_eq!(x.stack.depth(), 1); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0x132); assert_eq!(x.stack.depth(), 0); } #[test] fn ldvxk_test() { // SETUP let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); Chip8CpuInstructions::LDRK(0x1).execute(&mut x); assert!(matches!(x.state, gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey)); } #[test] fn series8xy4_corex_tests() { /// 8xy4 /// Set Vx = Vx + Vy /// Set VF=1 if Carry /// // 1 + 1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x02); assert_eq!(x.registers.peek(0x0f), 0x00); // 255+1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x00); assert_eq!(x.registers.peek(0x0f), 0x01); // 128+192 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 128); x.registers.poke(0x02, 192); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 64); assert_eq!(x.registers.peek(0x0f), 1); // 8xy6 - SHR Vx {, Vy} // Set Vx = Vx SHR 1. // // If the least-significant bit of Vx is 1, then VF is set to 1, // otherwise 0. Then Vx is divided by 2. let mut x = Chip8Computer::new(); // 0b10101010 -> 0b01010101 x.registers.poke(0x01, 0b10101010); x.registers.poke(0x0f, 0x0); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010100); assert_eq!(x.registers.peek(0x0f), 1); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10101000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010000); assert_eq!(x.registers.peek(0x0f), 0x01); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10100000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01000000); assert_eq!(x.registers.peek(0x0f), 0x01); } #[test] fn random_produces_different_numbers() { let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x00); let first_number = Chip8CpuInstructions::RND(0x01, 0xff).execute(&mut x).registers.peek(0x01); let second_number = Chip8CpuInstructions::RND(0x01, 0xff).execute(&mut x).registers.peek(0x01); assert_ne!(first_number, second_number); } #[test] fn delay_timer_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 delay_timer_out_of_ticks_works() { let mut st = DelayTimer::new(); st.set_timer(0); st.tick(); st.tick(); st.tick(); assert_eq!(st.current(), 0); } #[test] fn keypad_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)); } #[test] fn keypad_string_format_test() { let mut k = Keypad::new(); assert_eq!(k.format_as_string(), read_test_result("gemma_keypad_string_result.asc")); } #[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")); } #[test] fn reset_clears_registers() { let mut x = Chip8Registers::default(); for register in 0..0x10 { x.poke(register, random::()); } x.reset(); for register in 0..0x10 { assert_eq!(x.peek(register), 0x00); } } #[test] fn sound_timer_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 sound_timer_out_of_ticks_works() { let mut st = SoundTimer::new(); st.set_timer(0); st.tick(); st.tick(); st.tick(); assert_eq!(st.current(), 0); } #[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] #[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 stack_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::() % 50; for i in 0..num_loops { let start_count = x.depth(); let num_pop = random::() % 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); } } #[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 mut 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 mut v = build_checkerboard(); assert_eq!(v.clone().format_as_string(), read_test_result("test_video_write_checkerboard.asc")); } #[test] 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!(read_test_result("test_video_zero.asc"), 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!(read_test_result("test_multi_sprite.asc"), x.format_as_string()); } #[test] fn video_reset_test() { let mut x = build_checkerboard(); x.reset(); assert_eq!(x.format_as_string(), read_test_result("test_reset_clears_video.asc")); } #[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_eq!(false, x.peek(0x00)); assert_eq!(true, x.clone().has_frame_changed); } #[test] fn video_collision_test2() { let mut x = Chip8Video::default(); x.poke_byte(0x00, 0b11110000); assert_eq!(true, x.has_frame_changed); x.tick(); assert_eq!(false, x.has_frame_changed); // clear the 'has changed' flag // now set a no-collision value x.poke_byte(0x00, 0b00001111); assert_eq!(true, x.has_frame_changed); } #[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!(read_test_result("test_video_scroll_down_1.asc"), x.format_as_string()); } #[test] fn video_scroll_down_10_row_test() { let mut x = build_checkerboard(); x.scroll_down(10); assert_eq!(read_test_result("test_video_scroll_down_10.asc"), x.format_as_string()); } #[test] fn video_high_res_has_right_resolution() { let mut x = build_checkboard_hd(); println!("[{}]", x.format_as_string()); assert_eq!(read_test_result("test_video_highdef.asc"), x.format_as_string()); } #[test] fn video_scroll_down_1_row_test_schip() { let mut x = build_checkboard_hd(); x.scroll_down(1); println!("[{}]", x.format_as_string()); println!("[{}]", read_test_result("test_scroll_down_1_hd.asc")); assert_eq!(read_test_result("test_scroll_down_1_hd.asc"), 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!(read_test_result("test_scroll_down_10_hd.asc"), x.format_as_string()); } #[test] fn video_scroll_left_4_row_test_std_def() { let mut x = build_checkerboard(); x.scroll_left(); assert_eq!(read_test_result("test_scroll_left_4.asc"), 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!(read_test_result("test_scroll_left_4_hd.asc"), x.format_as_string()); } #[test] fn video_scroll_right_4_row_test_std_def() { let mut x = build_checkerboard(); x.scroll_right(); assert_eq!(read_test_result("test_scroll_right_4.asc"), 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!(read_test_result("test_scroll_right_4_hd.asc"), 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(); assert!(!x.video_memory.is_highres()); Chip8CpuInstructions::ENA.execute(&mut x); assert!(x.video_memory.is_highres()); Chip8CpuInstructions::ENA.execute(&mut x); assert!(x.video_memory.is_highres()); Chip8CpuInstructions::DIS.execute(&mut x); assert!(!x.video_memory.is_highres()); } #[test] fn instruction_test_scrolling_lowres() { let mut x = Chip8Computer::new(); x.video_memory = build_checkerboard(); Chip8CpuInstructions::SRT.execute(&mut x); assert_eq!(read_test_result("test_scroll_right_4.asc"), x.dump_video_to_string()); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); Chip8CpuInstructions::SLF.execute(&mut x); assert_eq!(read_test_result("test_scroll_left_4.asc"), x.dump_video_to_string()); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); Chip8CpuInstructions::SDN(0x01).execute(&mut x); assert_eq!(read_test_result("test_video_scroll_down_1.asc"), x.dump_video_to_string()); x = Chip8Computer::new(); x.video_memory = build_checkerboard(); Chip8CpuInstructions::SDN(0xA).execute(&mut x); assert_eq!(read_test_result("test_video_scroll_down_10.asc"), 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!(read_test_result("test_keypad_to_string.asc"), 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()); }