From e956e4abaacfdad2b9becd24b193314df1629a5c Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Sat, 18 Jul 2026 08:28:24 -0500 Subject: [PATCH] feat: make space invaders playable --- docs/todos.md | 40 ++++++++------ main.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 21 deletions(-) diff --git a/docs/todos.md b/docs/todos.md index 533db15..ba8cc5c 100644 --- a/docs/todos.md +++ b/docs/todos.md @@ -56,10 +56,20 @@ - [x] Refactor main - [x] Migrate drawing to raylib window +- [x] Handle keyboard input -- [ ] Handle keyboard input + CHIP-8 Pad QWERTY Keyboard + ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ + │ 1 │ 2 │ 3 │ C │ │ 1 │ 2 │ 3 │ 4 │ + ├───┼───┼───┼───┤ ├───┼───┼───┼───┤ + │ 4 │ 5 │ 6 │ D │ ====> │ Q │ W │ E │ R │ + ├───┼───┼───┼───┤ ├───┼───┼───┼───┤ + │ 7 │ 8 │ 9 │ E │ │ A │ S │ D │ F │ + ├───┼───┼───┼───┤ ├───┼───┼───┼───┤ + │ A │ 0 │ B │ F │ │ Z │ X │ C │ V │ + └───┴───┴───┴───┘ └───┴───┴───┴───┘ -- [ ] Implement remaining instructions +- [x] Implement remaining instructions | x | Opcode | Type | Description | | --- | ------ | ---- | --------------------------------------------------------------- | @@ -76,17 +86,17 @@ | x | `8xy6` | 8xy6 | Set Vx = Vx SHR 1 | | x | `8xy7` | 8xy7 | Set Vx = Vy - Vx, set VF = NOT borrow | | x | `8xyE` | 8xyE | Set Vx = Vx SHL 1 | - | | `9xy0` | 9xy0 | Skip next if Vx != Vy | + | x | `9xy0` | 9xy0 | Skip next if Vx != Vy | | x | `Cxkk` | Cxkk | Set Vx = random byte AND kk | - | | `Ex9E` | Ex9E | Skip next if key Vx is pressed | - | | `ExA1` | ExA1 | Skip next if key Vx is not pressed | - | | `Fx07` | Fx07 | Set Vx = delay timer value | - | | `Fx0A` | Fx0A | Wait for key press, store key in Vx | - | | `Fx15` | Fx15 | Set delay timer = Vx | - | | `Fx18` | Fx18 | Set sound timer = Vx | - | | `Fx1E` | Fx1E | Set I = I + Vx | - | | `Fx29` | Fx29 | Set I = location of sprite for digit Vx | - | | `Fx33` | Fx33 | Store BCD representation of Vx in memory | - | | `Fx55` | Fx55 | Store registers V0 through Vx in memory starting at location I | - | | `Fx65` | Fx65 | Read registers V0 through Vx from memory starting at location I | - | | `0nnn` | 0nnn | Jump to machine code routine (ignored) | + | x | `Ex9E` | Ex9E | Skip next if key Vx is pressed | + | x | `ExA1` | ExA1 | Skip next if key Vx is not pressed | + | x | `Fx07` | Fx07 | Set Vx = delay timer value | + | x | `Fx0A` | Fx0A | Wait for key press, store key in Vx | + | x | `Fx15` | Fx15 | Set delay timer = Vx | + | x | `Fx18` | Fx18 | Set sound timer = Vx | + | x | `Fx1E` | Fx1E | Set I = I + Vx | + | x | `Fx29` | Fx29 | Set I = location of sprite for digit Vx | + | x | `Fx33` | Fx33 | Store BCD representation of Vx in memory | + | x | `Fx55` | Fx55 | Store registers V0 through Vx in memory starting at location I | + | x | `Fx65` | Fx65 | Read registers V0 through Vx from memory starting at location I | + | - | `0nnn` | 0nnn | Jump to machine code routine (ignored) | diff --git a/main.c b/main.c index 457640e..1e51c6d 100644 --- a/main.c +++ b/main.c @@ -9,11 +9,31 @@ #define MEMORY_SIZE 4096 #define PROGRAM_START 512 +#define FONT_START 80 #define CPU_CLOCK_HZ 600 #define TIMER_HZ 60 #define SCREEN_WIDTH 64 #define SCREEN_HEIGHT 32 +const uint8_t fontset[80] = { + 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 +}; + const int CYCLES_PER_FRAME = CPU_CLOCK_HZ / TIMER_HZ; volatile sig_atomic_t keep_running = 1; @@ -43,6 +63,9 @@ typedef struct ChipEight { uint16_t stack[16]; uint8_t sc; uint32_t display[SCREEN_WIDTH * SCREEN_HEIGHT]; + uint8_t keys[16]; + uint8_t key_register; + bool waiting_for_key; } chip_eight_t; void println(const char *format, ...) { @@ -84,7 +107,6 @@ void draw(chip_eight_t *chip) { printf("\n"); } - } void setup_graceful_exit() { @@ -123,6 +145,12 @@ void init_chip(chip_eight_t *chip) { chip->dt = 0; chip->st = 0; memset(chip->display, 0, sizeof(chip->display)); + memset(chip->keys, 0, sizeof(chip->keys)); + chip->waiting_for_key = false; + + for (int i = 0; i < 80; i++) { + chip->memory[FONT_START + i] = fontset[i]; + } } void draw_sprite(chip_eight_t *chip, uint16_t n, uint16_t x, uint16_t y) { @@ -160,6 +188,28 @@ void draw_sprite(chip_eight_t *chip, uint16_t n, uint16_t x, uint16_t y) { } } +void update_keys(chip_eight_t *chip) { + chip->keys[1] = IsKeyDown(KEY_ONE); + chip->keys[2] = IsKeyDown(KEY_TWO); + chip->keys[3] = IsKeyDown(KEY_THREE); + chip->keys[12] = IsKeyDown(KEY_FOUR); + + chip->keys[4] = IsKeyDown(KEY_Q); + chip->keys[5] = IsKeyDown(KEY_W); + chip->keys[6] = IsKeyDown(KEY_E); + chip->keys[13] = IsKeyDown(KEY_R); + + chip->keys[7] = IsKeyDown(KEY_A); + chip->keys[8] = IsKeyDown(KEY_S); + chip->keys[9] = IsKeyDown(KEY_D); + chip->keys[14] = IsKeyDown(KEY_F); + + chip->keys[10] = IsKeyDown(KEY_Z); + chip->keys[0] = IsKeyDown(KEY_X); + chip->keys[11] = IsKeyDown(KEY_C); + chip->keys[15] = IsKeyDown(KEY_V); +} + int main(int argc, char *argv[]) { setup_graceful_exit(); @@ -181,6 +231,8 @@ int main(int argc, char *argv[]) { int load_rom_result = load_rom(chip, argv[1]); if (load_rom_result != 0) { + // TODO: Check for return value + // print helpful error message return load_rom_result; } @@ -192,6 +244,40 @@ int main(int argc, char *argv[]) { while(!WindowShouldClose() || !keep_running) { + update_keys(chip); + + if (chip->waiting_for_key) { + bool key_pressed = false; + + for (int i = 0; i < 16; i++) { + if (chip->keys[i]) { + chip->V[chip->key_register] = i; + chip->waiting_for_key = false; + key_pressed = true; + break; + } + } + + if (!key_pressed) { + BeginDrawing(); + ClearBackground(BLACK); + + for (int i = 0; i < SCREEN_HEIGHT; i++) { + for (int j = 0; j < SCREEN_WIDTH; j++) { + uint32_t screen_pixel = chip->display[i * SCREEN_WIDTH + j]; + + if (screen_pixel == 1) { + DrawRectangle(j * scale_factor, i * scale_factor, scale_factor, scale_factor, RAYWHITE); + } + } + } + + EndDrawing(); + continue; + } + + } + for(int i = 0; i < CYCLES_PER_FRAME; i++) { // read 2 bytes at a time from memory // reading first 1 byte shifting left 1 byte ORing 2 byte @@ -201,6 +287,9 @@ int main(int argc, char *argv[]) { // extracting the lowest 12 bits of the instruction uint16_t nnn = opcode & 4095; + //extracting the lowest 8 bits of the instruction + uint8_t nn = opcode & 255; + //extracting the lowest 4 bits of the instruction uint16_t n = opcode & 15; @@ -218,16 +307,16 @@ int main(int argc, char *argv[]) { if (opcode == 224) { // 00E0 memset(chip->display, 0, sizeof(chip->display)); } else if (opcode == 238) { // 00EE - chip->pc = chip->stack[15]; chip->sc -= 1; + chip->pc = chip->stack[chip->sc]; } break; case 4096: // 1nnn chip->pc = nnn; break; case 8192: // 2nnn + chip->stack[chip->sc] = chip->pc; chip->sc += 1; - chip->stack[15] = chip->pc; chip->pc = nnn; break; case 12288: // 3xkk @@ -251,7 +340,7 @@ int main(int argc, char *argv[]) { case 28672: // 7xkk chip->V[x] += kk; break; - case 32768: + case 32768: // 8xy0 // TODO: Maybe switch? if (n == 0) { chip->V[x] = chip->V[y]; @@ -281,6 +370,11 @@ int main(int argc, char *argv[]) { chip->V[x] *= 2; } break; + case 36864: // 9xy0 + if (chip->V[x] != chip->V[y]) { + chip->pc += 2; + } + break; case 40960: // ANNN chip->I = nnn; break; @@ -294,8 +388,44 @@ int main(int argc, char *argv[]) { case 53248: // Dxyn draw_sprite(chip, n, x, y); break; - case 57502: // E09E - // TODO: Need to get key inputs + case 57344: // E000 + if (nn == 158) { + if (chip->keys[chip->V[x]] == 1) { + chip->pc += 2; + } + } else if (nn == 161) { + if (chip->keys[chip->V[x]] != 1) { + chip->pc += 2; + } + } + break; + case 61440: // F000 + if (nn == 7) { + chip->V[x] = chip->dt; + } else if (nn == 10) { + chip->waiting_for_key = true; + chip->key_register = x; + } else if (nn == 21) { + chip->dt = chip->V[x]; + } else if (nn == 24) { + chip->st = chip->V[x]; + } else if (nn == 30) { + chip->I += chip->V[x]; + } else if (nn == 41) { + chip->I = FONT_START + (chip->V[x] * 5); + } else if (nn == 51) { + chip->memory[chip->I] = chip->V[x] / 100; + chip->memory[chip->I + 1] = (chip->V[x] / 10) % 10; + chip->memory[chip->I + 2] = chip->V[x] % 10; + } else if (nn == 85) { + for (int i = 0; i <= x; i++) { + chip->memory[chip->I + i] = chip->V[i]; + } + } else if (nn == 101) { + for (int i = 0; i <= x; i++) { + chip->V[i] = chip->memory[chip->I + i]; + } + } break; default: break;