Browse Source

Separate chip8 & sdl pkgs, update readme, add roms

master
arnaucube 4 years ago
parent
commit
6e0e034dfa
14 changed files with 460 additions and 456 deletions
  1. +11
    -3
      README.md
  2. +395
    -0
      chip8/chip8.go
  3. +5
    -0
      go.mod
  4. +2
    -0
      go.sum
  5. +47
    -453
      main.go
  6. BIN
      roms/TETRIS
  7. BIN
      roms/chip8-picture.ch8
  8. BIN
      roms/invaders.c8
  9. BIN
      roms/pong.c8
  10. BIN
      roms/slipperyslope.ch8
  11. BIN
      screenshots/s00.png
  12. BIN
      screenshots/s01.png
  13. BIN
      screenshots/s02.png
  14. BIN
      screenshots/s03.png

+ 11
- 3
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. CHIP-8 emulator written in Go.
[WIP]
https://en.wikipedia.org/wiki/CHIP-8 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)

+ 395
- 0
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
}

+ 5
- 0
go.mod

@ -0,0 +1,5 @@
module go-chip8
go 1.14
require github.com/veandco/go-sdl2 v0.4.4

+ 2
- 0
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=

+ 47
- 453
main.go

@ -3,489 +3,86 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"math/rand"
"go-chip8/chip8"
"os" "os"
"github.com/veandco/go-sdl2/sdl" "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 renderer *sdl.Renderer
zoom int32 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, 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 { if err != nil {
panic(err) panic(err)
} }
c.renderer, err = sdl.CreateRenderer(window, -1, 0)
renderer, err := sdl.CreateRenderer(window, -1, 0)
if err != nil { if err != nil {
panic(err) 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 { 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() { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch t := event.(type) { switch t := event.(type) {
case *sdl.QuitEvent: case *sdl.QuitEvent:
println("Quit")
fmt.Println("Quit")
os.Exit(0) os.Exit(0)
// running = false
// break
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
switch t.Type { switch t.Type {
case sdl.KEYDOWN: case sdl.KEYDOWN:
if keyHex, ok := validKeys[t.Keysym.Sym]; ok { if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
fmt.Println("down", t.Keysym.Sym) 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: case sdl.KEYUP:
if keyHex, ok := validKeys[t.Keysym.Sym]; ok { if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
fmt.Println("up", t.Keysym.Sym) 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 { if t.Keysym.Sym == sdl.K_ESCAPE {
fmt.Println("EXIT") fmt.Println("EXIT")
@ -528,22 +125,19 @@ func main() {
flag.Parse() 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 { if err != nil {
panic(err) panic(err)
} }
for { 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)
} }
} }

BIN
roms/TETRIS


BIN
roms/chip8-picture.ch8


BIN
roms/invaders.c8


BIN
roms/pong.c8


BIN
roms/slipperyslope.ch8


BIN
screenshots/s00.png

Before After
Width: 520  |  Height: 280  |  Size: 2.6 KiB

BIN
screenshots/s01.png

Before After
Width: 521  |  Height: 282  |  Size: 2.2 KiB

BIN
screenshots/s02.png

Before After
Width: 516  |  Height: 258  |  Size: 1.7 KiB

BIN
screenshots/s03.png

Before After
Width: 516  |  Height: 258  |  Size: 1.1 KiB

Loading…
Cancel
Save