From 1694157e27a86321ccc9e06445cdc51f4199819f Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Sun, 27 Oct 2024 12:08:22 -0400 Subject: [PATCH] move tests to unit_tests to clean up individual files adds start of font characters --- gemma/src/chip8/delay_timer.rs | 31 - gemma/src/chip8/instructions.rs | 792 +--------- gemma/src/chip8/keypad.rs | 25 - gemma/src/chip8/registers.rs | 63 +- gemma/src/chip8/sound_timer.rs | 30 - gemma/src/chip8/stack.rs | 68 - gemma/src/chip8/system_memory.rs | 44 +- gemma/src/chip8/util.rs | 56 - gemma/src/chip8/video.rs | 391 +---- gemma/src/constants.rs | 114 +- gemma/tests/computer_tests.rs | 10 +- gemma/tests/unit_tests.rs | 1320 +++++++++++++++++ gemmaimgui/src/bin/gemmaimgui.rs | 8 +- gemmaimgui/src/bin/support/emmagui_support.rs | 30 +- gemmautil/src/bin/bin2hex.rs | 83 ++ resources/schip_font/0.asc | 16 + resources/schip_font/1.asc | 16 + resources/schip_font/2.asc | 16 + resources/schip_font/3.asc | 16 + resources/schip_font/4.asc | 16 + resources/schip_font/5.asc | 16 + resources/schip_font/6.asc | 16 + resources/schip_font/7.asc | 16 + resources/schip_font/8.asc | 16 + resources/schip_font/9.asc | 16 + resources/schip_font/A.asc | 16 + resources/schip_font/B.asc | 16 + resources/schip_font/C.asc | 16 + resources/schip_font/D.asc | 16 + resources/schip_font/E.asc | 16 + resources/schip_font/F.asc | 16 + 31 files changed, 1797 insertions(+), 1524 deletions(-) create mode 100644 gemma/tests/unit_tests.rs create mode 100644 gemmautil/src/bin/bin2hex.rs create mode 100644 resources/schip_font/0.asc create mode 100644 resources/schip_font/1.asc create mode 100644 resources/schip_font/2.asc create mode 100644 resources/schip_font/3.asc create mode 100644 resources/schip_font/4.asc create mode 100644 resources/schip_font/5.asc create mode 100644 resources/schip_font/6.asc create mode 100644 resources/schip_font/7.asc create mode 100644 resources/schip_font/8.asc create mode 100644 resources/schip_font/9.asc create mode 100644 resources/schip_font/A.asc create mode 100644 resources/schip_font/B.asc create mode 100644 resources/schip_font/C.asc create mode 100644 resources/schip_font/D.asc create mode 100644 resources/schip_font/E.asc create mode 100644 resources/schip_font/F.asc diff --git a/gemma/src/chip8/delay_timer.rs b/gemma/src/chip8/delay_timer.rs index eb5a7f6..f289675 100644 --- a/gemma/src/chip8/delay_timer.rs +++ b/gemma/src/chip8/delay_timer.rs @@ -28,34 +28,3 @@ impl DelayTimer { } } } - -#[cfg(test)] -mod test { - use crate::chip8::sound_timer::SoundTimer; - use super::*; - - #[test] - fn smoke() { - assert!(true) - } - - #[test] - fn 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 out_of_ticks_works() { - let mut st = DelayTimer::new(); - st.set_timer(0); - st.tick(); - st.tick(); - st.tick(); - assert_eq!(st.current(), 0); - } -} \ No newline at end of file diff --git a/gemma/src/chip8/instructions.rs b/gemma/src/chip8/instructions.rs index 67e8d3b..171b657 100644 --- a/gemma/src/chip8/instructions.rs +++ b/gemma/src/chip8/instructions.rs @@ -735,12 +735,29 @@ impl Chip8CpuInstructions { let source_memory_offset = input.registers.peek_i(); let x_offset = input.registers.peek(*x) as u16; let y_offset = input.registers.peek(*y) as u16; - - for byte_index in 0..*n { - let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset); - let x_offset: u16 = (x_offset + byte_index as u16) * 64; - for bit_index in 0..8 { - input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0); + if input.video_memory.is_highres() { + // if n == 0 we have a 16 row sprite (font maybe) + let actual_num_loops = if *n == 0u8 { + 16 + } else { + *n + }; + for byte_index in 0..actual_num_loops { + let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset); + let next_byte = input.memory.peek(byte_index as u16 + 1u16 + source_memory_offset); + let x_offset = (x_offset + byte_index as u16) * 64; + for bit_index in 0..8 { + input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0); + input.video_memory.poke(x_offset + (y_offset + bit_index as u16) + 8, (current_byte & (0x80 >> bit_index)) != 0); + } + } + } else { + for byte_index in 0..*n { + let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset); + let x_offset: u16 = (x_offset + byte_index as u16) * 64; + for bit_index in 0..8 { + input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0); + } } } @@ -921,766 +938,3 @@ impl Chip8CpuInstructions { Chip8CpuInstructions::XXXXERRORINSTRUCTION } } - -#[cfg(test)] -mod test { - use crate::chip8::system_memory::{*}; - use crate::constants::CHIP8_VIDEO_MEMORY; - use super::*; - - #[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::LDI_S(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::LDI_S(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).encode(), 0xffff); - assert!(matches!(Chip8CpuInstructions::decode(i), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); - } - } - - /// START OF THE EXECUTION TESTS - #[test] - fn sys_test() { - // 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); - } - - #[test] - fn jpaddr_test() { - // 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); - } - - // ** test moved up so it can be used later - #[test] - fn ld_vx_byte_test() { - // 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); - } - - #[test] - fn sevxbyte_match_test() { - // 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); - } - - #[test] - fn se_vx_vy_test() { - // 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); - } - - #[test] - fn ld_vx_vy_test() { - // 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); - } - - #[test] - fn or_vx_vy_test() { - // 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); - } - - #[test] - fn and_vx_vy_test() { - // 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); - } - - #[test] - fn xor_vx_vy_test() { - // 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); - } - - #[test] - fn add_vx_vy_test() { - // 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); - } - - #[test] - fn shr_vx_vy_test() { - /* - 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); - } - - #[test] - fn ldi_addr_test() { - 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::LDI_S(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); - } - - #[test] - fn shl_vx_vy_test_1() { - // 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); - } - - #[test] - fn ld_f_vx_test() { - // 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); - } - - #[test] - fn ld_b_vx_test() { - // 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); - } - - #[test] - fn ld_i_vx_test() { - // 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); - } - } - - #[test] - fn ld_vx_i_test() { - // 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); - } - } - - #[test] - fn Sknkpvx_test() { - // 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); - } - - #[test] - fn skpvx_test() { - // 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); - } - - #[test] - fn draw_nibble_vx_vy_n_test() { - 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); - } - - #[test] - fn ret_test() { - // 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, 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); - } - - #[test] - fn series8xy6_corex_tests() { - // 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); - } -} diff --git a/gemma/src/chip8/keypad.rs b/gemma/src/chip8/keypad.rs index 884f1e5..039b1f7 100644 --- a/gemma/src/chip8/keypad.rs +++ b/gemma/src/chip8/keypad.rs @@ -60,28 +60,3 @@ impl Keypad { !self.key_state(key) } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn smoke() { assert!(true) } - - #[test] - fn 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)); - } -} \ No newline at end of file diff --git a/gemma/src/chip8/registers.rs b/gemma/src/chip8/registers.rs index d350485..e964bb1 100644 --- a/gemma/src/chip8/registers.rs +++ b/gemma/src/chip8/registers.rs @@ -2,9 +2,9 @@ /// Privately using zero base array so -1 to shift from pub to priv. #[derive(Clone, Copy)] pub struct Chip8Registers { - registers: [u8; 16], - i_register: u16, - pc: u16, + pub registers: [u8; 16], + pub i_register: u16, + pub pc: u16, } impl Chip8Registers { @@ -82,60 +82,3 @@ impl Chip8Registers { ) } } - -#[cfg(test)] -mod test { - use rand::random; - use crate::chip8::registers::Chip8Registers; - - #[test] - fn smoke() { assert!(true) } - - #[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.registers[register] = random::(); - } - x.reset(); - for register in 0..0x10 { - assert_eq!(x.registers[register], 0x00); - } - } -} \ No newline at end of file diff --git a/gemma/src/chip8/sound_timer.rs b/gemma/src/chip8/sound_timer.rs index 711c70e..da46cf7 100644 --- a/gemma/src/chip8/sound_timer.rs +++ b/gemma/src/chip8/sound_timer.rs @@ -37,33 +37,3 @@ impl SoundTimer { self.counter = 0x00; } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn smoke() { - assert!(true) - } - - #[test] - fn 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 out_of_ticks_works() { - let mut st = SoundTimer::new(); - st.set_timer(0); - st.tick(); - st.tick(); - st.tick(); - assert_eq!(st.current(), 0); - } -} \ No newline at end of file diff --git a/gemma/src/chip8/stack.rs b/gemma/src/chip8/stack.rs index 70d2588..fcbb902 100644 --- a/gemma/src/chip8/stack.rs +++ b/gemma/src/chip8/stack.rs @@ -39,71 +39,3 @@ impl Chip8Stack { self.items = vec![] } } - -#[cfg(test)] -mod test { - use rand::random; - use super::*; - - #[test] - fn smoke() { assert!(true) } - - #[test] - fn 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 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); - } - - } -} \ No newline at end of file diff --git a/gemma/src/chip8/system_memory.rs b/gemma/src/chip8/system_memory.rs index 3c1f809..cbed98d 100644 --- a/gemma/src/chip8/system_memory.rs +++ b/gemma/src/chip8/system_memory.rs @@ -86,46 +86,4 @@ impl Chip8SystemMemory { } } } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn smoke() { - assert!(true) - } - - #[test] - fn model_smoke() { - let m = Chip8SystemMemory::default(); - for i in 0..5 { - assert_eq!(m.peek(i), CHIP8FONT_0[i as usize]); - } - - assert_eq!(m.peek((CHIP8_MEMORY_SIZE - 1) as u16), 0); - } - - #[test] - fn known_data_loaded_correctly() { - let to_load = [ 0x01, 0x02, 0x03, 0x04, 0x05 , 0x06 ]; - let mut x = Chip8SystemMemory::default(); - - for (index, value) in [1..10].iter().enumerate() { - assert_ne!(x.peek(0), 0x01); - x.poke(0, 0x01); - assert_eq!(x.peek(0), 0x01); - } - } - - #[test] - fn verify_load_program() { - // first line of 1-chip-logo.ch8 - let program_to_load = [0x00e0, 0x6101, 0x6008, 0xa250, 0xd01f, 0x6010, 0xa25f, 0xd01f]; - - let mut x = Chip8SystemMemory::new(); - - - } -} +} \ No newline at end of file diff --git a/gemma/src/chip8/util.rs b/gemma/src/chip8/util.rs index a3d48a4..592b59b 100644 --- a/gemma/src/chip8/util.rs +++ b/gemma/src/chip8/util.rs @@ -62,59 +62,3 @@ impl InstructionUtil { ((to_read_from & 0x0f00) >> 8) as u8 } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn smoke() { - assert!(true) - } - - #[test] - fn 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 join_bytes() { - // from 0xAB low and 0xCD high we get 0xABCD - let merged = InstructionUtil::join_bytes(0xcd, 0xab); - assert_eq!(merged, 0xcdab); - } - - #[test] - fn 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 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 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); - } -} diff --git a/gemma/src/chip8/video.rs b/gemma/src/chip8/video.rs index 5bff2eb..57e35c0 100644 --- a/gemma/src/chip8/video.rs +++ b/gemma/src/chip8/video.rs @@ -1,4 +1,5 @@ use log::{debug}; +use crate::chip8::util::InstructionUtil; 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}; @@ -204,393 +205,3 @@ impl Default for Chip8Video { Chip8Video { memory: mem, has_frame_changed: false, current_res: Chip8VideoModes::LowRes } } } - -#[cfg(test)] -mod test { - use std::io::Read; - use crate::constants::*; - use super::*; - - const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/"; - - 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) - } - - 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 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 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 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 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 poke_multirow_2_byte_sprite() { - // take 2 rows of 16bits and write them to memory - } - - #[test] - fn 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 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 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 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 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 write_checkboard() { - let mut v = build_checkerboard(); - assert_eq!(v.clone().format_as_string(), read_test_result("test_video_write_checkerboard.asc")); - } - - #[test] - fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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()); - } -} diff --git a/gemma/src/constants.rs b/gemma/src/constants.rs index ddb8c2d..dbeb3f3 100644 --- a/gemma/src/constants.rs +++ b/gemma/src/constants.rs @@ -5,6 +5,9 @@ pub const CHIP8_VIDEO_HEIGHT: i32 = 32i32; pub const CHIP8_VIDEO_MEMORY: usize = (CHIP8_VIDEO_HEIGHT * CHIP8_VIDEO_WIDTH) as usize; pub const CHIP8_ROM_SIZE: usize = 512; +pub const RESOURCES_ROOT: &str = "../resources"; +pub const TESTS_ROOT: &str = "../resources/tests/"; + pub const CHIP8_KEYBOARD: [[u8; 4]; 4] = [ [0x01, 0x02, 0x03, 0x0C], [0x04, 0x05, 0x06, 0x0D], @@ -53,7 +56,7 @@ pub const INST_SKP: &str = "SKP"; pub const INST_SNEB: &str = "SNEB"; pub const INST_SNEY: &str = "SNEY"; pub const INST_SNKP: &str = "SNKP"; -pub const INST_STR : &str = "STR"; +pub const INST_STR: &str = "STR"; pub const INST_SUB: &str = "SUB"; pub const INST_SUBC: &str = "SUBC"; pub const INST_SYS: &str = "SYS"; @@ -61,8 +64,6 @@ pub const INST_DIS: &str = "DIS"; pub const INST_ENA: &str = "ENA"; pub const INST_ORY: &str = "ORY"; - - pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200; pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0]; pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70]; @@ -81,47 +82,90 @@ pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0]; pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0]; pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80]; - -pub const SCHIPFONT_0: [u8; 0x10] = [0xF0, 0xFC, 0xFE, 0xFF, 0xF3, 0xE1, 0xE0, 0xE0, - 0xE0, 0xE0, 0xE0, 0xF1, 0xFB, 0xFF, 0xFE, 0x7C]; -pub const SCHIPFONT_1: [u8; 0x10] = [0x18, 0x3C, 0x7E, 0xFF, 0xF7, 0xE3, 0xC1, 0xC0, - 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 +pub const SCHIPFONT_0: [u8; 0x20] = [ + 0x00, 0x00, + 0x01, 0x80, + 0x03, 0xc0, + 0x06, 0x60, + 0x0c, 0x30, + 0x0c, 0x30, + 0x18, 0x18, + 0x18, 0x18, + 0x18, 0x18, + 0x18, 0x18, + 0x0c, 0x30, + 0x0c, 0x30, + 0x06, 0x60, + 0x03, 0xc0, // 0b0000001111000000 + 0x01, 0x80, // 0b0000000110000000 + 0x00, 0x00 // 0b0000000000000000 ]; -pub const SCHIPFONT_2: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, - 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF +pub const SCHIPFONT_1: [u8; 0x20] = [ + 0x00, 0x00, + 0x03, 0xc0, + 0x02, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x00, 0xc0, + 0x03, 0xf0, + 0x00, 0x00 ]; -pub const SCHIPFONT_3: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, -0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF]; -pub const SCHIPFONT_4: [u8; 0x10] = [0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xF7, 0xF3, 0xF1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0]; -pub const SCHIPFONT_5: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x7F, 0x7F, - 0x01, 0x01, 0xC1, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; -pub const SCHIPFONT_6: [u8; 0x10] = [0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, +pub const SCHIPFONT_2: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, + 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; +pub const SCHIPFONT_3: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, + 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; +pub const SCHIPFONT_4: [u8; 0x20] = [0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xF7, 0xF3, 0xF1, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; +pub const SCHIPFONT_5: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x7F, 0x7F, + 0x01, 0x01, 0xC1, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; +pub const SCHIPFONT_6: [u8; 0x20] = [0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_7: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xF0, 0x78, 0x3C, - 0x1E, 0x0F, 0x07, 0x03, 0x01, 0x01, 0x01, 0x01 +pub const SCHIPFONT_7: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xF0, 0x78, 0x3C, + 0x1E, 0x0F, 0x07, 0x03, 0x01, 0x01, 0x01, 0x01,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_8: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, - 0x7E, 0xFE, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C +pub const SCHIPFONT_8: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, + 0x7E, 0xFE, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_9: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, - 0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFF, 0xFE, 0x7C +pub const SCHIPFONT_9: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, + 0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_A: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, - 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3 +pub const SCHIPFONT_A: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, + 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_B: [u8; 0x10] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF, - 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE +pub const SCHIPFONT_B: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF, + 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_C: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0x01, 0x01, - 0x01, 0x01, 0xC3, 0xC3, 0xFF, 0xFE, 0xFC, 0x78 +pub const SCHIPFONT_C: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0x01, 0x01, + 0x01, 0x01, 0xC3, 0xC3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_D: [u8; 0x10] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, - 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C +pub const SCHIPFONT_D: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C, 0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_E: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, - 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF +pub const SCHIPFONT_E: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, + 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 ]; -pub const SCHIPFONT_F: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, - 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03]; +pub const SCHIPFONT_F: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, + 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, + 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; diff --git a/gemma/tests/computer_tests.rs b/gemma/tests/computer_tests.rs index ce2865c..a006797 100644 --- a/gemma/tests/computer_tests.rs +++ b/gemma/tests/computer_tests.rs @@ -48,11 +48,9 @@ fn level2_test() { let mut x = Chip8Computer::new(); // Load the IBM rom and run it. // it takes 39 cycles to get to the end so lets run it 40. - let test_rom_to_run = load_rom("2-ibm-logo.ch8"); x.load_bytes_to_memory(0x200, (&test_rom_to_run).into()); - - for i in 0..40 { + for _ in 0..40 { x.step_system(); } // ...then verify that the current video memory of the chip-8 @@ -79,12 +77,12 @@ fn level3_test() { fn rps_test() { let mut x = Chip8Computer::new(); x.load_bytes_to_memory(0x200, &load_rom("RPS.ch8").into()); - for i in 0..0xF0 { + for _ in 0..0xF0 { x.step_system(); } assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage1.asc")); x.keypad.push_key(0x01); - for i in 0..0x200 { + for _ in 0..0x200 { x.step_system(); } assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage2.asc")); @@ -95,7 +93,7 @@ fn level4_test() { // flags let mut x = Chip8Computer::new(); x.load_bytes_to_memory(0x200, &load_rom("4-flags.ch8").into()); - for i in 0..0x400 { + for _ in 0..0x400 { x.step_system(); } diff --git a/gemma/tests/unit_tests.rs b/gemma/tests/unit_tests.rs new file mode 100644 index 0000000..402c34d --- /dev/null +++ b/gemma/tests/unit_tests.rs @@ -0,0 +1,1320 @@ +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::keypad::Keypad; +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 = "/home/tmerritt/Projects/trevors_chip8_toy/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::LDI_S(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::LDI_S(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).encode(), 0xffff); + assert!(matches!(Chip8CpuInstructions::decode(i), 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::LDI_S(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); +} + +#[test] +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 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()); +} diff --git a/gemmaimgui/src/bin/gemmaimgui.rs b/gemmaimgui/src/bin/gemmaimgui.rs index fa918bf..5ead06c 100644 --- a/gemmaimgui/src/bin/gemmaimgui.rs +++ b/gemmaimgui/src/bin/gemmaimgui.rs @@ -33,6 +33,7 @@ fn main() { support::simple_init(file!(), move |_, ui| { let current_time = Instant::now(); + let mut num_cycles = 0; // Key Checks let down_keys = ui.io().keys_down; @@ -58,9 +59,10 @@ fn main() { } } - system.tick(); - - + while Instant::now().duration_since(current_time).as_millis() < 16 && num_cycles < 1000 { + system.tick(); + num_cycles += 1; + } // GUI Parts if ui_state.show_video { GemmaImguiSupport::video_display(&system.state(), &ui_state, ui); diff --git a/gemmaimgui/src/bin/support/emmagui_support.rs b/gemmaimgui/src/bin/support/emmagui_support.rs index 8cf2f9e..5c342bd 100644 --- a/gemmaimgui/src/bin/support/emmagui_support.rs +++ b/gemmaimgui/src/bin/support/emmagui_support.rs @@ -16,7 +16,7 @@ use crate::ImGuiUiState; use crate::support::gui_file_list::GuiFileList; use super::ui_state; -const ROM_ROOT: &str = "/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms"; +const ROM_ROOT: &str = "/home/tmerritt/Projects/trevors_chip8_toy/resources/roms"; pub struct GemmaImguiSupport {} @@ -43,23 +43,37 @@ impl GemmaImguiSupport { pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) { // draw area size + let (width, height) = system_to_control.video_memory.get_resolution(); let draw_area_size = ui.io().display_size; // println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]); - let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10; - let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10; + let cell_width = ((draw_area_size[0] as i32 / width) * 6) / 10; + let cell_height = ((draw_area_size[1] as i32 / height) * 6) / 10; ui.window(format!("Display {cell_width}x{cell_height}")) .size([300.0, 300.0], Condition::Once) .build(|| { - let (width, height) = system_to_control.video_memory.get_resolution(); let origin = ui.cursor_screen_pos(); let fg = ui.get_window_draw_list(); if (system_to_control.video_memory.is_highres()) { ui.text("High Def Video here"); - + for current_row in 0..=height { + let y_offset = origin[1] as i32 + (current_row * cell_height); + for current_column in 0..=width { + let x_offset = origin[0] as i32 + (current_column * cell_width); + let current_origin = [x_offset as f32, y_offset as f32]; + let current_limit = [(x_offset + cell_width) as f32, (y_offset + cell_height) as f32]; + let memory_offset = (current_row * width + current_column) as u16; + let to_render = system_to_control.video_memory.peek(memory_offset); + let color: ImColor32 = if to_render { + gui_state.on_colour + } else { + gui_state.off_colour + }; + fg.add_rect_filled_multicolor(current_origin, current_limit, color, color, color, color); + } + } } else { ui.text("StdDef video here."); - for current_row in 0..=height { let y_offset = origin[1] as i32 + (current_row * cell_height); for current_column in 0..=width { @@ -88,7 +102,7 @@ impl GemmaImguiSupport { ui.text(format!("Step {:04x}", system_to_control.num_cycles()).as_str()); /* ROM Lister */ - let new_filename = GuiFileList::display_path(PathBuf::from("/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms/"), &gui_state.filename_to_load, ui); + let new_filename = GuiFileList::display_path(PathBuf::from("/home/tmerritt/Projects/trevors_chip8_toy/resources/roms/"), &gui_state.filename_to_load, ui); if !new_filename.is_empty() { if new_filename != gui_state.filename_to_load { debug!("NEW FILENAME SELECTED -> {new_filename}"); @@ -98,7 +112,7 @@ impl GemmaImguiSupport { let mut buffer = Vec::new(); debug!("PREPARING TO LOAD {}", gui_state.filename_to_load); // let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory"); - let mut input_file = File::open(Path::new(&("/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory"); + let mut input_file = File::open(Path::new(&("/home/tmerritt/Projects/trevors_chip8_toy/resources/roms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory"); input_file.read_to_end(&mut buffer).expect("unable to read file"); system_to_control.load_bytes_to_system_memory((&*buffer).into()); } diff --git a/gemmautil/src/bin/bin2hex.rs b/gemmautil/src/bin/bin2hex.rs new file mode 100644 index 0000000..43678fe --- /dev/null +++ b/gemmautil/src/bin/bin2hex.rs @@ -0,0 +1,83 @@ +use std::fs::File; +use std::io; +use std::io::{BufReader, Read}; +use clap::{Arg, Command, ArgAction, ValueEnum}; + +#[derive(Debug)] +struct CliArgs { + input: String, +} + +fn main() { + println!("Taxation is Theft!"); + // Set up the command line arguments + let matches = Command::new("my_program") + .about("Processes an input file and outputs it with a specified bit width") + .arg( + Arg::new("input") + .help("The input file to process") + .required(true) + .index(1), + ) + + .get_matches(); + + // Parse the command-line arguments + let args = CliArgs { + input: matches.get_one::("input").unwrap().to_string(), + }; + + // Use the parsed arguments + println!("Input file: {}", args.input); + + // behave like a shift register and load each character from the file 1 by 1. + let results = read_file_to_bools(&args.input); + for result in results.unwrap().bytes() { + print!("0x{:02x}, ", result.unwrap()); + } +} +fn read_file_to_bools(file_path: &str) -> io::Result> { + // Open the file + let file = File::open(file_path)?; + let mut reader = BufReader::new(file); + + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes)?; + + let mut output = Vec::new(); + let mut current_byte = 0u8; + let mut bit_index = 0; + + for &byte in &bytes { + // Convert ASCII character '1' or '0' to boolean, skip any other characters + let bit = match byte { + b'1' => true, + b'0' => false, + _ => continue, // Skip non-'1' or '0' characters + }; + + // Set the appropriate bit in the current byte + if bit { + current_byte |= 1 << (7 - bit_index); // Set the bit at the correct position + } + + bit_index += 1; + + // Once we have filled 8 bits, push the byte and reset + if bit_index == 8 { + output.push(current_byte); + current_byte = 0; + bit_index = 0; + } + } + + // If there are remaining bits, push the last byte (it will be partially filled) + if bit_index > 0 { + output.push(current_byte); + } + + + + Ok(output) +} + diff --git a/resources/schip_font/0.asc b/resources/schip_font/0.asc new file mode 100644 index 0000000..8124bd3 --- /dev/null +++ b/resources/schip_font/0.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000110000000 +0000001111000000 +0000011001100000 +0000110000110000 +0000110000110000 +0001100000011000 +0001100000011000 +0001100000011000 +0001100000011000 +0000110000110000 +0000110000110000 +0000011001100000 +0000001111000000 +0000000110000000 +0000000000000000 diff --git a/resources/schip_font/1.asc b/resources/schip_font/1.asc new file mode 100644 index 0000000..ada11f7 --- /dev/null +++ b/resources/schip_font/1.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000001111000000 +0000001011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000000011000000 +0000001111110000 +0000000000000000 diff --git a/resources/schip_font/2.asc b/resources/schip_font/2.asc new file mode 100644 index 0000000..1911c3a --- /dev/null +++ b/resources/schip_font/2.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000011111000000 +0000100000100000 +0001000000010000 +0000000000100000 +0000000001000000 +0000000010000000 +0000000100000000 +0000001000000000 +0000010000000000 +0000100000000000 +0000100000000000 +0000100000000000 +0000100000000000 +0000111111110000 +0000000000000000 diff --git a/resources/schip_font/3.asc b/resources/schip_font/3.asc new file mode 100644 index 0000000..ed72ba8 --- /dev/null +++ b/resources/schip_font/3.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000011111000000 +0000100000100000 +0001000000010000 +0001000000010000 +0000000000010000 +0000000000010000 +0000011111100000 +0000000000010000 +0000000000010000 +0000000000010000 +0000100000010000 +0000100000100000 +0000011111000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/4.asc b/resources/schip_font/4.asc new file mode 100644 index 0000000..80974f0 --- /dev/null +++ b/resources/schip_font/4.asc @@ -0,0 +1,16 @@ +0000000000000000 +0001000000100000 +0001000000100000 +0001000000100000 +0001000000100000 +0001000000100000 +0001000000100000 +0001111111100000 +0000000000100000 +0000000000100000 +0000000000100000 +0000000000100000 +0000000000100000 +0000000000100000 +0000000000100000 +0000000000000000 diff --git a/resources/schip_font/5.asc b/resources/schip_font/5.asc new file mode 100644 index 0000000..f271cf7 --- /dev/null +++ b/resources/schip_font/5.asc @@ -0,0 +1,16 @@ +0000000000000000 +0011111111111100 +0010000000000000 +0010000000000000 +0010000000000000 +0010000000000000 +0010000000000000 +0010111111000000 +0011000000100000 +0000000000010000 +0000000000010000 +0000000000010000 +0001000000100000 +0001111111000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/6.asc b/resources/schip_font/6.asc new file mode 100644 index 0000000..bd98e4e --- /dev/null +++ b/resources/schip_font/6.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000011111111100 +0000100000000000 +0001000000000000 +0010000000000000 +0010000000000000 +0010000000000000 +0010111111000000 +0011000000100000 +0010000000010000 +0010000000010000 +0010000000010000 +0011000000100000 +0001111111000000 +0000000000000000 diff --git a/resources/schip_font/7.asc b/resources/schip_font/7.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/7.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/8.asc b/resources/schip_font/8.asc new file mode 100644 index 0000000..593af4c --- /dev/null +++ b/resources/schip_font/8.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000110000000 +0000001001000000 +0000010000100000 +0000001001000000 +0000000110000000 +0000011110000000 +0001100001100000 +0011000000110000 +0011000000110000 +0011000000110000 +0001100001100000 +0000110011000000 +0000011110000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/9.asc b/resources/schip_font/9.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/9.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/A.asc b/resources/schip_font/A.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/A.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/B.asc b/resources/schip_font/B.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/B.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/C.asc b/resources/schip_font/C.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/C.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/D.asc b/resources/schip_font/D.asc new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/resources/schip_font/D.asc @@ -0,0 +1,16 @@ +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 diff --git a/resources/schip_font/E.asc b/resources/schip_font/E.asc new file mode 100644 index 0000000..b616c7e --- /dev/null +++ b/resources/schip_font/E.asc @@ -0,0 +1,16 @@ +0000000000000000 +0111111111111000 +0100000000001000 +0100000000001000 +0100000000000000 +0100000000000000 +0111111111000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000001000 +0111111111111000 +0000000000000000 diff --git a/resources/schip_font/F.asc b/resources/schip_font/F.asc new file mode 100644 index 0000000..a12b031 --- /dev/null +++ b/resources/schip_font/F.asc @@ -0,0 +1,16 @@ +0000000000000000 +0111111111111000 +0100000000001000 +0100000000001000 +0100000000000000 +0100000000000000 +0111111111000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0100000000000000 +0000000000000000