Created
October 15, 2014 01:31
-
-
Save jonas747/be46642f69a7270afe8c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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