diff --git a/chip8/chip8.go b/chip8/chip8.go index ec62a21..ac0ea4e 100644 --- a/chip8/chip8.go +++ b/chip8/chip8.go @@ -2,6 +2,7 @@ package chip8 import ( "encoding/binary" + "image" "math" ) @@ -24,14 +25,15 @@ const ( ) type Emulator struct { - V [16]uint8 // general registers - I uint16 // address register - SP uint16 // stack pointer - PC uint16 // program counter - DT uint8 // delay timer - ST uint8 // sound timer - Memory [MemorySize]uint8 // 4KB of system RAM - ROM []uint8 // game rom + V [16]uint8 // general registers + I uint16 // address register + SP uint16 // stack pointer + PC uint16 // program counter + DT uint8 // delay timer + ST uint8 // sound timer + Memory [MemorySize]uint8 // 4KB of system RAM + ROM []uint8 // game rom + Display *image.Gray } func NewEmulator() *Emulator { @@ -49,9 +51,11 @@ func (emulator *Emulator) Reset() { emulator.ST = 0 emulator.Memory = [MemorySize]uint8{} copy(emulator.Memory[ProgramAddress:], emulator.ROM) + emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height)) } func (emulator *Emulator) Cycle() { + pc := emulator.PC emulator.DT = uint8(math.Max(0, float64(emulator.DT)-1)) @@ -60,12 +64,19 @@ func (emulator *Emulator) Cycle() { instruction := binary.BigEndian.Uint16(emulator.Memory[emulator.PC:]) nnn := instruction & 0x0FFF - // n := uint8(instruction & 0x000F) + n := uint8(instruction & 0x000F) kk := uint8(instruction & 0x00FF) x := uint8(instruction & 0x0F00 >> 8) y := uint8(instruction & 0x00F0 >> 4) switch instruction & 0xF000 >> 12 { + case 0x0: + switch instruction & 0x00FF { + case 0xE0: // 00E0 - CLS + emulator.ClearScreen() + case 0xEE: // 00EE - RET + emulator.Return() + } case 0x1: // 1nnn - JP addr emulator.Jump(nnn) case 0x2: // 2nnn - CALL addr @@ -106,6 +117,7 @@ func (emulator *Emulator) Cycle() { case 0xB: // Bnnn - JP V0, addr case 0xC: // Cxkk - RND Vx, byte case 0xD: // Dxyn - DRW Vx, Vy, nibble + emulator.Draw(x, y, n) case 0xE: switch instruction & 0x00FF { case 0x9E: // SKP Vx diff --git a/chip8/instructions.go b/chip8/instructions.go index 63e99e0..a8fc463 100644 --- a/chip8/instructions.go +++ b/chip8/instructions.go @@ -1,9 +1,20 @@ package chip8 +import ( + "image" + "image/color" + "image/draw" +) + +// Clear the display. func (emulator *Emulator) ClearScreen() { - // TODO: clear the screen buffer + draw.Draw(emulator.Display, emulator.Display.Bounds(), &image.Uniform{color.Black}, image.Point{}, draw.Src) } +// Return from a subroutine. +// +// The interpreter sets the program counter to the address at the top of the stack, +// then subtracts 1 from the stack pointer. func (emulator *Emulator) Return() { emulator.StackPop() } @@ -128,7 +139,7 @@ func (emulator *Emulator) Sub(x uint8, y uint8) { // If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. // Then Vx is divided by 2. func (emulator *Emulator) ShiftRight(x uint8) { - emulator.V[0xF] = emulator.V[x] & 0b00000001 + emulator.V[0xF] = emulator.V[x] & 0b0000_0001 emulator.V[x] >>= 1 } @@ -146,6 +157,41 @@ func (emulator *Emulator) SubN(x uint8, y uint8) { // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. // Then Vx is multiplied by 2. func (emulator *Emulator) ShiftLeft(x uint8) { - emulator.V[0xF] = emulator.V[x] & 0b10000000 + emulator.V[0xF] = emulator.V[x] & 0b1000_0000 emulator.V[x] <<= 1 } + +// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. +// +// The interpreter reads n bytes from memory, starting at the address stored in I. +// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy). +// Sprites are XORed onto the existing screen. If this causes any pixels to be erased, +// VF is set to 1, otherwise it is set to 0. If the sprite is positioned so part of it +// is outside the coordinates of the display, it wraps around to the opposite side of the screen. +func (emulator *Emulator) Draw(x uint8, y uint8, n uint8) { + width := uint8(8) + height := n + + emulator.V[0xF] = 0x00 // clean collision flag + + for row := uint8(0); row < height; row += 1 { + sprite := emulator.Memory[emulator.I+uint16(row)] + + for col := uint8(0); col < width; col += 1 { + if (sprite & 0b1000_0000) == 0x1 { + px, py := int(emulator.V[x]+col), int(emulator.V[y]+row) + + // check for pixel collision + if emulator.Display.At(px, py) != color.Black { + emulator.V[0xF] = 0x01 // set collision flag + } + + // draw pixel + emulator.Display.Set(px, py, color.White) + } + + // shift the sprite left 1. This will move the next next col/bit of the sprite into the first position. + sprite <<= 1 + } + } +}