diff --git a/chip8/chip8.go b/chip8/chip8.go index 78e8b26..37c21ba 100644 --- a/chip8/chip8.go +++ b/chip8/chip8.go @@ -3,12 +3,11 @@ package chip8 import ( "encoding/binary" "image" - "math" ) const ( MemorySize = 4096 // 4KB of memory - InstructionSize = 2 + InstructionSize = 2 // 2 bytes long instructions ) const ( @@ -21,22 +20,31 @@ const ( FPS = 60 ) -type Emulator struct { - V [16]uint8 // general registers - I uint16 // address register - PC uint16 // program counter - DT uint8 // delay timer - ST uint8 // sound timer - Stack *Stack // very simple stack - Memory [MemorySize]uint8 // 4KB of system RAM - ROM []uint8 // game rom - Display *image.Gray // display buffer +type KeyPressed func(key uint8) bool + +type ROM struct { + Name string + Data []byte } -func NewEmulator() *Emulator { +type Emulator struct { + V [16]uint8 // general registers + I uint16 // address register + PC uint16 // program counter + DT uint8 // delay timer + ST uint8 // sound timer + Stack *Stack // very simple stack + Memory [MemorySize]uint8 // 4KB of system RAM + ROM ROM // game rom + Display *image.RGBA // display buffer + KeyPressed KeyPressed // input function +} + +func NewEmulator(keyPressed KeyPressed) *Emulator { emulator := new(Emulator) + emulator.KeyPressed = keyPressed emulator.Stack = NewStack() - emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height)) + emulator.Display = image.NewRGBA(image.Rect(0, 0, Width, Height)) emulator.Reset() return emulator } @@ -54,10 +62,9 @@ func (emulator *Emulator) Reset() { emulator.LoadFont() } -func (emulator *Emulator) LoadROM(rom []uint8) { +func (emulator *Emulator) LoadROM(rom ROM) { emulator.ROM = rom - // load rom on memory - copy(emulator.Memory[ProgramAddress:], emulator.ROM) + copy(emulator.Memory[ProgramAddress:], emulator.ROM.Data) } func (emulator *Emulator) LoadFont() { @@ -81,11 +88,18 @@ func (emulator *Emulator) LoadFont() { }) } -func (emulator *Emulator) Cycle() { - emulator.DT = uint8(math.Max(0, float64(emulator.DT)-1)) - emulator.ST = uint8(math.Max(0, float64(emulator.ST)-1)) +func (emulator *Emulator) UpdateTimers() { + if emulator.DT > 0 { + emulator.DT -= 1 + } + if emulator.ST > 0 { + emulator.ST -= 1 + } +} +func (emulator *Emulator) Cycle() { instruction := binary.BigEndian.Uint16(emulator.Memory[emulator.PC:]) + emulator.PC += InstructionSize nnn := instruction & 0x0FFF n := uint8(instruction & 0x000F) @@ -93,20 +107,18 @@ func (emulator *Emulator) Cycle() { x := uint8(instruction & 0x0F00 >> 8) y := uint8(instruction & 0x00F0 >> 4) - switch instruction & 0xF000 >> 12 { + switch instruction >> 12 & 0xF { case 0x0: - switch instruction & 0x00FF { - case 0xE0: // 00E0 - CLS + switch instruction { + case 0x00E0: // 00E0 - CLS emulator.ClearScreen() - case 0xEE: // 00EE - RET + case 0x00EE: // 00EE - RET emulator.Return() } case 0x1: // 1nnn - JP addr emulator.Jump(nnn) - return // skip PC increment case 0x2: // 2nnn - CALL addr emulator.Call(nnn) - return // skip PC increment case 0x3: // 3xkk - SE Vx, byte emulator.SkipEqualByte(x, kk) case 0x4: // 4xkk - SNE Vx, byte @@ -177,6 +189,4 @@ func (emulator *Emulator) Cycle() { emulator.ReadRegisters(x) } } - - emulator.PC += InstructionSize } diff --git a/chip8/chip8_test.go b/chip8/chip8_test.go index f490828..9aa8ac9 100644 --- a/chip8/chip8_test.go +++ b/chip8/chip8_test.go @@ -8,7 +8,7 @@ import ( ) func TestEmulator_Reset(t *testing.T) { - emulator := chip8.NewEmulator() + emulator := chip8.NewEmulator(nil) emulator.V[0x03] = 0xFF emulator.V[0x0F] = 0xBB diff --git a/chip8/instructions.go b/chip8/instructions.go index 64b0876..0cd3aa6 100644 --- a/chip8/instructions.go +++ b/chip8/instructions.go @@ -4,7 +4,6 @@ import ( "image" "image/color" "image/draw" - _ "image/png" "math/rand" ) @@ -125,7 +124,6 @@ func (emulator *Emulator) Add(x uint8, y uint8) { emulator.V[0xF] = uint8(sum >> 8) } -// SUB Vx, Vy // Set Vx = Vx - Vy, set VF = NOT borrow. // // If Vx > Vy, then VF is set to 1, otherwise 0. @@ -140,7 +138,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] & 0b0000_0001 + emulator.V[0xF] = emulator.V[x] & 0b00000001 emulator.V[x] >>= 1 } @@ -158,7 +156,7 @@ 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] & 0b1000_0000 + emulator.V[0xF] = (emulator.V[x] & 0b10000000) >> 7 emulator.V[x] <<= 1 } @@ -202,29 +200,27 @@ func (emulator *Emulator) Random(x uint8, kk uint8) { // 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) { + x = emulator.V[x] + y = emulator.V[y] 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 yline := uint8(0); yline < height; yline++ { + sprite := emulator.Memory[emulator.I+uint16(yline)] - for col := uint8(0); col < width; col += 1 { - if (sprite & 0b10000000) == 0b10000000 { - px, py := int(emulator.V[x]+col), int(emulator.V[y]+row) + for xline := uint8(0); xline < width; xline++ { + if (sprite & 0b10000000) != 0x00 { + px, py := int(x+xline)%Width, int(y+yline)%Height + color := emulator.Display.PixOffset(px, py) + 1 // color offset: 0:red, 1:green, 2:blue - // check for pixel collision - if emulator.Display.At(px, py) != color.Black { - emulator.V[0xF] = 0x01 // set collision flag + if emulator.Display.Pix[color] != 0x00 { + emulator.V[0xF] = 0x01 // collision } - // draw pixel - emulator.Display.Set(px, py, color.White) + emulator.Display.Pix[color] ^= 0xFF } - - // shift the sprite left 1 - // this will move the next next col/bit of the sprite into the first position sprite <<= 1 } } @@ -235,7 +231,9 @@ func (emulator *Emulator) Draw(x uint8, y uint8, n uint8) { // Checks the keyboard, and if the key corresponding to the value of Vx // is currently in the down position, PC is increased by 2. func (emulator *Emulator) SkipKeyPressed(x uint8) { - // TODO: implement input + if emulator.KeyPressed(emulator.V[x]) { + emulator.PC += InstructionSize + } } // Skip next instruction if key with the value of Vx is not pressed. @@ -243,7 +241,9 @@ func (emulator *Emulator) SkipKeyPressed(x uint8) { // Checks the keyboard, and if the key corresponding to the value of Vx // is currently in the up position, PC is increased by 2. func (emulator *Emulator) SkipKeyNotPressed(x uint8) { - // TODO: implement input + if !emulator.KeyPressed(emulator.V[x]) { + emulator.PC += InstructionSize + } } // Set Vx = delay timer value. @@ -257,7 +257,14 @@ func (emulator *Emulator) ReadDT(x uint8) { // // All execution stops until a key is pressed, then the value of that key is stored in Vx. func (emulator *Emulator) ReadKey(x uint8) { - // TODO: implement input + for { + for key := uint8(0); key < 16; key++ { + if emulator.KeyPressed(key) { + emulator.V[x] = key + return + } + } + } } // Set delay timer = Vx. @@ -286,7 +293,7 @@ func (emulator *Emulator) AddI(x uint8) { // The value of I is set to the location for the hexadecimal sprite // corresponding to the value of Vx. func (emulator *Emulator) SetI(x uint8) { - emulator.I += uint16(emulator.V[x]) * 5 + emulator.I = uint16(emulator.V[x]) * 5 } // Store BCD representation of Vx in memory locations I, I+1, and I+2. @@ -303,16 +310,12 @@ func (emulator *Emulator) LoadBCD(x uint8) { // // The interpreter copies the values of registers V0 through Vx into memory, starting at the address in I. func (emulator *Emulator) StoreRegisters(x uint8) { - for i := uint8(0); i <= x; i++ { - emulator.Memory[emulator.I+uint16(i)] = emulator.V[i] - } + copy(emulator.Memory[emulator.I:], emulator.V[:x+1]) } // Read registers V0 through Vx from memory starting at location I. // // The interpreter reads values from memory starting at location I into registers V0 through Vx. func (emulator *Emulator) ReadRegisters(x uint8) { - for i := uint8(0); i <= x; i++ { - emulator.V[i] = emulator.Memory[emulator.I+uint16(i)] - } + copy(emulator.V[0:], emulator.Memory[emulator.I:emulator.I+uint16(x)+1]) } diff --git a/cmd/chip8/main.go b/cmd/chip8/main.go deleted file mode 100644 index 3b47293..0000000 --- a/cmd/chip8/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "io/ioutil" - "log" - "os" - - "github.com/hajimehoshi/ebiten" - "github.com/tangzero/chip8-emulator/chip8" -) - -const ( - ScreenScale = 20 - Width = chip8.Width * ScreenScale - Height = chip8.Height * ScreenScale -) - -type UI struct { - Emulator *chip8.Emulator -} - -func (ui *UI) Update(screen *ebiten.Image) error { - ui.Emulator.Cycle() - return nil -} - -func (ui *UI) Draw(screen *ebiten.Image) { - frame, err := ebiten.NewImageFromImage(ui.Emulator.Display, ebiten.FilterDefault) - if err != nil { - log.Fatal(err) - } - - op := new(ebiten.DrawImageOptions) - op.GeoM.Scale(ScreenScale, ScreenScale) - - screen.DrawImage(frame, op) -} - -func (ui *UI) Layout(outsideWidth, outsideHeight int) (int, int) { - return Width, Height -} - -func main() { - rom, err := ioutil.ReadFile(os.Args[1]) - if err != nil { - log.Fatal(err) - } - - ui := UI{Emulator: chip8.NewEmulator()} - ui.Emulator.LoadROM(rom) - - ebiten.SetWindowSize(Width, Height) - ebiten.SetWindowTitle("CHIP 8") - if err := ebiten.RunGame(&ui); err != nil { - log.Fatal(err) - } -} diff --git a/go.mod b/go.mod index 1768482..8c4f655 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,22 @@ module github.com/tangzero/chip8-emulator go 1.17 +require ( + github.com/hajimehoshi/ebiten/v2 v2.2.5 + github.com/stretchr/testify v1.7.0 +) + require ( github.com/davecgh/go-spew v1.1.0 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect - github.com/hajimehoshi/ebiten v1.12.12 // indirect - github.com/hajimehoshi/ebiten/v2 v2.2.5 // indirect github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.7.0 // indirect golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 113a7c6..8d674c1 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,24 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/bitmapfont v1.3.0/go.mod h1:/Qb7yVjHYNUV4JdqNkPs6BSZwLjKqkZOMIp6jZD0KgE= github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= -github.com/hajimehoshi/ebiten v1.12.12 h1:JvmF1bXRa+t+/CcLWxrJCRsdjs2GyBYBSiFAfIqDFlI= -github.com/hajimehoshi/ebiten v1.12.12/go.mod h1:1XI25ImVCDPJiXox4h9yK/CvN5sjDYnbF4oZcFzPXHw= github.com/hajimehoshi/ebiten/v2 v2.2.5 h1:i6NdS6pEi5kgfTh+4XAVCVtCXxjTyxzU1cj1oqHWkZQ= github.com/hajimehoshi/ebiten/v2 v2.2.5/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0= -github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= -github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= -github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= -github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -36,48 +26,37 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5 h1:peBP2oZO/xVnGMaWMCyFEI0WENsGj71wx5K12mRELHQ= golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -92,14 +71,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/libretro/libretro.go b/libretro/libretro.go index 23092b2..eff8665 100644 --- a/libretro/libretro.go +++ b/libretro/libretro.go @@ -16,7 +16,7 @@ var emulator *chip8.Emulator //export Initialize func Initialize() { - emulator = chip8.NewEmulator() + emulator = chip8.NewEmulator(nil) } //export Deinitialize diff --git a/main.go b/main.go new file mode 100644 index 0000000..96d6b70 --- /dev/null +++ b/main.go @@ -0,0 +1,107 @@ +package main + +import ( + _ "embed" + "log" + "time" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/tangzero/chip8-emulator/chip8" +) + +//go:embed test_opcode.ch8 +var DefaultROM []byte + +const ( + ScreenScale = 20 + Width = chip8.Width * ScreenScale + Height = chip8.Height * ScreenScale +) + +type State = int + +const ( + LoadingState State = iota + RunningState +) + +// Keyboard Layout +// 1 2 3 C +// 4 5 6 D +// 7 8 9 E +// A 0 B F +var KeyMapping = []ebiten.Key{ + ebiten.KeyX, + ebiten.Key1, + ebiten.Key2, + ebiten.Key3, + ebiten.KeyQ, + ebiten.KeyW, + ebiten.KeyE, + ebiten.KeyA, + ebiten.KeyS, + ebiten.KeyD, + ebiten.KeyZ, + ebiten.KeyC, + ebiten.Key4, + ebiten.KeyR, + ebiten.KeyF, + ebiten.KeyV, +} + +type UI struct { + Emulator *chip8.Emulator + State State +} + +func (ui *UI) Run() { + for { + ui.Emulator.Cycle() + time.Sleep(time.Millisecond * 2) + } +} + +func (ui *UI) Update() error { + switch ui.State { + case LoadingState: + go ui.Run() + ui.State = RunningState + case RunningState: + ui.Emulator.UpdateTimers() + } + return nil +} + +func (ui *UI) Draw(screen *ebiten.Image) { + frame := ebiten.NewImageFromImage(ui.Emulator.Display) + + operation := new(ebiten.DrawImageOptions) + operation.GeoM.Scale(ScreenScale, ScreenScale) + + screen.DrawImage(frame, operation) +} + +func (ui *UI) Layout(outsideWidth, outsideHeight int) (int, int) { + return Width, Height +} + +func KeyPressed(key uint8) bool { + return ebiten.IsKeyPressed(KeyMapping[key]) +} + +func main() { + rom := LoadROM() + + ui := UI{ + Emulator: chip8.NewEmulator(KeyPressed), + State: LoadingState, + } + ui.Emulator.LoadROM(rom) + + ebiten.SetWindowSize(Width, Height) + ebiten.SetWindowTitle("CHIP-8 : " + rom.Name) + + if err := ebiten.RunGame(&ui); err != nil { + log.Fatal(err) + } +} diff --git a/main_desktop.go b/main_desktop.go new file mode 100644 index 0000000..57cc361 --- /dev/null +++ b/main_desktop.go @@ -0,0 +1,29 @@ +//go:build !js + +package main + +import ( + "io/ioutil" + "log" + "os" + "path" + "strings" + + "github.com/tangzero/chip8-emulator/chip8" +) + +func LoadROM() chip8.ROM { + data := DefaultROM + name := "test_opcode" + + if len(os.Args) > 1 { + bytes, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + log.Fatal(err) + } + data = bytes + name = strings.Split(path.Base(os.Args[1]), ".")[0] + } + + return chip8.ROM{Data: data, Name: name} +} diff --git a/main_web.go b/main_web.go new file mode 100644 index 0000000..b26d3fd --- /dev/null +++ b/main_web.go @@ -0,0 +1,11 @@ +//go:build js + +package main + +import "github.com/tangzero/chip8-emulator/chip8" + +func LoadROM() chip8.ROM { + data := DefaultROM + name := "test_opcode" + return chip8.ROM{Data: data, Name: name} +} diff --git a/roms/README.md b/roms/README.md new file mode 100644 index 0000000..8194191 --- /dev/null +++ b/roms/README.md @@ -0,0 +1,38 @@ +# CHIP-8 Public Domain ROMs + +* Chip-8 Games Pack: + * Puzzle + * Blinky + * Blitz + * Brix + * Connect 4 + * Guess + * Hidden + * Invaders + * Kaleid + * Maze + * Merun + * Missle + * Pong + * Pong 2 + * Puzzle + * Syzgy + * Tank + * Tetris + * TicTac + * UFO + * Vbrix + * Wipeoff + +* Super Chip Games Pack: + * Alien + * Ant + * Blinky + * Car + * Field + * Joust + * Piper + * Race + * Spacefig + * Uboat + * Worm3 diff --git a/roms/Space Invaders [David Winter].ch8 b/roms/Space Invaders [David Winter].ch8 deleted file mode 100644 index 3ada8df..0000000 Binary files a/roms/Space Invaders [David Winter].ch8 and /dev/null differ diff --git a/roms/c8-games/15puzzle.ch8 b/roms/c8-games/15puzzle.ch8 new file mode 100644 index 0000000..3ef0bc8 Binary files /dev/null and b/roms/c8-games/15puzzle.ch8 differ diff --git a/roms/c8-games/blinky.ch8 b/roms/c8-games/blinky.ch8 new file mode 100644 index 0000000..235cf98 Binary files /dev/null and b/roms/c8-games/blinky.ch8 differ diff --git a/roms/c8-games/blitz.ch8 b/roms/c8-games/blitz.ch8 new file mode 100644 index 0000000..0d2effa Binary files /dev/null and b/roms/c8-games/blitz.ch8 differ diff --git a/roms/c8-games/brix.ch8 b/roms/c8-games/brix.ch8 new file mode 100644 index 0000000..ad639d9 Binary files /dev/null and b/roms/c8-games/brix.ch8 differ diff --git a/roms/c8-games/connect4.ch8 b/roms/c8-games/connect4.ch8 new file mode 100644 index 0000000..200a67a Binary files /dev/null and b/roms/c8-games/connect4.ch8 differ diff --git a/roms/c8-games/guess.ch8 b/roms/c8-games/guess.ch8 new file mode 100644 index 0000000..36f783d Binary files /dev/null and b/roms/c8-games/guess.ch8 differ diff --git a/roms/c8-games/hidden.ch8 b/roms/c8-games/hidden.ch8 new file mode 100644 index 0000000..bd6b18d Binary files /dev/null and b/roms/c8-games/hidden.ch8 differ diff --git a/roms/c8-games/invaders.ch8 b/roms/c8-games/invaders.ch8 new file mode 100644 index 0000000..f7db5f5 Binary files /dev/null and b/roms/c8-games/invaders.ch8 differ diff --git a/roms/c8-games/kaleid.ch8 b/roms/c8-games/kaleid.ch8 new file mode 100644 index 0000000..a1bc7cc Binary files /dev/null and b/roms/c8-games/kaleid.ch8 differ diff --git a/roms/c8-games/maze.ch8 b/roms/c8-games/maze.ch8 new file mode 100644 index 0000000..152ae7d Binary files /dev/null and b/roms/c8-games/maze.ch8 differ diff --git a/roms/c8-games/merlin.ch8 b/roms/c8-games/merlin.ch8 new file mode 100644 index 0000000..747843a Binary files /dev/null and b/roms/c8-games/merlin.ch8 differ diff --git a/roms/c8-games/missile.ch8 b/roms/c8-games/missile.ch8 new file mode 100644 index 0000000..310e2de Binary files /dev/null and b/roms/c8-games/missile.ch8 differ diff --git a/roms/c8-games/pong.ch8 b/roms/c8-games/pong.ch8 new file mode 100644 index 0000000..e371e91 Binary files /dev/null and b/roms/c8-games/pong.ch8 differ diff --git a/roms/c8-games/pong2.ch8 b/roms/c8-games/pong2.ch8 new file mode 100644 index 0000000..295ce91 Binary files /dev/null and b/roms/c8-games/pong2.ch8 differ diff --git a/roms/c8-games/puzzle.ch8 b/roms/c8-games/puzzle.ch8 new file mode 100644 index 0000000..bec7af0 Binary files /dev/null and b/roms/c8-games/puzzle.ch8 differ diff --git a/roms/c8-games/syzygy.ch8 b/roms/c8-games/syzygy.ch8 new file mode 100644 index 0000000..b0653ef Binary files /dev/null and b/roms/c8-games/syzygy.ch8 differ diff --git a/roms/c8-games/tank.ch8 b/roms/c8-games/tank.ch8 new file mode 100644 index 0000000..ca4bbab Binary files /dev/null and b/roms/c8-games/tank.ch8 differ diff --git a/roms/c8-games/tetris.ch8 b/roms/c8-games/tetris.ch8 new file mode 100644 index 0000000..9f5e087 Binary files /dev/null and b/roms/c8-games/tetris.ch8 differ diff --git a/roms/c8-games/tictac.ch8 b/roms/c8-games/tictac.ch8 new file mode 100644 index 0000000..4d4bc99 Binary files /dev/null and b/roms/c8-games/tictac.ch8 differ diff --git a/roms/c8-games/ufo.ch8 b/roms/c8-games/ufo.ch8 new file mode 100644 index 0000000..7fa5a15 Binary files /dev/null and b/roms/c8-games/ufo.ch8 differ diff --git a/roms/c8-games/vbrix.ch8 b/roms/c8-games/vbrix.ch8 new file mode 100644 index 0000000..07f4006 Binary files /dev/null and b/roms/c8-games/vbrix.ch8 differ diff --git a/roms/c8-games/vers.ch8 b/roms/c8-games/vers.ch8 new file mode 100644 index 0000000..b0fe240 Binary files /dev/null and b/roms/c8-games/vers.ch8 differ diff --git a/roms/c8-games/wipeoff.ch8 b/roms/c8-games/wipeoff.ch8 new file mode 100644 index 0000000..2d5e513 Binary files /dev/null and b/roms/c8-games/wipeoff.ch8 differ diff --git a/roms/sc-games/alien.ch8 b/roms/sc-games/alien.ch8 new file mode 100644 index 0000000..e21c7c4 Binary files /dev/null and b/roms/sc-games/alien.ch8 differ diff --git a/roms/sc-games/ant.ch8 b/roms/sc-games/ant.ch8 new file mode 100644 index 0000000..b140fab Binary files /dev/null and b/roms/sc-games/ant.ch8 differ diff --git a/roms/sc-games/blinky.ch8 b/roms/sc-games/blinky.ch8 new file mode 100644 index 0000000..4d0d4bb Binary files /dev/null and b/roms/sc-games/blinky.ch8 differ diff --git a/roms/sc-games/car.ch8 b/roms/sc-games/car.ch8 new file mode 100644 index 0000000..88b00f5 Binary files /dev/null and b/roms/sc-games/car.ch8 differ diff --git a/roms/sc-games/field.ch8 b/roms/sc-games/field.ch8 new file mode 100644 index 0000000..cea0a97 Binary files /dev/null and b/roms/sc-games/field.ch8 differ diff --git a/roms/sc-games/joust.ch8 b/roms/sc-games/joust.ch8 new file mode 100644 index 0000000..29626c0 Binary files /dev/null and b/roms/sc-games/joust.ch8 differ diff --git a/roms/sc-games/piper.ch8 b/roms/sc-games/piper.ch8 new file mode 100644 index 0000000..b308743 Binary files /dev/null and b/roms/sc-games/piper.ch8 differ diff --git a/roms/sc-games/race.ch8 b/roms/sc-games/race.ch8 new file mode 100644 index 0000000..aedd304 Binary files /dev/null and b/roms/sc-games/race.ch8 differ diff --git a/roms/sc-games/spacefig.ch8 b/roms/sc-games/spacefig.ch8 new file mode 100644 index 0000000..0bdf035 Binary files /dev/null and b/roms/sc-games/spacefig.ch8 differ diff --git a/roms/sc-games/uboat.ch8 b/roms/sc-games/uboat.ch8 new file mode 100644 index 0000000..58c2d84 Binary files /dev/null and b/roms/sc-games/uboat.ch8 differ diff --git a/roms/sc-games/worm3.ch8 b/roms/sc-games/worm3.ch8 new file mode 100644 index 0000000..d316a9d Binary files /dev/null and b/roms/sc-games/worm3.ch8 differ diff --git a/roms/test_opcode.ch8 b/test_opcode.ch8 similarity index 100% rename from roms/test_opcode.ch8 rename to test_opcode.ch8