diff --git a/Cargo.toml b/Cargo.toml index f89063d..763b31f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" [dependencies] bitflags = "2.6.0" lazy_static = "1.5.0" +rand = "0.8.5" +sdl2 = "0.37.0" diff --git a/src/cpu.rs b/src/cpu.rs index 0fcddbb..9a883f4 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -55,7 +55,7 @@ impl Default for CPU { } } -trait Mem { +pub trait Mem { fn mem_read(&self, addr: u16) -> u8; fn mem_write(&mut self, addr: u16, data: u8); @@ -128,10 +128,24 @@ impl CPU { self.mem_write_u16(0xFFFC, 0x8000); } + // TEMPORARY + pub fn load_sneak(&mut self, program: Vec) { + self.memory[0x0600..(0x0600 + program.len())].copy_from_slice(&program[..]); + self.mem_write_u16(0xFFFC, 0x0600); + } + pub fn run(&mut self) { + self.run_with_callback(|_| {}); + } + + pub fn run_with_callback(&mut self, mut callback: F) + where + F: FnMut(&mut CPU), + { let opcodes: &HashMap = &opcodes::OPCODES_MAP; loop { + callback(self); let code = self.mem_read(self.program_counter); self.program_counter += 1; let program_counter_state = self.program_counter; @@ -427,6 +441,8 @@ impl CPU { if program_counter_state == self.program_counter { self.program_counter += (opcode.len - 1) as u16; } + + callback(self); } } diff --git a/src/main.rs b/src/main.rs index 5c37f2d..d327b09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,151 @@ extern crate lazy_static; #[macro_use] extern crate bitflags; +use cpu::Mem; +use cpu::CPU; + +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::pixels::PixelFormatEnum; +use sdl2::EventPump; + +use rand::Rng; + fn main() { - println!("Hello, world!"); + let game_code = vec![ + 0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02, + 0x85, 0x02, 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9, + 0x0f, 0x85, 0x14, 0xa9, 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85, + 0x00, 0xa5, 0xfe, 0x29, 0x03, 0x18, 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20, + 0x8d, 0x06, 0x20, 0xc3, 0x06, 0x20, 0x19, 0x07, 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c, + 0x38, 0x06, 0xa5, 0xff, 0xc9, 0x77, 0xf0, 0x0d, 0xc9, 0x64, 0xf0, 0x14, 0xc9, 0x73, 0xf0, + 0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26, 0xa9, 0x01, 0x85, + 0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9, 0x01, + 0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05, + 0xa9, 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00, + 0xc5, 0x10, 0xd0, 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, 0xe6, 0x03, 0xe6, 0x03, 0x20, + 0x2a, 0x06, 0x60, 0xa2, 0x02, 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, 0xb5, 0x11, 0xc5, 0x11, + 0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0, 0x06, 0x4c, 0xaa, 0x06, 0x4c, 0x35, 0x07, 0x60, + 0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca, 0x10, 0xf9, 0xa5, 0x02, 0x4a, 0xb0, + 0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5, 0x10, 0x38, 0xe9, 0x20, + 0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28, 0x60, 0xe6, + 0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10, + 0xb0, 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5, + 0x10, 0x29, 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe, + 0x91, 0x00, 0x60, 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0xa2, 0x00, 0xa9, 0x01, 0x81, 0x10, + 0x60, 0xa2, 0x00, 0xea, 0xea, 0xca, 0xd0, 0xfb, 0x60, + ]; + + // init sdl2 + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem + .window("Snake game", (32.0 * 10.0) as u32, (32.0 * 10.0) as u32) + .position_centered() + .build() + .unwrap(); + + let mut canvas = window.into_canvas().present_vsync().build().unwrap(); + let mut event_pump = sdl_context.event_pump().unwrap(); + canvas.set_scale(10.0, 10.0).unwrap(); + + let creator = canvas.texture_creator(); + let mut texture = creator + .create_texture_target(PixelFormatEnum::RGB24, 32, 32) + .unwrap(); + + // init cpu + let mut cpu = cpu::CPU::new(); + cpu.load_sneak(game_code); + cpu.reset(); + + let mut screen_state = [0_u8; 32 * 3 * 32]; + let mut rng = rand::thread_rng(); + + cpu.run_with_callback(move |cpu| { + // TODO: + // read user input and write it to mem[0xFF] + // update mem[0xFE] with new Random Number + // read mem mapped screen state + // render screen state + handle_user_input(cpu, &mut event_pump); + cpu.mem_write(0xfe, rng.gen_range(1..16)); + + if read_screen_state(cpu, &mut screen_state) { + texture.update(None, &screen_state, 32 * 3).unwrap(); + canvas.copy(&texture, None, None).unwrap(); + canvas.present(); + } + + ::std::thread::sleep(std::time::Duration::new(0, 70_000)); + }); +} + +fn handle_user_input(cpu: &mut CPU, event_pump: &mut EventPump) { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => std::process::exit(0), + Event::KeyDown { + keycode: Some(Keycode::W), + .. + } => { + cpu.mem_write(0xff, 0x77); + } + Event::KeyDown { + keycode: Some(Keycode::S), + .. + } => { + cpu.mem_write(0xff, 0x73); + } + Event::KeyDown { + keycode: Some(Keycode::A), + .. + } => { + cpu.mem_write(0xff, 0x61); + } + Event::KeyDown { + keycode: Some(Keycode::D), + .. + } => { + cpu.mem_write(0xff, 0x64); + } + _ => { /* do nothing */ } + } + } +} + +fn color(byte: u8) -> Color { + match byte { + 0 => sdl2::pixels::Color::BLACK, + 1 => sdl2::pixels::Color::WHITE, + 2 | 9 => sdl2::pixels::Color::GREY, + 3 | 10 => sdl2::pixels::Color::RED, + 4 | 11 => sdl2::pixels::Color::GREEN, + 5 | 12 => sdl2::pixels::Color::BLUE, + 6 | 13 => sdl2::pixels::Color::MAGENTA, + 7 | 14 => sdl2::pixels::Color::YELLOW, + _ => sdl2::pixels::Color::CYAN, + } +} + +fn read_screen_state(cpu: &CPU, frame: &mut [u8; 32 * 3 * 32]) -> bool { + let mut frame_idx = 0; + let mut update = false; + for i in 0x0200..0x600 { + let color_idx = cpu.mem_read(i as u16); + let (b1, b2, b3) = color(color_idx).rgb(); + if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 { + frame[frame_idx] = b1; + frame[frame_idx + 1] = b2; + frame[frame_idx + 2] = b3; + update = true; + } + frame_idx += 3; + } + update }