Add sdl2 handlers and bunch of tests

First semi-working state, games can be loaded and they open
a sdl2 window \o/

TODOS:
+ Stacks offset is wrong somewhere
+ Input not working (maybe because of above reason?)
+ Add mising tests
This commit is contained in:
Marco Thomas
2021-03-12 16:13:25 +01:00
parent f61b3ed74d
commit 41d9e5dad0
6 changed files with 432 additions and 35 deletions

29
src/cartridge.rs Normal file
View File

@@ -0,0 +1,29 @@
use std::fs::File;
use std::io::prelude::*;
const BUFFER_SIZE: usize = 3584;
pub struct Cartridge {
pub rom: [u8; BUFFER_SIZE],
pub size: usize,
}
impl Cartridge {
pub fn new(filename: &str) -> Self {
let mut file = File::open(filename).expect("Error while opening file!");
let mut buffer = [0u8; BUFFER_SIZE];
// either read a byte, or noting (0)
let bytes = if let Ok(bytes) = file.read(&mut buffer) {
bytes
}
else {
0
};
Cartridge {
rom: buffer,
size: bytes,
}
}
}

52
src/display.rs Normal file
View File

@@ -0,0 +1,52 @@
use sdl2;
use sdl2::{render::Canvas, video::Window, pixels, rect::Rect};
pub struct Display {
canvas: Canvas<Window>,
}
impl Display {
pub fn new(sdl_ctx: &sdl2::Sdl) -> Self {
let video = sdl_ctx.video().unwrap();
let window = video
.window(
"FooBar",
(crate::SCREEN_WIDTH * crate::SCREEN_SCALE) as u32,
(crate::SCREEN_HEIGHT * crate::SCREEN_SCALE) as u32)
.position_centered()
.opengl()
.build()
.unwrap();
let mut canvas = window
.into_canvas()
.build()
.unwrap();
canvas.set_draw_color(pixels::Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Display {
canvas,
}
}
pub fn draw(&mut self, pixel: &[[u8; crate::SCREEN_WIDTH]; crate::SCREEN_HEIGHT]) {
for (y, &row) in pixel.iter().enumerate() {
for (x, &column) in row.iter().enumerate() {
self.canvas.set_draw_color(
if column == 1 {
pixels::Color::RGB(255, 255, 255)
}
else {
pixels::Color::RGB(0, 0, 0)
});
let _ = self.canvas
.fill_rect(
Rect::new((x * crate::SCREEN_SCALE) as i32, (y * crate::SCREEN_SCALE) as i32, crate::SCREEN_SCALE as u32, crate::SCREEN_SCALE as u32));
}
}
self.canvas.present();
}
}

View File

61
src/input.rs Normal file
View File

@@ -0,0 +1,61 @@
use sdl2;
use sdl2::{event::Event, keyboard::Keycode};
pub struct Input {
events: sdl2::EventPump,
}
impl Input {
pub fn new(sdl_ctx: &sdl2::Sdl) -> Self {
Input {
events: sdl_ctx.event_pump().unwrap(),
}
}
pub fn fetch(&mut self) -> Result<[bool; 16], ()> {
// error if we don't get input
for event in self.events.poll_iter() {
if let Event::Quit { .. } = event {
return Err(());
}
}
// get all inputs
let input: Vec<Keycode> = self.events
.keyboard_state()
.pressed_scancodes()
.filter_map(Keycode::from_scancode)
.collect();
// set all no not-pressed as default
let mut keys = [false; 16];
// map to internal values
for key in input {
let index = match key {
Keycode::Num1 => Some(0x1),
Keycode::Num2 => Some(0x2),
Keycode::Num3 => Some(0x3),
Keycode::Num4 => Some(0xc),
Keycode::Q => Some(0x4),
Keycode::W => Some(0x5),
Keycode::E => Some(0x6),
Keycode::R => Some(0xd),
Keycode::A => Some(0x7),
Keycode::S => Some(0x8),
Keycode::D => Some(0x9),
Keycode::F => Some(0xe),
Keycode::Z => Some(0xa),
Keycode::X => Some(0x0),
Keycode::C => Some(0xb),
Keycode::V => Some(0xf),
_ => None,
};
if let Some(pos) = index {
keys[pos] = true;
}
}
Ok(keys)
}
}

View File

@@ -5,12 +5,22 @@ use std::env;
mod processor;
mod fontset;
mod display;
mod input;
mod cartridge;
use crate::processor::Processor;
use crate::cartridge::Cartridge;
const MEMORY_SIZE: usize = 4096;
const GAME_ENTRY: usize = 0x200; // most games load into 0x200
const SCREEN_WIDTH: usize = 64;
const SCREEN_HEIGHT: usize = 32;
const SCREEN_SCALE: usize = 20;
fn main() {
let cartridge = env::args().nth(1);
match cartridge {
let game_file = env::args().nth(1);
match game_file {
Some(_) => println!("Found a cartridge file! Trying to load..."),
None => {
println!("No cartridge file found! Exiting!");
@@ -21,7 +31,8 @@ fn main() {
let mut processor = Processor::new();
// load cartridge file
let cartridge = Cartridge::new(&game_file.unwrap());
processor.start();
processor.start(&cartridge.rom);
}

View File

@@ -1,21 +1,21 @@
// TODO add all comments
extern crate sdl2;
use rand::Rng;
use crate::fontset::FONT;
use crate::display::Display;
use crate::input::Input;
const MEMORY_SIZE: usize = 4096;
const GAME_ENTRY: usize = 0x200; // most games load into 0x200
const SCREEN_WIDTH: usize = 64;
const SCREEN_HEIGHT: usize = 32;
const OPCODE_SIZE: usize = 2;
pub struct Processor {
memory: [u8; MEMORY_SIZE],
memory: [u8; crate::MEMORY_SIZE],
register: [u8; 16],
index: usize, // used to store memory addresses
pc: usize, // programm counter
screen: [[u8; SCREEN_WIDTH]; SCREEN_HEIGHT],
screen: [[u8; crate::SCREEN_WIDTH]; crate::SCREEN_HEIGHT],
draw_flag: bool, // redraw screen if true
delay_timer: usize,
sound_timer: usize,
@@ -28,7 +28,7 @@ pub struct Processor {
impl Processor {
pub fn new() -> Self {
let mut mem = [0; MEMORY_SIZE];
let mut mem = [0; crate::MEMORY_SIZE];
// load font
for (pos, &val) in FONT.iter().enumerate() {
mem[pos] = val;
@@ -38,8 +38,8 @@ impl Processor {
memory: mem,
register: [0; 16],
index: 0,
pc: GAME_ENTRY,
screen: [[0; SCREEN_WIDTH]; SCREEN_HEIGHT],
pc: crate::GAME_ENTRY,
screen: [[0; crate::SCREEN_WIDTH]; crate::SCREEN_HEIGHT],
draw_flag: false,
delay_timer: 0,
sound_timer: 0,
@@ -51,18 +51,23 @@ impl Processor {
}
}
// TODO: cartridge needed
pub fn start(&mut self) {
pub fn start(&mut self, game: &[u8]) {
let sdl_ctx = sdl2::init().unwrap();
let mut display = Display::new(&sdl_ctx);
let mut input = Input::new(&sdl_ctx);
self.load_game(&game);
loop {
// get keyinput using sdl2
self.key = input.fetch().unwrap();
// emulate one cycle
self.cycle();
// draw to screen using sdl2
if self.draw_flag {
display.draw(&self.screen);
}
// play sound using sdl2
@@ -72,7 +77,6 @@ impl Processor {
}
pub fn cycle(&mut self) {
// reset
self.draw_flag = false;
@@ -102,10 +106,9 @@ impl Processor {
}
pub fn load_game(&mut self, game: &[u8]) {
// load game
for (pos, &val) in game.iter().enumerate() {
let position = GAME_ENTRY + pos;
if position < MEMORY_SIZE { // don't go above mem limit
let position = crate::GAME_ENTRY + pos;
if position < crate::MEMORY_SIZE { // don't go above mem limit
self.memory[position] = val;
}
else {
@@ -122,11 +125,10 @@ impl Processor {
pub fn decode_opcode(&mut self, opcode: u16) {
// values from http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#3.0
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
let nibbles = (
(opcode & 0xF000 >> 12) as usize,
(opcode & 0x0F00 >> 8) as usize,
(opcode & 0x00F0 >> 4) as usize,
((opcode & 0xF000) >> 12) as usize,
((opcode & 0x0F00) >> 8) as usize,
((opcode & 0x00F0) >> 4) as usize,
(opcode & 0x000F) as usize
);
@@ -187,7 +189,8 @@ impl Processor {
// Clear screen
fn code_00e0(&mut self) {
self.screen = [[0; SCREEN_WIDTH]; SCREEN_HEIGHT];
self.screen = [[0; crate::SCREEN_WIDTH]; crate::SCREEN_HEIGHT];
self.draw_flag = true;
self.pc += OPCODE_SIZE;
}
@@ -204,8 +207,8 @@ impl Processor {
// Call subroutine at nnn
fn code_2nnn(&mut self, nnn: usize) {
self.sp += 1;
self.stack[self.sp] = self.pc;
self.sp += 1;
self.pc = nnn;
}
@@ -247,7 +250,8 @@ impl Processor {
// Set Vx = Vx + kk
fn code_7xkk(&mut self, x: usize, kk: u8) {
self.register[x] = self.register[x] + kk;
let val = self.register[x] as u16;
self.register[x] = (val + kk as u16) as u8;
self.pc += OPCODE_SIZE;
}
@@ -290,7 +294,7 @@ impl Processor {
let y_val = self.register[y];
self.register[0x0f] = (x_val > y_val) as u8;
self.register[x] = (x_val - y_val) as u8;
self.register[x] = x_val.wrapping_sub(y_val) as u8;
self.pc += OPCODE_SIZE;
}
@@ -304,14 +308,14 @@ impl Processor {
// Set Vx = Vy - Vx, set VF = NOT borrow
fn code_8xy7(&mut self, x: usize, y: usize) {
self.register[0x0f] = (self.register[y] > self.register[x]) as u8;
self.register[x] = self.register[y] - self.register[x];
self.register[x] = self.register[y].wrapping_sub(self.register[x]);
self.pc += OPCODE_SIZE;
}
// Set Vx = Vx SHL 1
fn code_8xye(&mut self, x: usize, _y: usize) {
self.register[0x0f] = (self.register[x] & 0b10000000) >> 7;
self.register[x] = self.register[x] * 2;
self.register[x] <<= 1;
self.pc += OPCODE_SIZE;
}
@@ -345,19 +349,18 @@ impl Processor {
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
fn code_dxyn(&mut self, x: usize, y: usize, n: usize) {
// TODO
self.register[0x0f] = 0;
for byte in 0..n {
// get y coord, which we want to draw -> use modulo, so we don't overlap
let y = (self.register[y] as usize + byte) % SCREEN_HEIGHT;
let y = (self.register[y] as usize + byte) % crate::SCREEN_HEIGHT;
for bit in 0..8 {
// get x coord, just as above
let x = (self.register[x] as usize + bit) % SCREEN_WIDTH;
let x = (self.register[x] as usize + bit) % crate::SCREEN_WIDTH;
// bit hack to get every bit in a row
let pixel_to_draw = (self.memory[self.index + byte] >> (7 - bit)) & 1;
// check if we will overwrite an existing pixel
self.register[0x0f] = self.register[0x0f] | (pixel_to_draw & self.screen[x][y]);
self.screen[x][y] = self.screen[x][y] ^ pixel_to_draw;
self.register[0x0f] = self.register[0x0f] | (pixel_to_draw & self.screen[y][x]);
self.screen[y][x] = self.screen[y][x] ^ pixel_to_draw;
}
}
self.draw_flag = true;
@@ -435,7 +438,7 @@ impl Processor {
// Store registers V0 through Vx in memory starting at location I
fn code_fx55(&mut self, x: usize) {
// TODO offset correct?
for reg_i in 0..x {
for reg_i in 0..x + 1 {
self.memory[self.index + reg_i] = self.register[reg_i];
}
self.pc += OPCODE_SIZE;
@@ -443,9 +446,250 @@ impl Processor {
// Read registers V0 through Vx from memory starting at location I
fn code_fx65(&mut self, x: usize) {
for reg_i in 0..x {
for reg_i in 0..x + 1 {
self.register[reg_i] = self.memory[self.index + reg_i];
}
self.pc += OPCODE_SIZE;
}
}
#[cfg(test)]
mod tests{
use super::*;
fn new_processor() -> Processor {
let mut processor = Processor::new();
processor.register = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8];
processor
}
const ENTRY: usize = crate::GAME_ENTRY;
const SKIP: usize = ENTRY + OPCODE_SIZE * 2;
const NEXT: usize = ENTRY + OPCODE_SIZE;
#[test]
fn test_initial_state() {
let processor = Processor::new();
assert_eq!(processor.pc, 0x200);
assert_eq!(processor.sp, 0);
assert_eq!(processor.stack, [0; 16]);
// Font loading
assert_eq!(processor.memory[0..5], [0xF0, 0x90, 0x90, 0x90, 0xF0]);
}
#[test]
fn test_load_game() {
let mut processor = Processor::new();
processor.load_game(&[1, 2, 3]);
assert_eq!(processor.memory[crate::GAME_ENTRY], 1);
assert_eq!(processor.memory[crate::GAME_ENTRY + 1], 2);
assert_eq!(processor.memory[crate::GAME_ENTRY + 2], 3);
}
#[test]
fn test_code_00e0() {
let mut processor = Processor::new();
processor.screen = [[1; crate::SCREEN_WIDTH]; crate::SCREEN_HEIGHT];
processor.decode_opcode(0x00e0);
for y in 0..crate::SCREEN_HEIGHT {
for x in 0..crate::SCREEN_WIDTH {
assert_eq!(processor.screen[y][x], 0);
}
}
}
#[test]
fn test_code_00ee() {
let mut processor = Processor::new();
processor.sp = 3;
processor.stack[2] = 0x1337;
processor.decode_opcode(0x00ee);
assert_eq!(processor.sp, 2);
assert_eq!(processor.pc, 0x1337);
}
#[test]
fn test_code_1nnn() {
let mut processor = Processor::new();
processor.decode_opcode(0x1222);
assert_eq!(processor.pc, 0x0222);
}
#[test]
fn test_code_2nnn() {
let mut processor = new_processor();
processor.decode_opcode(0x2333);
assert_eq!(processor.sp, 1);
assert_eq!(processor.pc, 0x0333);
assert_eq!(processor.stack[0], NEXT);
}
#[test]
fn test_code_3xkk() {
let mut processor = new_processor();
processor.decode_opcode(0x3202);
assert_eq!(processor.pc, SKIP);
let mut processor = new_processor();
processor.decode_opcode(0x3206);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_4xkk() {
let mut processor = new_processor();
processor.decode_opcode(0x3206);
assert_eq!(processor.pc, NEXT);
let mut processor = new_processor();
processor.decode_opcode(0x3202);
assert_eq!(processor.pc, SKIP);
}
#[test]
fn test_code_5xy0() {
let mut processor = new_processor();
processor.decode_opcode(0x5010);
assert_eq!(processor.pc, SKIP);
let mut processor = new_processor();
processor.decode_opcode(0x5070);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_6xkk() {
let mut processor = new_processor();
processor.decode_opcode(0x6133);
assert_eq!(processor.register[1], 0x33);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_7xkk() {
let mut processor = new_processor();
processor.decode_opcode(0x7001);
assert_eq!(processor.register[0], 0x02);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy0() {
let mut processor = new_processor();
processor.decode_opcode(0x8f00);
assert_eq!(processor.register[0], 1);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy1() {
let mut processor = new_processor();
processor.decode_opcode(0x8011);
assert_eq!(processor.register[0], 1);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy2() {
let mut processor = new_processor();
processor.decode_opcode(0x8142);
assert_eq!(processor.register[1], 1);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy3() {
let mut processor = new_processor();
processor.decode_opcode(0x8143);
assert_eq!(processor.register[2], 2);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy4() {
// no carry
let mut processor = new_processor();
processor.decode_opcode(0x8124);
assert_eq!(processor.register[1], 3);
assert_eq!(processor.pc, NEXT);
// carry
let mut processor = new_processor();
processor.register[2] = 254;
processor.decode_opcode(0x8324);
assert_eq!(processor.register[1], 1);
assert_eq!(processor.register[0x0f], 1);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy5() {
// set carry
let mut processor = new_processor();
processor.decode_opcode(0x8205);
assert_eq!(processor.register[2], 1);
assert_eq!(processor.register[0x0f], 1);
assert_eq!(processor.pc, NEXT);
// don't set carry
let mut processor = new_processor();
processor.decode_opcode(0x8065);
assert_eq!(processor.register[0], 253);
assert_eq!(processor.register[0x0f], 0);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy6() {
// set VF
let mut processor = new_processor();
processor.decode_opcode(0x8416);
assert_eq!(processor.register[0x0f], 1);
assert_eq!(processor.register[4], 1);
assert_eq!(processor.pc, NEXT);
// don't set VF
let mut processor = new_processor();
processor.decode_opcode(0x8216);
assert_eq!(processor.register[0x0f], 0);
assert_eq!(processor.register[2], 1);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xy7() {
// set VF
let mut processor = new_processor();
processor.decode_opcode(0x8937);
assert_eq!(processor.register[0x0f], 0);
assert_eq!(processor.register[9], 253);
assert_eq!(processor.pc, NEXT);
// don't set VF
let mut processor = new_processor();
processor.decode_opcode(0x8397);
assert_eq!(processor.register[0x0f], 1);
assert_eq!(processor.register[3], 3);
assert_eq!(processor.pc, NEXT);
}
#[test]
fn test_code_8xye() {
// set VF
let mut processor = new_processor();
processor.register[0] = 0b10000000;
processor.decode_opcode(0x801e);
assert_eq!(processor.register[0x0f], 1);
assert_eq!(processor.register[0], 0);
assert_eq!(processor.pc, NEXT);
// don't set VF
let mut processor = new_processor();
processor.decode_opcode(0x801e);
assert_eq!(processor.register[0x0f], 0);
assert_eq!(processor.register[0], 2);
assert_eq!(processor.pc, NEXT);
}
}