diff options
Diffstat (limited to 'main.ha')
| -rw-r--r-- | main.ha | 569 |
1 files changed, 569 insertions, 0 deletions
@@ -0,0 +1,569 @@ +use fmt; +use fs; +use io; +use math::random; +use os; +use time; +use types; +use sdl2; + +def SCALE: int = 10; +def CLOCKSPEED: int = 540; + +def SCREEN_WIDTH: size = 64; +def SCREEN_HEIGHT: size = 32; + +def MEM_SIZE: size = 4096; +def START_ADDR: u16 = 0x200; + +def FONT_SIZE: size = 80; +def FONT: [FONT_SIZE]u8 = [ + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80 // F +]; + +type inst = struct { + op: u16, + X: u8, + Y: u8, + N: u8, + NN: u8, + NNN: u16 +}; + +type state = struct { + mem: [MEM_SIZE]u8, + display: [SCREEN_WIDTH * SCREEN_HEIGHT]bool, + draw: bool, + keys: [16]bool, + stack: [16]u16, + SP: u16, + PC: u16, + V: [16]u8, + I: u16, + DT: u8, + ST: u8, +}; + +fn chip8() state = { + let state = state { + PC = START_ADDR, + ... + }; + + state.mem[..FONT_SIZE] = FONT[..]; + + return state; +}; + +fn reset(state: *state) void = { + state.display = [false...]; + state.keys = [false...]; + state.stack = [0...]; + state.SP = 0; + state.PC = START_ADDR; + state.V = [0...]; + state.I = 0; + state.DT = 0; + state.ST = 0; + + state.mem[..FONT_SIZE] = FONT[..]; +}; + +fn instruction(opcode: u16) inst = inst { + op = opcode, + X = (opcode >> 8): u8 & 0x0F, + Y = (opcode >> 4): u8 & 0x0F, + N = opcode: u8 & 0x0F, + NN = opcode: u8 & 0xFF, + NNN = opcode & 0x0FFF, +}; + +fn print_instruction(state: *state, inst: inst) void = { + fmt::printf("Address: 0x{:.4X}, OP: 0x{:.4X}, Desc: ", state.PC - 2, inst.op)!; + + switch ((inst.op & 0xF000) >> 12) { + case 0x00 => + if (inst.NNN == 0x0E0) { + fmt::printfln("Clear screen")!; + } else if (inst.NNN == 0x0EE) { + fmt::printfln("Return from subroutine to 0x{:.4X}", state.stack[state.SP - 1])!; + } else fmt::printfln("Unkown OP code")!; + case 0x01 => + fmt::printfln("Jump to 0x{:.4X}", inst.NNN)!; + case 0x02 => + fmt::printfln("Call subroutine at 0x{:.4X}", inst.NNN)!; + case 0x03 => + fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) == 0x{:.2X}", + inst.X, state.V[inst.X], inst.NN)!; + case 0x04 => + fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) != 0x{:.2X}", + inst.X, state.V[inst.X], inst.NN)!; + case 0x05 => + fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) == V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x06 => + fmt::printfln("V{:X} (0x{:.2X}) = 0x{:.2X}", inst.X, state.V[inst.X], inst.NN)!; + case 0x07 => + fmt::printfln("V{:X} (0x{:.2X}) += 0x{:.2X}", inst.X, state.V[inst.X], inst.NN)!; + case 0x08 => + switch (inst.N) { + case 0x00 => + fmt::printfln("V{:X} (0x{:.2X}) = V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x01 => + fmt::printfln("V{:X} (0x{:.2X}) |= V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x02 => + fmt::printfln("V{:X} (0x{:.2X}) &= V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x03 => + fmt::printfln("V{:X} (0x{:.2X}) ^= V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x04 => + fmt::printfln("V{:X} (0x{:.2X}) += V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x05 => + fmt::printfln("V{:X} (0x{:.2X}) -= V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x06 => + fmt::printfln("V{:X} >>= 1", inst.X)!; + case 0x07 => + fmt::printfln("V{:X} = V{:X} (0x{:.2X}) - V{:X} (0x{:.2X})", + inst.X, inst.Y, state.V[inst.Y], inst.X, state.V[inst.X])!; + case 0x0E => + fmt::printfln("V{:X} <<= 1", inst.X)!; + case => + fmt::printfln("Unkown OP code")!; + }; + case 0x09 => + fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) != V{:X} (0x{:.2X})", + inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!; + case 0x0A => + fmt::printfln("I (0x{:.4X}) = 0x{:.4X}", state.I, inst.NNN)!; + case 0x0B => + fmt::printfln("Jump to 0x{:.4X}", state.V[0x00] + inst.NNN)!; + case 0x0C => + fmt::printfln("V{:X} (0x{:.2X}) = rand() & 0x{:.2X}", + inst.X, state.V[inst.X], inst.NN)!; + case 0x0D => + fmt::printfln("Draw 8 pixel wide, {} pixel tall sprite at X = V{:X} and Y = V{:X} ({}, {}), reading from memory location 0x{:.4X} onward", + inst.N, inst.X, inst.Y, state.V[inst.X], state.V[inst.Y], state.I)!; + case 0x0E => + if (inst.NN == 0x9E) { + fmt::printfln("Skip next instruction if the key stored in V{:X} (0x{:X}) is pressed", + inst.X, state.V[inst.X] & 0x0F)!; + } else if (inst.NN == 0xA1) { + fmt::printfln("Skip next instruction if the key stored in V{:X} (0x{:X}) is not pressed", + inst.X, state.V[inst.X] & 0x0F)!; + } else fmt::printfln("Unkown OP code")!; + case 0x0F => + switch (inst.NN) { + case 0x07 => + fmt::printfln("V{:X} (0x{:.2X}) = delay timer (0x{:.2X})", + inst.X, state.V[inst.X], state.DT)!; + case 0x0A => + fmt::printfln("Wait for keypress and store it in V{:X} (0x{:.2X})", + inst.X, state.V[inst.X])!; + case 0x15 => + fmt::printfln("Set delay timer (0x{:.2X}) to V{:X} (0x{:.2X})", + state.DT, inst.X, state.V[inst.X])!; + case 0x18 => + fmt::printfln("Set sound timer (0x{:.2X}) to V{:X} (0x{:.2X})", + state.ST, inst.X, state.V[inst.X])!; + case 0x1E => + fmt::printfln("I += V{:X} (0x{:.2X})", inst.X, state.V[inst.X])!; + case 0x29 => + fmt::printfln("I = sprite location for character {:X} (0x{:.4X})", + state.V[inst.X] & 0x0F, (state.V[inst.X] & 0x0F) * 5)!; + case 0x33 => + fmt::printfln("Stores the BCD representation of V{:X} (0x{:.2X}) with hundreds at 0x{:.4X}, tens at 0x{:.4X} and ones at 0x{:.4X}", + inst.X, state.V[inst.X], state.I, state.I + 1, state.I + 2)!; + case 0x55 => + fmt::printfln("Stores from V0 to (and including) V{:X} in memory, starting at 0x{:.4X}", + inst.X, state.I)!; + case 0x65 => + fmt::printfln("Fills from V0 to (and including) V{:X} with values from memory, starting at 0x{:.4X}", + inst.X, state.I)!; + case => + fmt::printfln("Unkown OP code")!; + }; + case => + fmt::printfln("Unkown OP code")!; + }; +}; + +fn execute_instruction(state: *state, inst: inst, random: *random::random) void = { + switch ((inst.op & 0xF000) >> 12) { + case 0x00 => + if (inst.NNN == 0x0E0) { + state.display[..] = [false...]; + state.draw = true; + } else if (inst.NNN == 0x0EE) { + state.SP -= 1; + state.PC = state.stack[state.SP]; + }; + case 0x01 => + state.PC = inst.NNN; + case 0x02 => + state.stack[state.SP] = state.PC; + state.SP += 1; + state.PC = inst.NNN; + case 0x03 => + if (state.V[inst.X] == inst.NN) + state.PC += 2; + case 0x04 => + if (state.V[inst.X] != inst.NN) + state.PC += 2; + case 0x05 => + if (state.V[inst.X] == state.V[inst.Y]) + state.PC += 2; + case 0x06 => + state.V[inst.X] = inst.NN; + case 0x07 => + state.V[inst.X] += inst.NN; + case 0x08 => + switch (inst.N) { + case 0x00 => + state.V[inst.X] = state.V[inst.Y]; + case 0x01 => + state.V[inst.X] |= state.V[inst.Y]; + case 0x02 => + state.V[inst.X] &= state.V[inst.Y]; + case 0x03 => + state.V[inst.X] ^= state.V[inst.Y]; + case 0x04 => + state.V[0x0F] = if (state.V[inst.X] > types::U8_MAX - state.V[inst.Y]) 1 else 0; + state.V[inst.X] += state.V[inst.Y]; + case 0x05 => + state.V[0x0F] = if (state.V[inst.X] >= state.V[inst.Y]) 1 else 0; + + state.V[inst.X] -= state.V[inst.Y]; + case 0x06 => + state.V[0x0F] = state.V[inst.X] & 0x01; + state.V[inst.X] >>= 1; + case 0x07 => + state.V[0x0F] = if (state.V[inst.Y] >= state.V[inst.X]) 1 else 0; + + state.V[inst.X] = state.V[inst.Y] - state.V[inst.X]; + case 0x0E => + state.V[0x0F] = state.V[inst.X] & 0x80; + state.V[inst.X] <<= 1; + case => + return; + }; + case 0x09 => + if (state.V[inst.X] != state.V[inst.Y]) + state.PC += 2; + case 0x0A => + state.I = inst.NNN; + case 0x0B => + state.PC = state.V[0x00] + inst.NNN; + case 0x0C => + state.V[inst.X] = (random::next(random) & 255): u8 & inst.NN; + case 0x0D => + const rx = state.V[inst.X]; + const ry = state.V[inst.Y]; + const sprite_height = inst.N; + let sprite_row = 0u8; + + state.V[0x0F] = 0; + + for (let y = 0u8; y < sprite_height; y += 1) { + sprite_row = state.mem[state.I + y]; + + for (let x = 0u8; x < 8; x += 1) { + if ((sprite_row & (0x80 >> x)) != 0) { + let pixel = &state.display[((ry + y) * SCREEN_WIDTH + rx + x) % (SCREEN_WIDTH * SCREEN_HEIGHT)]; + + if (*pixel) + state.V[0x0F] = 1; + + *pixel = !*pixel; + }; + }; + }; + + state.draw = true; + case 0x0E => + if (inst.NN == 0x9E) { + if (state.keys[state.V[inst.X] & 0x0F]) + state.PC += 2; + } else if (inst.NN == 0xA1) { + if (!state.keys[state.V[inst.X] & 0x0F]) + state.PC += 2; + }; + case 0x0F => + switch (inst.NN) { + case 0x07 => + state.V[inst.X] = state.DT; + case 0x0A => + let any_pressed = false; + let key: u8 = 0xFF; + + for (let i = 0z; key == 0xFF && i < len(state.keys); i += 1) + if (state.keys[i]) { + any_pressed = true; + key = i: u8; + break; + }; + + if (!any_pressed) state.PC -= 2 + else { + if (state.keys[key]) + state.PC -= 2 + else { + state.V[inst.X] = key; + key = 0xFF; + any_pressed = false; + }; + }; + case 0x15 => + state.DT = state.V[inst.X]; + case 0x18 => + state.ST = state.V[inst.X]; + case 0x1E => + state.I += state.V[inst.X]; + case 0x29 => + state.I = (state.V[inst.X] & 0x0F) * 5; + case 0x33 => + let BCD = state.V[inst.X]; + state.mem[state.I + 2] = BCD % 10; + BCD /= 10; + state.mem[state.I + 1] = BCD % 10; + BCD /= 10; + state.mem[state.I] = BCD; + case 0x55 => + for (let i = 0u8; i <= inst.X; i += 1) { + state.mem[state.I] = state.V[i]; + state.I += 1; + }; + case 0x65 => + for (let i = 0u8; i <= inst.X; i += 1) { + state.V[i] = state.mem[state.I]; + state.I += 1; + }; + case => + return; + }; + case => + return; + }; +}; + +fn tick_timers(state: *state) void = { + if (state.DT > 0) + state.DT -= 1; + + if (state.ST > 0) { + // BEEP + + state.ST -= 1; + }; +}; + +fn tick(state: *state, random: *random::random, debug: bool) void = { + let op_hi = state.mem[state.PC]: u16; + let op_lo = state.mem[state.PC + 1]: u16; + let op = (op_hi << 8) | op_lo; + state.PC += 2; + + if (debug) + print_instruction(state, instruction(op)); + + execute_instruction(state, instruction(op), random); +}; + +fn update_screen(state: *state, renderer: *sdl2::SDL_Renderer) (void | sdl2::error) = { + let rect = sdl2::SDL_Rect { + x = 0, + y = 0, + w = SCALE, + h = SCALE + }; + + for (let i = 0z; i < len(state.display); i += 1) { + rect.x = (i % SCREEN_WIDTH): int * SCALE; + rect.y = (i / SCREEN_WIDTH): int * SCALE; + + if (state.display[i]) { + sdl2::SDL_SetRenderDrawColor(renderer, 0xFE, 0xFE, 0xFE, 0xFF)?; + sdl2::SDL_RenderFillRect(renderer, &rect)?; + + sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?; + sdl2::SDL_RenderDrawRect(renderer, &rect)?; + } else { + sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?; + sdl2::SDL_RenderFillRect(renderer, &rect)?; + }; + }; + + sdl2::SDL_RenderPresent(renderer); +}; + +fn run() (void | fs::error | io::error | sdl2::error) = { + if (len(os::args) < 2) { + fmt::fatalf("Usage: {} <rom>", os::args[0]); + }; + + let debug = os::getenv("CHIP8_DEBUG") is str; + + const chip8 = chip8(); + let seed = time::now(time::clock::REALTIME).nsec: u64; + let random = random::init(seed); + + const rom = os::open(os::args[1])?; + defer io::close(rom)!; + + io::read(rom, chip8.mem[START_ADDR..])?; + + sdl2::SDL_Init(sdl2::SDL_INIT_VIDEO | sdl2::SDL_INIT_AUDIO)?; + defer sdl2::SDL_Quit(); + + let window = sdl2::SDL_CreateWindow("CHIP-8", sdl2::SDL_WINDOWPOS_CENTERED, sdl2::SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH: int * SCALE, SCREEN_HEIGHT: int * SCALE, sdl2::SDL_WindowFlags::SHOWN)?; + defer sdl2::SDL_DestroyWindow(window); + + let renderer = sdl2::SDL_CreateRenderer(window, -1, sdl2::SDL_RendererFlags::ACCELERATED)?; + defer sdl2::SDL_DestroyRenderer(renderer); + + sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?; + sdl2::SDL_RenderClear(renderer)?; + + let quit = false; + for (!quit) { + let event = sdl2::event { ... }; + for (sdl2::SDL_PollEvent(&event)? == 1) + switch (event.event_type) { + case sdl2::SDL_EventType::QUIT => + quit = true; + case sdl2::SDL_EventType::KEYDOWN => + switch (event.key.keysym.scancode) { + case sdl2::SDL_Scancode::ESCAPE => + quit = true; + case sdl2::SDL_Scancode::TWO => + chip8.keys[0x1] = true; + case sdl2::SDL_Scancode::THREE => + chip8.keys[0x2] = true; + case sdl2::SDL_Scancode::FOUR => + chip8.keys[0x3] = true; + case sdl2::SDL_Scancode::FIVE => + chip8.keys[0xC] = true; + case sdl2::SDL_Scancode::W => + chip8.keys[0x4] = true; + case sdl2::SDL_Scancode::E => + chip8.keys[0x5] = true; + case sdl2::SDL_Scancode::R => + chip8.keys[0x6] = true; + case sdl2::SDL_Scancode::T => + chip8.keys[0xD] = true; + case sdl2::SDL_Scancode::S => + chip8.keys[0x7] = true; + case sdl2::SDL_Scancode::D => + chip8.keys[0x8] = true; + case sdl2::SDL_Scancode::F => + chip8.keys[0x9] = true; + case sdl2::SDL_Scancode::G => + chip8.keys[0xE] = true; + case sdl2::SDL_Scancode::X => + chip8.keys[0xA] = true; + case sdl2::SDL_Scancode::C => + chip8.keys[0x0] = true; + case sdl2::SDL_Scancode::V => + chip8.keys[0xB] = true; + case sdl2::SDL_Scancode::B => + chip8.keys[0xF] = true; + case => + void; + }; + case sdl2::SDL_EventType::KEYUP => + switch (event.key.keysym.scancode) { + case sdl2::SDL_Scancode::ESCAPE => + quit = true; + case sdl2::SDL_Scancode::TWO => + chip8.keys[0x1] = false; + case sdl2::SDL_Scancode::THREE => + chip8.keys[0x2] = false; + case sdl2::SDL_Scancode::FOUR => + chip8.keys[0x3] = false; + case sdl2::SDL_Scancode::FIVE => + chip8.keys[0xC] = false; + case sdl2::SDL_Scancode::W => + chip8.keys[0x4] = false; + case sdl2::SDL_Scancode::E => + chip8.keys[0x5] = false; + case sdl2::SDL_Scancode::R => + chip8.keys[0x6] = false; + case sdl2::SDL_Scancode::T => + chip8.keys[0xD] = false; + case sdl2::SDL_Scancode::S => + chip8.keys[0x7] = false; + case sdl2::SDL_Scancode::D => + chip8.keys[0x8] = false; + case sdl2::SDL_Scancode::F => + chip8.keys[0x9] = false; + case sdl2::SDL_Scancode::G => + chip8.keys[0xE] = false; + case sdl2::SDL_Scancode::X => + chip8.keys[0xA] = false; + case sdl2::SDL_Scancode::C => + chip8.keys[0x0] = false; + case sdl2::SDL_Scancode::V => + chip8.keys[0xB] = false; + case sdl2::SDL_Scancode::B => + chip8.keys[0xF] = false; + case => + void; + }; + case => void; + }; + + const frame_start = sdl2::SDL_GetPerformanceCounter(); + + for (let i = 0; i < CLOCKSPEED / 60; i += 1) { + tick(&chip8, &random, debug); + + if ((chip8.mem[chip8.PC - 2] >> 4) == 0x0D) + break; + }; + + const frame_end = sdl2::SDL_GetPerformanceCounter(); + + const time_elapsed = ((frame_end - frame_start) * 1000): f64 / sdl2::SDL_GetPerformanceFrequency(): f64; + + sdl2::SDL_Delay(if (16.67 > time_elapsed) (16.67 - time_elapsed): u32 else 0); + + if (chip8.draw) { + update_screen(&chip8, renderer)?; + chip8.draw = false; + }; + + tick_timers(&chip8); + }; +}; + +export fn main() void = { + match (run()) { + case let err: fs::error => + fmt::fatal("FS error: ", fs::strerror(err)); + case let err: io::error => + fmt::fatal("I/O error: ", io::strerror(err)); + case let err: sdl2::error => + fmt::fatal("SDL error: ", sdl2::strerror(err)); + case void => + return; + }; +}; |
