From 6e0e034dfab42c4d0905bd97d037a77f0e0b2c6c Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sat, 17 Oct 2020 12:59:44 +0200 Subject: [PATCH] Separate chip8 & sdl pkgs, update readme, add roms --- README.md | 14 +- chip8/chip8.go | 395 ++++++++++++++++++++++++++++++++ go.mod | 5 + go.sum | 2 + main.go | 500 ++++------------------------------------- roms/TETRIS | Bin 0 -> 494 bytes roms/chip8-picture.ch8 | Bin 0 -> 164 bytes roms/invaders.c8 | Bin 0 -> 1301 bytes roms/pong.c8 | Bin 0 -> 294 bytes roms/slipperyslope.ch8 | Bin 0 -> 2482 bytes screenshots/s00.png | Bin 0 -> 2707 bytes screenshots/s01.png | Bin 0 -> 2272 bytes screenshots/s02.png | Bin 0 -> 1710 bytes screenshots/s03.png | Bin 0 -> 1160 bytes 14 files changed, 460 insertions(+), 456 deletions(-) create mode 100644 chip8/chip8.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 roms/TETRIS create mode 100644 roms/chip8-picture.ch8 create mode 100755 roms/invaders.c8 create mode 100755 roms/pong.c8 create mode 100644 roms/slipperyslope.ch8 create mode 100644 screenshots/s00.png create mode 100644 screenshots/s01.png create mode 100644 screenshots/s02.png create mode 100644 screenshots/s03.png diff --git a/README.md b/README.md index 1cb3495..8eb7c97 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ -# go-chip8 +# go-chip8 [![Go Report Card](https://goreportcard.com/badge/github.com/arnaucube/go-chip8)](https://goreportcard.com/report/github.com/arnaucube/go-chip8) CHIP-8 emulator written in Go. -[WIP] - https://en.wikipedia.org/wiki/CHIP-8 + +### Usage +``` +go run main.go -file roms/invaders.c8 +``` + +![](screenshots/s00.png) +![](screenshots/s01.png) +![](screenshots/s02.png) +![](screenshots/s03.png) diff --git a/chip8/chip8.go b/chip8/chip8.go new file mode 100644 index 0000000..685a250 --- /dev/null +++ b/chip8/chip8.go @@ -0,0 +1,395 @@ +package chip8 + +import ( + "fmt" + "io/ioutil" + "math/rand" +) + +// W represents the width of the screen +const W = 64 + +// H represents the height of the screen +const H = 32 + +// Chip8 contains all the data and methods for the Chip8 emulator +type Chip8 struct { + opcode uint16 + memory [4096]byte + + // register + v [16]byte + + index uint16 + pc uint16 + + Gfx [W * H]byte + + delayTimer byte + soundTimer byte + + stack [16]uint16 + sp int + + Key [16]byte + + DrawFlag bool +} + +// Initialize registers and memory +func (c *Chip8) Initialize() { + c.pc = 0x200 + c.opcode = 0 + c.index = 0 + c.sp = 0 + + for i := 0; i < len(fontSet); i++ { + c.memory[i] = fontSet[i] + } +} + +// EmulateCycle emulates the chip8 cycle +func (c *Chip8) EmulateCycle() { + // Fetch Opcode + c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1]) + x := byte((c.opcode & 0x0F00) >> 8) + y := byte((c.opcode & 0x00F0) >> 4) + nn := byte(c.opcode & 0x00FF) + nnn := uint16(c.opcode & 0x0FFF) + + // Decode Opcode + // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table + // http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml + switch c.opcode & 0xF000 { + case 0x0000: + switch c.opcode & 0x000F { + case 0x0000: + // 00E0 Clear screen + for i := 0; i < len(c.Gfx); i++ { + c.Gfx[i] = 0 + } + c.pc += 2 + c.DrawFlag = true + break + case 0x000E: + // 00EE Returns from a subroutine + c.sp-- + c.pc = c.stack[c.sp] + c.pc += 2 + break + default: + fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode) + } + case 0x1000: + // 1NNN Jumps to address NNN + c.pc = nnn + break + case 0x2000: + // 2NNN Calls subroutine at NNN + c.stack[c.sp] = c.pc + c.sp++ + c.pc = nnn + break + case 0x3000: + // 3XNN Skips the next instruction if VX equals NN. (Usually + // the next instruction is a jump to skip a code block) + if c.v[x] == nn { + c.pc += 2 + } + c.pc += 2 + break + case 0x4000: + // 4XNN Skips the next instruction if VX doesn't equal NN. + // (Usually the next instruction is a jump to skip a code + // block) + if c.v[x] != nn { + c.pc += 2 + } + c.pc += 2 + break + case 0x5000: + // 5XY0 Skips the next instruction if VX equals VY. (Usually + // the next instruction is a jump to skip a code block) + if c.v[x] != c.v[y] { + c.pc += 2 + } + c.pc += 2 + break + case 0x6000: + // 6XNN Sets VX to NN + c.v[x] = nn + c.pc += 2 + break + case 0x7000: + // 7XNN Adds NN to VX. (Carry flag is not changed) + c.v[x] += nn + c.pc += 2 + break + case 0x8000: + switch c.opcode & 0x000F { + case 0x0000: + // 0x8XY0 Sets VX to the value of VY + c.v[x] = c.v[y] + c.pc += 2 + case 0x0001: + // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation) + c.v[x] = (c.v[x] | c.v[y]) + c.pc += 2 + case 0x0002: + // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation) + c.v[x] = (c.v[x] & c.v[y]) + c.pc += 2 + case 0x0003: + // 0x8XY3 Sets VX to VX xor VY + c.v[x] = (c.v[x] ^ c.v[y]) + c.pc += 2 + case 0x0004: + // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a + // carry, and to 0 when there isn't + if c.v[y] > (0xFF - c.v[x]) { + c.v[0xF] = 1 + } else { + c.v[0xF] = 0 + } + c.v[x] += c.v[y] + c.pc += 2 + break + case 0x0005: + // 0x8XY5 VY is subtracted from VX. VF is set to 0 when + // there's a borrow, and 1 when there isn't + if c.v[x] > c.v[y] { + c.v[0xF] = 0x1 + } else { + c.v[0xF] = 0x0 + } + c.v[x] -= c.v[y] + c.pc += 2 + case 0x0006: + // 0x8XY6 Stores the least significant bit of VX in VF + // and then shifts VX to the right by 1 + if c.opcode&0x1 >= 1 { + c.v[0xF] = 1 + } else { + c.v[0xF] = 0 + } + c.v[x] = c.v[x] >> 1 + c.pc += 2 + case 0x0007: + // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when + // there's a borrow, and 1 when there isn't + if c.v[y] > c.v[x] { + c.v[0xF] = 0x1 + } else { + c.v[0xF] = 0x0 + } + c.v[x] = c.v[y] - c.v[x] + c.pc += 2 + case 0x000E: + // 0x8XYE Stores the most significant bit of VX in VF + // and then shifts VX to the left by 1 + if c.opcode&0x80 == 0x80 { + c.v[0xF] = 1 + } else { + c.v[0xF] = 0 + } + c.v[x] = c.v[x] << 1 + c.pc += 2 + default: + fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode) + } + case 0x9000: + // 9XY0 Skips the next instruction if VX doesn't equal VY. + // (Usually the next instruction is a jump to skip a code + // block) + if c.v[x] != c.v[y] { + c.pc += 2 + } + c.pc += 2 + case 0xA000: + // ANNN set index to NNN position + c.index = nnn + c.pc += 2 + break + case 0xB000: + // BNNN Jumps to the address NNN plus V0 + c.pc = nnn + uint16(c.v[0]) + case 0xC000: + // CXNN Sets VX to the result of a bitwise and operation on a + // random number (Typically: 0 to 255) and NN + r := byte(rand.Intn(255)) + c.v[x] = r & nn + c.pc += 2 + case 0xD000: + // DXYN Draws a sprite at coordinate (VX, VY) that has a width + // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels + // is read as bit-coded starting from memory location I; I + // value doesn’t change after the execution of this + // instruction. As described above, VF is set to 1 if any + // screen pixels are flipped from set to unset when the sprite + // is drawn, and to 0 if that doesn’t happen + + height := c.opcode & 0x000F + + var pixel byte + c.v[0xF] = 0 + + for yline := uint16(0); yline < height; yline++ { + pixel = c.memory[c.index+yline] + for xline := uint16(0); xline < 8; xline++ { + if (pixel & (0x80 >> xline)) != 0 { + pos := (uint16(c.v[x]) + xline) + ((uint16(c.v[y]) + yline) * W) + if pos >= 2048 { + break + } + if c.Gfx[pos] == 1 { + c.v[0xF] = 1 + } + c.Gfx[pos] ^= 1 + } + } + + } + + c.DrawFlag = true + c.pc += 2 + break + case 0xE000: + switch c.opcode & 0x00FF { + case 0x009E: + // EX9E Skips the next instruction if the key stored in + // VX is pressed. (Usually the next instruction is a + // jump to skip a code block) + if c.Key[c.v[x]] != 0 { + c.pc += 2 + } + c.pc += 2 + break + case 0x00A1: + // EXA1 Skips the next instruction if the key stored in + // VX isn't pressed. (Usually the next instruction is a + // jump to skip a code block) + if c.Key[c.v[x]] != 1 { + c.pc += 2 + } + c.pc += 2 + default: + fmt.Printf("Unknown opcode [0xE000]: 0x%X\n", c.opcode) + } + break + case 0xF000: + switch c.opcode & 0x00FF { + case 0x0007: + // FX07 Sets VX to the value of the delay timer + c.v[x] = c.delayTimer + c.pc += 2 + case 0x000A: + // FX0A A key press is awaited, and then stored in VX. + // (Blocking Operation. All instruction halted until + // next key event) + pressed := false + for i := 0; i < 16; i++ { + if c.Key[i] == 1 { + c.v[x] = byte(i) + pressed = true + } + } + if !pressed { + return + } + c.pc += 2 + case 0x0015: + // FX15 Sets the delay timer to VX + c.delayTimer = c.v[x] + c.pc += 2 + case 0x0018: + // FX18 Sets the sound timer to VX + c.soundTimer = c.v[x] + c.pc += 2 + case 0x001E: + // FX1E Adds VX to I. VF is not affected + c.index += uint16(c.v[x]) + c.pc += 2 + case 0x0029: + // FX29 Sets I to the location of the sprite for the + // character in VX. Characters 0-F (in hexadecimal) are + // represented by a 4x5 font + c.index = uint16(c.v[x]) * 5 + c.pc += 2 + break + case 0x0033: + c.memory[c.index] = c.v[x] / 100 + c.memory[c.index+1] = (c.v[x] / 10) % 10 + c.memory[c.index+2] = (c.v[x] / 100) % 10 + c.pc += 2 + break + case 0x0055: + // FX55 Stores V0 to VX (including VX) in memory + // starting at address I. The offset from I is + // increased by 1 for each value written, but I itself + // is left unmodified + for i := uint16(0); i <= uint16(x); i++ { + c.memory[c.index+i] = c.v[i] + } + c.pc += 2 + case 0x0065: + // 0xFX65 Fills V0 to VX (including VX) with values + // from memory starting at address I. The offset from I + // is increased by 1 for each value written, but I + // itself is left unmodified + for i := uint16(0); i < uint16(x)+1; i++ { + c.v[i] = c.memory[c.index+i] + } + c.pc += 2 + break + default: + fmt.Printf("Unknown opcode [0xF000]: 0x%X\n", c.opcode) + } + break + default: + fmt.Printf("Unknown opcode: 0x%X\n", c.opcode) + } + + // Update timers + if c.delayTimer > 0 { + c.delayTimer-- + } + if c.soundTimer > 0 { + if c.soundTimer == 1 { + fmt.Printf("Beep!\n") + } + c.soundTimer-- + } +} + +// LoadGame loads the rom file of the given file path into the Chip8 memory +func (c *Chip8) LoadGame(filepath string) error { + buffer, err := ioutil.ReadFile(filepath) + if err != nil { + return err + } + + for i := 0; i < len(buffer); i++ { + // 0x200 == 512 + c.memory[512+i] = buffer[i] + } + return nil +} + +var fontSet = [80]byte{ + 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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..76a23a8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module go-chip8 + +go 1.14 + +require github.com/veandco/go-sdl2 v0.4.4 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a0880c9 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/veandco/go-sdl2 v0.4.4 h1:coOJGftOdvNvGoUIZmm4XD+ZRQF4mg9ZVHmH3/42zFQ= +github.com/veandco/go-sdl2 v0.4.4/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= diff --git a/main.go b/main.go index d973f37..3f6a361 100644 --- a/main.go +++ b/main.go @@ -3,489 +3,86 @@ package main import ( "flag" "fmt" - "io/ioutil" - "math/rand" + "go-chip8/chip8" "os" "github.com/veandco/go-sdl2/sdl" ) -const w = 64 -const h = 32 - -type chip8 struct { - opcode uint16 - memory [4096]byte - - // register - v [16]byte - - index uint16 - pc uint16 - - gfx [w * h]byte - - delayTimer byte - soundTimer byte - - stack [16]uint16 - sp int - - key [16]byte - - drawFlag bool - - // graphics +// SdlEmulator represents the Chip8 emulator with Sdl frontend +type SdlEmulator struct { + w int + h int renderer *sdl.Renderer zoom int32 + chip8 chip8.Chip8 } -// Initialize registers and memory once -func (c *chip8) initialize() { - c.pc = 0x200 - c.opcode = 0 - c.index = 0 - c.sp = 0 - c.zoom = 8 - - for i := 0; i < len(fontSet); i++ { - c.memory[i] = fontSet[i] - } -} - -func (c *chip8) emulateCycle() { - // Fetch Opcode - c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1]) - x := byte((c.opcode & 0x0F00) >> 8) - y := byte((c.opcode & 0x00F0) >> 4) - nn := byte(c.opcode & 0x00FF) - nnn := uint16(c.opcode & 0x0FFF) - // fmt.Printf("%X\n", c.opcode) - - // Decode Opcode - // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table - // http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml - switch c.opcode & 0xF000 { - case 0x0000: - switch c.opcode & 0x000F { - case 0x0000: - // 00E0 Clear screen - for i := 0; i < len(c.gfx); i++ { - c.gfx[i] = 0 - } - c.pc += 2 - c.drawFlag = true - break - case 0x000E: - // 00EE Returns from a subroutine - c.sp-- - c.pc = c.stack[c.sp] - c.pc += 2 - break - default: - fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode) - } - case 0x1000: - // 1NNN Jumps to address NNN - // c.pc = c.opcode & 0x0FFF - c.pc = nnn - break - case 0x2000: - // 2NNN Calls subroutine at NNN - c.stack[c.sp] = c.pc - c.sp++ - // c.pc = c.opcode & 0x0FFF - c.pc = nnn - break - case 0x3000: - // 3XNN Skips the next instruction if VX equals NN. (Usually - // the next instruction is a jump to skip a code block) - if c.v[x] == nn { - c.pc += 2 - } - c.pc += 2 - break - case 0x4000: - // 4XNN Skips the next instruction if VX doesn't equal NN. - // (Usually the next instruction is a jump to skip a code - // block) - if c.v[x] != nn { - c.pc += 2 - } - c.pc += 2 - break - case 0x5000: - // 5XY0 Skips the next instruction if VX equals VY. (Usually - // the next instruction is a jump to skip a code block) - if c.v[x] != c.v[y] { - c.pc += 2 - } - c.pc += 2 - break - case 0x6000: - // 6XNN Sets VX to NN - c.v[x] = nn - c.pc += 2 - break - case 0x7000: - // 7XNN Adds NN to VX. (Carry flag is not changed) - // c.v[c.opcode&0x0F00>>8] += byte(c.opcode) - c.v[x] += nn - c.pc += 2 - break - case 0x8000: - switch c.opcode & 0x000F { - case 0x0000: - // 0x8XY0 Sets VX to the value of VY - c.v[x] = c.v[y] - c.pc += 2 - case 0x0001: - // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation) - c.v[x] = (c.v[x] | c.v[y]) - c.pc += 2 - case 0x0002: - // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation) - c.v[x] = (c.v[x] & c.v[y]) - c.pc += 2 - case 0x0003: - // 0x8XY3 Sets VX to VX xor VY - c.v[x] = (c.v[x] ^ c.v[y]) - c.pc += 2 - case 0x0004: - // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a - // carry, and to 0 when there isn't - if c.v[y] > (0xFF - c.v[x]) { - c.v[0xF] = 1 - } else { - c.v[0xF] = 0 - } - c.v[x] += c.v[y] - c.pc += 2 - break - case 0x0005: - // 0x8XY5 VY is subtracted from VX. VF is set to 0 when - // there's a borrow, and 1 when there isn't - if c.v[x] > c.v[y] { - c.v[0xF] = 0x1 - } else { - c.v[0xF] = 0x0 - } - c.v[x] -= c.v[y] - c.pc += 2 - case 0x0006: - // 0x8XY6 Stores the least significant bit of VX in VF - // and then shifts VX to the right by 1 - if c.opcode&0x1 >= 1 { - c.v[0xF] = 1 - } else { - c.v[0xF] = 0 - } - c.v[x] = c.v[x] >> 1 - c.pc += 2 - case 0x0007: - // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when - // there's a borrow, and 1 when there isn't - if c.v[y] > c.v[x] { - c.v[0xF] = 0x1 - } else { - c.v[0xF] = 0x0 - } - c.v[x] = c.v[y] - c.v[x] - c.pc += 2 - case 0x000E: - // 0x8XYE Stores the most significant bit of VX in VF - // and then shifts VX to the left by 1 - if c.opcode&0x80 == 0x80 { - c.v[0xF] = 1 - } else { - c.v[0xF] = 0 - } - c.v[x] = c.v[x] << 1 - c.pc += 2 - default: - fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode) - } - case 0x9000: - // 9XY0 Skips the next instruction if VX doesn't equal VY. - // (Usually the next instruction is a jump to skip a code - // block) - if c.v[x] != c.v[y] { - c.pc += 2 - } - c.pc += 2 - case 0xA000: - // ANNN set index to NNN position - c.index = nnn - c.pc += 2 - break - case 0xB000: - // BNNN Jumps to the address NNN plus V0 - c.pc = nnn + uint16(c.v[0]) - case 0xC000: - // CXNN Sets VX to the result of a bitwise and operation on a - // random number (Typically: 0 to 255) and NN - r := byte(rand.Intn(255)) - c.v[x] = r & nn - c.pc += 2 - case 0xD000: - // DXYN Draws a sprite at coordinate (VX, VY) that has a width - // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels - // is read as bit-coded starting from memory location I; I - // value doesn’t change after the execution of this - // instruction. As described above, VF is set to 1 if any - // screen pixels are flipped from set to unset when the sprite - // is drawn, and to 0 if that doesn’t happen - - height := c.opcode & 0x000F - - var pixel byte - c.v[0xF] = 0 +// NewSdlEmulator creates a new SdlEmulator +func NewSdlEmulator(w, h int, zoom int32) SdlEmulator { + var c chip8.Chip8 + c.Initialize() - for yline := uint16(0); yline < height; yline++ { - pixel = c.memory[c.index+yline] - for xline := uint16(0); xline < 8; xline++ { - if (pixel & (0x80 >> xline)) != 0 { - pos := (uint16(c.v[x]) + xline) + ((uint16(c.v[y]) + yline) * w) - if pos >= 2048 { - break - } - if c.gfx[pos] == 1 { - c.v[0xF] = 1 - } - c.gfx[pos] ^= 1 - } - } - - } - - c.drawFlag = true - c.pc += 2 - break - case 0xE000: - switch c.opcode & 0x00FF { - case 0x009E: - // EX9E Skips the next instruction if the key stored in - // VX is pressed. (Usually the next instruction is a - // jump to skip a code block) - if c.key[c.v[x]] != 0 { - c.pc += 2 - } - c.pc += 2 - break - case 0x00A1: - // EXA1 Skips the next instruction if the key stored in - // VX isn't pressed. (Usually the next instruction is a - // jump to skip a code block) - if c.key[c.v[x]] != 1 { - c.pc += 2 - } - c.pc += 2 - default: - fmt.Printf("Unknown opcode [0xE000]: 0x%X\n", c.opcode) - } - break - case 0xF000: - switch c.opcode & 0x00FF { - case 0x0007: - // FX07 Sets VX to the value of the delay timer - c.v[x] = c.delayTimer - c.pc += 2 - case 0x000A: - // FX0A A key press is awaited, and then stored in VX. - // (Blocking Operation. All instruction halted until - // next key event) - pressed := false - for i := 0; i < 16; i++ { - if c.key[i] == 1 { - c.v[x] = byte(i) - pressed = true - } - } - if !pressed { - return - } - c.pc += 2 - case 0x0015: - // FX15 Sets the delay timer to VX - c.delayTimer = c.v[x] - c.pc += 2 - case 0x0018: - // FX18 Sets the sound timer to VX - c.soundTimer = c.v[x] - c.pc += 2 - case 0x001E: - // FX1E Adds VX to I. VF is not affected - c.index += uint16(c.v[x]) - c.pc += 2 - case 0x0029: - // FX29 Sets I to the location of the sprite for the - // character in VX. Characters 0-F (in hexadecimal) are - // represented by a 4x5 font - c.index = uint16(c.v[x]) * 5 - c.pc += 2 - break - case 0x0033: - c.memory[c.index] = c.v[x] / 100 - c.memory[c.index+1] = (c.v[x] / 10) % 10 - c.memory[c.index+2] = (c.v[x] / 100) % 10 - c.pc += 2 - break - case 0x0055: - // FX55 Stores V0 to VX (including VX) in memory - // starting at address I. The offset from I is - // increased by 1 for each value written, but I itself - // is left unmodified - for i := uint16(0); i <= uint16(x); i++ { - c.memory[c.index+i] = c.v[i] - } - c.pc += 2 - case 0x0065: - // 0xFX65 Fills V0 to VX (including VX) with values - // from memory starting at address I. The offset from I - // is increased by 1 for each value written, but I - // itself is left unmodified - for i := uint16(0); i < uint16(x)+1; i++ { - c.v[i] = c.memory[c.index+i] - } - c.pc += 2 - break - default: - fmt.Printf("Unknown opcode [0xF000]: 0x%X\n", c.opcode) - } - break - default: - fmt.Printf("Unknown opcode: 0x%X\n", c.opcode) - } - - // Update timers - if c.delayTimer > 0 { - c.delayTimer-- - } - if c.soundTimer > 0 { - if c.soundTimer == 1 { - fmt.Printf("Beep!\n") - } - c.soundTimer-- - } -} - -func (c *chip8) loadGame(filepath string) error { - buffer, err := ioutil.ReadFile(filepath) - if err != nil { - return err - } - - for i := 0; i < len(buffer); i++ { - // 0x200 == 512 - c.memory[512+i] = buffer[i] - } - return nil -} - -var fontSet = [80]byte{ - 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 -} - -func (c *chip8) setupInput() { -} - -func (c *chip8) setupGraphics() { - fmt.Println(c.zoom) window, err := sdl.CreateWindow("go-chip8", sdl.WINDOWPOS_UNDEFINED, - sdl.WINDOWPOS_UNDEFINED, w*c.zoom, h*c.zoom, sdl.WINDOW_SHOWN) + sdl.WINDOWPOS_UNDEFINED, int32(w)*zoom, int32(h)*zoom, sdl.WINDOW_SHOWN) if err != nil { panic(err) } - c.renderer, err = sdl.CreateRenderer(window, -1, 0) + renderer, err := sdl.CreateRenderer(window, -1, 0) if err != nil { panic(err) } -} -func (c *chip8) drawGraphics() { - // x := 0 - // y := 0 - // for i := 0; i < len(c.gfx); i++ { - // if i%w == 0 { - // x = 0 - // y++ - // fmt.Println("") - // } - // if c.gfx[i]^1 == 0 { - // c.renderer.DrawPoint(int32(x), int32(y)) - // fmt.Print("x") - // } else { - // fmt.Print(" ") - // } - // x++ - // } - - // for y := 0; y < h; y++ { - // for x := 0; x < w; x++ { - // pixel := c.gfx[y*w+x] - // if pixel != 0 { - // c.renderer.DrawPoint(int32(x), int32(y)) - // } - // } - // } + return SdlEmulator{ + w: w, + h: h, + renderer: renderer, + zoom: zoom, + chip8: c, + } +} - c.renderer.SetDrawColor(0, 0, 0, 1) - c.renderer.Clear() - c.renderer.SetDrawColor(255, 255, 255, 1) - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { - pixel := c.gfx[y*w+x] +func (e *SdlEmulator) drawGraphics() { + e.renderer.SetDrawColor(0, 0, 0, 1) + e.renderer.Clear() + e.renderer.SetDrawColor(255, 255, 255, 1) + for y := 0; y < e.h; y++ { + for x := 0; x < e.w; x++ { + pixel := e.chip8.Gfx[y*e.w+x] if pixel != 0 { - c.renderer.FillRect(&sdl.Rect{ - X: int32(x) * c.zoom, - Y: int32(y) * c.zoom, - W: c.zoom, - H: c.zoom, + e.renderer.FillRect(&sdl.Rect{ + X: int32(x) * e.zoom, + Y: int32(y) * e.zoom, + W: e.zoom, + H: e.zoom, }) } } } - c.renderer.Present() - c.drawFlag = false + e.renderer.Present() + e.chip8.DrawFlag = false } -func (c *chip8) setKeys() { +func (e *SdlEmulator) setKeys() { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch t := event.(type) { case *sdl.QuitEvent: - println("Quit") + fmt.Println("Quit") os.Exit(0) - // running = false - // break case *sdl.KeyboardEvent: switch t.Type { case sdl.KEYDOWN: if keyHex, ok := validKeys[t.Keysym.Sym]; ok { fmt.Println("down", t.Keysym.Sym) - c.key[keyHex] = 1 - fmt.Println(keyHex, c.key[keyHex]) + e.chip8.Key[keyHex] = 1 + fmt.Println(keyHex, e.chip8.Key[keyHex]) } case sdl.KEYUP: if keyHex, ok := validKeys[t.Keysym.Sym]; ok { fmt.Println("up", t.Keysym.Sym) - c.key[keyHex] = 0 - fmt.Println(keyHex, c.key[keyHex]) + e.chip8.Key[keyHex] = 0 + fmt.Println(keyHex, e.chip8.Key[keyHex]) } if t.Keysym.Sym == sdl.K_ESCAPE { fmt.Println("EXIT") @@ -528,22 +125,19 @@ func main() { flag.Parse() - var c chip8 - c.initialize() - c.setupGraphics() - c.setupInput() + emulator := NewSdlEmulator(chip8.W, chip8.H, 8) - err := c.loadGame(*filepath) + err := emulator.chip8.LoadGame(*filepath) if err != nil { panic(err) } for { - c.emulateCycle() - if c.drawFlag { - c.drawGraphics() + emulator.chip8.EmulateCycle() + if emulator.chip8.DrawFlag { + emulator.drawGraphics() } - c.setKeys() - sdl.Delay(100 / 60) + emulator.setKeys() + sdl.Delay(200 / 60) } } diff --git a/roms/TETRIS b/roms/TETRIS new file mode 100644 index 0000000000000000000000000000000000000000..9f5e0874dbd03b5e99d32a1d81fe3cb343904cb4 GIT binary patch literal 494 zcmYk2yGsK>5XNWCX+%VlyR+~?lv`40X#skLaSFO}dqV`E0DV;L4@g-?2y(W#Fx1@3Mc&ZNDkgLrFV^Cm-+M2rg zjM!iiTd~UQN-JPj=OW-q-SKIcNeUgIT_rCjxXUNY#8M`~7%h>@zU5Ei75S|TQ{~eY zGs<{VJTwsmk7WRuU;r5gjH4KbX3PZ3XD-eHy%{*pF6KkV))=4 z`JzkR?q2g_p!I^6O7DgGA{(S6IyO{AmyQH@i$ojd4RavFCM58G9nu!nCcxtj#eU@= zw4d4X3(SKF+M#^Vj^%BF6v*8n(9SpgEx(^=+f_piamh`f#L!@1A(Y5IsX0;I_gJ9ar3bKc*%*)hU?J_db zHpya)flW574=cK`vDvNnWm{kNuoto79NUwy4is_bVa)mx!w{k+p1c1hX*=A&s%{wQRRi1nI9zSDv1 zJPLJiw$)+c()-6IejM1JczHl<1M;gJPYIVr@_U<${9*fTlx&tciEOvEkR3Vx2Doc) z3O)2`Rq?%YRUtWY*g(dLwdaA19X80Jse2cRyD{b%E?soEWt@3AL7PtV1uN2{^5bAUzCT&h3Dh;D&&*7BvD{v<+c;@^XuA) zH6HSE=u=q^X*C5})%ghW!|+LNgx~p!T>I8}=qU^;mivcc7~!vTqn!KhH+2OMYKU>} z7WaCs`!3{t3Co(DMbp%@ov?hq#Kw3RavLshi~)QI9XJ3ujyr|`=#Z-D5Jd}sK?w&y zqN{5F0f1Zu=vWYk&Uj5c4zRSe;x}5nN8?3TeX+>%05LyXLL80nN-d8cpE(0?F!?j$ z;AngSBt=?S04Va?lauMaqw#0;@3!nhP8<7Q{AvA*uxJCI#8**rxvYcnpQh&Kp1_lj z=jJ}2-T%9}d9S^_9bT+&KY#9(U$%OC-(FkWf~^-$lE#%uGJz_j>(EFpM&Xr;xN?2m}F*reTjZFfRof zTXCYrXT%-lj7$_oj4%ggWO!B(SbBMx@jOd1^z9a~JBLFMgmS+Om+1SMA3w7KqYs?a zRDua0R5#2%U2W1K5TL{qrcD9~g_vn27^Zq<`?MN015qPv5^98Xb=nNK{^xX9MFEQ0(9n^cvUT~Vjc{JvVlv!A|lx9~5`cIUbP literal 0 HcmV?d00001 diff --git a/roms/pong.c8 b/roms/pong.c8 new file mode 100755 index 0000000000000000000000000000000000000000..6af8d1e35400592ecbc0f2c816ff454fa0c06b96 GIT binary patch literal 294 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 AB>(^b literal 0 HcmV?d00001 diff --git a/screenshots/s03.png b/screenshots/s03.png new file mode 100644 index 0000000000000000000000000000000000000000..14a792d7807eabeb64c09ae6dc0b9712e4c5a9bf GIT binary patch literal 1160 zcmeAS@N?(olHy`uVBq!ia0y~yU}9llU}WN81Bx_mJ}(BOSc;uILpXq-h9ji|$hG%$ zaSW-L^Y+fc+(Qlm4j1~qC3^PIPbY_r0D9f18 z&CAH3z`?+PneyHV9EdJRXSi{?jq0g&fz?_$h8t$j?|l7hc&u5C@!nK?uGqh!)IH(- zTqXu-CI$y!Mj+_UDO?08^r4#dYC7+J2Uo#DmT_c9E}H!A|O zJveC4a^neEri!ks?|;_+de3|tr#Y(%_r>g0;9-zI2MoOz{L