Skip to content

Instantly share code, notes, and snippets.

@jonas747
Created October 15, 2014 01:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonas747/be46642f69a7270afe8c to your computer and use it in GitHub Desktop.
Save jonas747/be46642f69a7270afe8c to your computer and use it in GitHub Desktop.
package main
import (
"github.com/veandco/go-sdl2/sdl"
"io/ioutil"
"log"
"sync"
"time"
)
var chip8 *Chip8Emu
var (
keys = map[sdl.Keycode]int{
sdl.GetKeyFromName("0"): 0,
sdl.GetKeyFromName("1"): 1,
sdl.GetKeyFromName("2"): 2,
sdl.GetKeyFromName("3"): 3,
sdl.GetKeyFromName("4"): 4,
sdl.GetKeyFromName("5"): 5,
sdl.GetKeyFromName("6"): 6,
sdl.GetKeyFromName("7"): 7,
sdl.GetKeyFromName("8"): 8,
sdl.GetKeyFromName("9"): 9,
sdl.GetKeyFromName("a"): 10,
sdl.GetKeyFromName("b"): 11,
sdl.GetKeyFromName("c"): 12,
sdl.GetKeyFromName("d"): 13,
sdl.GetKeyFromName("e"): 14,
sdl.GetKeyFromName("f"): 15,
}
chip8_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 main() {
window := sdl.CreateWindow("chip 8 emulator", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
640, 320, sdl.WINDOW_SHOWN)
renderer := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
chip8 = new(Chip8Emu)
go chip8.handleEvents()
chip8.init()
chip8.loadGame("games/PONG")
ticker := time.NewTicker(time.Duration(1/60) * time.Second)
for {
<-ticker.C
chip8.cycle()
if chip8.drawFlag {
chip8.draw(renderer)
}
}
}
type Chip8Emu struct {
opcode uint16
memory [4096]byte
V [16]byte // General prupose registers, 16th is "carry flag"
I uint16 // index register
pc uint16 // program counter
gfx [64 * 32]bool
delayTimer uint8
soundTimer uint8
stack [16]uint16
sp uint16 // Stack pointer
key [16]bool
keyMutex sync.Mutex
drawFlag bool
}
func (c *Chip8Emu) init() {
// Initialize registers and memory once
c.pc = 0x200
c.opcode = 0
c.I = 0
c.sp = 0
// Clear stack
c.stack = [16]uint16{}
c.sp = 0
// Clear registers V0-VF
c.V = [16]byte{}
// Clear memory
c.memory = [4096]byte{}
// Clear screen
c.gfx = [64 * 32]bool{}
// Load fontset
for i := 0; i < 80; i++ {
c.memory[i] = chip8_fontset[i]
}
// sReset timers
c.delayTimer = 0
c.soundTimer = 0
}
func (c *Chip8Emu) loadGame(path string) {
file, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
for i := 0; i < len(file); i++ {
c.memory[512+i] = file[i]
}
}
func (c *Chip8Emu) cycle() {
// Fetch Opcode
opcode := uint16(c.memory[c.pc]<<8 | c.memory[c.pc+1])
c.keyMutex.Lock()
defer c.keyMutex.Unlock()
switch opcode & 0xf000 {
case 0x0000:
switch opcode {
case 0x00e0: // Clear the screen
c.gfx = [64 * 32]bool{}
c.drawFlag = true
case 0x0ee: // Return from subroutine
c.pc = c.stack[c.sp-1]
c.sp--
}
case 0x1000: // Jumps to address NNN
address := uint16(opcode & 0x0fff)
c.pc = address
case 0x2000: // Call subtroutine
address := uint16(opcode & 0x0fff)
c.stack[c.sp] = c.pc
c.sp++
c.pc = address
case 0x3000, 0x4000:
vx := c.V[opcode&0x0f00>>8]
nn := byte(opcode & 0x00ff)
if opcode&0xf000 == 0x3000 { // Skips the next instruction if VX equals NN.
if vx == nn {
c.pc += 4
} else {
c.pc += 2
}
} else { // Skips the next instruction if VX doesn't equal NN.
if vx == nn {
c.pc += 2
} else {
c.pc += 4
}
}
case 0x5000: // Skips the next instruction if VX equals VY.
vx := c.V[opcode&0x0f00>>8]
vy := c.V[opcode&0x00f0>>4]
if vx == vy {
c.pc += 4
} else {
c.pc += 2
}
case 0x6000: // Sets vx to nn
nn := opcode & 0x00ff
addr := opcode & 0x0f00 >> 8
c.V[addr] = byte(nn)
c.pc += 2
case 0x7000: // Adds NN to VX.
vxAddr := opcode & 0x0f00 >> 8
vx := c.V[vxAddr]
nn := uint8(opcode & 0x00ff)
vx += nn
c.V[vxAddr] = vx
c.pc += 2
case 0x8000:
vxAddr := opcode & 0x0f00 >> 8
vyAddr := opcode & 0x00f0 >> 4
vx := c.V[vxAddr]
vy := c.V[vyAddr]
switch opcode & 0x000f {
case 0x0: // vx = vy
c.V[vxAddr] = vy
case 0x1: // vx = or
c.V[vxAddr] = vx | vy
case 0x2: // vx = and
c.V[vxAddr] = vx & vy
case 0x3: // vx = xor
c.V[vxAddr] = vx ^ vy
case 0x4: // Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
c.V[vxAddr] += vy
if c.V[vxAddr] > 255 {
c.V[0xf] = 1
t := uint16(vx) + uint16(vy)
c.V[vxAddr] = uint8(t >> 8)
}
case 0x5: // VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
case 0x6: // Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift
case 0x7: // Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
case 0xe: // Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.[2]
}
c.pc += 2
case 0xa000: // ANNN: Sets I to the address NNN
c.I = opcode & 0x0fff
c.pc += 2
case 0xd000: // DXYN Draw sprite
xpos := int(c.V[int(opcode&0x0f00>>8)])
ypos := int(c.V[int(opcode&0x00f0>>4)])
height := int(opcode & 0x000f)
c.V[0xf] = 0
for yline := 0; yline < height; yline++ {
spriteLine := c.memory[int(c.I)+yline]
for xline := 0; xline < 8; xline++ {
masked := spriteLine & 0x80 >> uint8(xline)
if masked != 0 {
index := xpos + xline + ((ypos + yline) * 64)
if c.gfx[index] {
c.V[0xf] = 1
c.gfx[index] = false
} else {
c.gfx[index] = true
}
}
}
}
c.drawFlag = true
c.pc += 2
case 0xe000:
key := uint8(opcode >> 8 & 0x000f)
pressed := c.key[key]
switch opcode & 0x00ff {
case 0x009e: // Skips the next instruction if the key stored in VX is pressed
if pressed {
c.pc += 4
} else {
c.pc += 2
}
case 0x00a1: // Skips the next instruction if the key stored in VX isn't pressed.
if !pressed {
c.pc += 4
} else {
c.pc += 2
}
}
default:
log.Println("Uknown opcode", opcode)
}
if c.delayTimer > 0 {
c.delayTimer++
}
if c.soundTimer > 0 {
if c.soundTimer == 1 {
log.Println("Beep... make your computer actually beep later i guess...")
c.soundTimer--
}
}
}
func (c *Chip8Emu) draw(renderer *sdl.Renderer) {
renderer.Clear()
renderer.SetDrawColor(255, 255, 255, 255)
for x := 0; x < 64; x++ {
for y := 0; y < 32; y++ {
index := x + (y * 64)
if !c.gfx[index] {
continue
}
rx := int32(x * 10)
ry := int32(y * 10)
rect := &sdl.Rect{rx, ry, 10, 10}
renderer.DrawRect(rect)
}
}
}
func (c *Chip8Emu) handleEvents() {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch t := event.(type) {
case *sdl.KeyUpEvent:
kc := t.Keysym.Sym
if rc, ok := keys[kc]; ok {
c.keyMutex.Lock()
c.key[rc] = false
c.keyMutex.Unlock()
}
case *sdl.KeyDownEvent:
kc := t.Keysym.Sym
if rc, ok := keys[kc]; ok {
c.keyMutex.Lock()
c.key[rc] = true
c.keyMutex.Unlock()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment