arnaucube
arnaucube/go-chip8Public Self-hosted repository
go-chip8/main.go
main.goRaw
  1package main
  2
  3import (
  4	"flag"
  5	"fmt"
  6	"io/ioutil"
  7	"math/rand"
  8	"os"
  9
 10	"github.com/veandco/go-sdl2/sdl"
 11)
 12
 13const w = 64
 14const h = 32
 15
 16type chip8 struct {
 17	opcode uint16
 18	memory [4096]byte
 19
 20	// register
 21	v [16]byte
 22
 23	index uint16
 24	pc    uint16
 25
 26	gfx [w * h]byte
 27
 28	delayTimer byte
 29	soundTimer byte
 30
 31	stack [16]uint16
 32	sp    int
 33
 34	key [16]byte
 35
 36	drawFlag bool
 37
 38	// graphics
 39	renderer *sdl.Renderer
 40	zoom     int32
 41}
 42
 43// Initialize registers and memory once
 44func (c *chip8) initialize() {
 45	c.pc = 0x200
 46	c.opcode = 0
 47	c.index = 0
 48	c.sp = 0
 49	c.zoom = 10
 50
 51	for i := 0; i < len(fontSet); i++ {
 52		c.memory[i] = fontSet[i]
 53	}
 54}
 55
 56func (c *chip8) emulateCycle() {
 57	// Fetch Opcode
 58	c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1])
 59	x := byte((c.opcode & 0x0F00) >> 8)
 60	y := byte((c.opcode & 0x00F0) >> 4)
 61	nn := byte(c.opcode & 0x00FF)
 62	nnn := uint16(c.opcode & 0x0FFF)
 63	// fmt.Printf("%X\n", c.opcode)
 64
 65	// Decode Opcode
 66	// https://en.wikipedia.org/wiki/CHIP-8#Opcode_table
 67	// http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml
 68	switch c.opcode & 0xF000 {
 69	case 0x0000:
 70		switch c.opcode & 0x000F {
 71		case 0x0000:
 72			// 00E0 Clear screen
 73			for i := 0; i < len(c.gfx); i++ {
 74				c.gfx[i] = 0
 75			}
 76			c.pc += 2
 77			c.drawFlag = true
 78			break
 79		case 0x000E:
 80			// 00EE Returns from a subroutine
 81			c.sp--
 82			c.pc = c.stack[c.sp]
 83			break
 84		default:
 85			fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode)
 86		}
 87	case 0x1000:
 88		// 1NNN Jumps to address NNN
 89		// c.pc = c.opcode & 0x0FFF
 90		c.pc = nnn
 91		break
 92	case 0x2000:
 93		// 2NNN Calls subroutine at NNN
 94		c.stack[c.sp] = c.pc
 95		c.sp++
 96		// c.pc = c.opcode & 0x0FFF
 97		c.pc = nnn
 98		break
 99	case 0x3000:
100		// 3XNN Skips the next instruction if VX equals NN. (Usually
101		// the next instruction is a jump to skip a code block)
102		if c.v[x] == nn {
103			c.pc += 2
104		}
105		c.pc += 2
106		break
107	case 0x4000:
108		// 4XNN Skips the next instruction if VX doesn't equal NN.
109		// (Usually the next instruction is a jump to skip a code
110		// block)
111		if c.v[x] != nn {
112			c.pc += 2
113		}
114		c.pc += 2
115		break
116	case 0x5000:
117		// 5XY0 Skips the next instruction if VX equals VY. (Usually
118		// the next instruction is a jump to skip a code block)
119		if c.v[x] != c.v[y] {
120			c.pc += 2
121		}
122		c.pc += 2
123		break
124	case 0x6000:
125		// 6XNN Sets VX to NN
126		c.v[x] = nn
127		c.pc += 2
128		break
129	case 0x7000:
130		// 7XNN Adds NN to VX. (Carry flag is not changed)
131		// c.v[c.opcode&0x0F00>>8] += byte(c.opcode)
132		c.v[x] += nn
133		c.pc += 2
134		break
135	case 0x8000:
136		switch c.opcode & 0x000F {
137		case 0x0000:
138			// 0x8XY0 Sets VX to the value of VY
139			c.v[x] = c.v[y]
140			c.pc += 2
141		case 0x0001:
142			// 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation)
143			c.v[x] = (c.v[x] | c.v[y])
144			c.pc += 2
145		case 0x0002:
146			// 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation)
147			c.v[x] = (c.v[x] & c.v[y])
148			c.pc += 2
149		case 0x0003:
150			// 0x8XY3 Sets VX to VX xor VY
151			c.v[x] = (c.v[x] ^ c.v[y])
152			c.pc += 2
153		case 0x0004:
154			// 0x8XY4 Adds VY to VX. VF is set to 1 when there's a
155			// carry, and to 0 when there isn't
156			if c.v[y] > (0xFF - c.v[x]) {
157				c.v[0xF] = 1
158			} else {
159				c.v[0xF] = 0
160			}
161			c.v[x] += c.v[y]
162			c.pc += 2
163			break
164		case 0x0005:
165			// 0x8XY5 VY is subtracted from VX. VF is set to 0 when
166			// there's a borrow, and 1 when there isn't
167			if c.v[x] > c.v[y] {
168				c.v[0xF] = 0x1
169			} else {
170				c.v[0xF] = 0x0
171			}
172			c.v[x] -= c.v[y]
173			c.pc += 2
174		case 0x0006:
175			// 0x8XY6 Stores the least significant bit of VX in VF
176			// and then shifts VX to the right by 1
177			if c.opcode&0x1 >= 1 {
178				c.v[0xF] = 1
179			} else {
180				c.v[0xF] = 0
181			}
182			c.v[x] = c.v[x] >> 1
183			c.pc += 2
184		case 0x0007:
185			// 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when
186			// there's a borrow, and 1 when there isn't
187			if c.v[y] > c.v[x] {
188				c.v[0xF] = 0x1
189			} else {
190				c.v[0xF] = 0x0
191			}
192			c.v[x] = c.v[y] - c.v[x]
193			c.pc += 2
194		case 0x000E:
195			// 0x8XYE Stores the most significant bit of VX in VF
196			// and then shifts VX to the left by 1
197			if c.opcode&0x80 == 0x80 {
198				c.v[0xF] = 1
199			} else {
200				c.v[0xF] = 0
201			}
202			c.v[x] = c.v[x] << 1
203			c.pc += 2
204		default:
205			fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode)
206		}
207	case 0x9000:
208		// 9XY0 Skips the next instruction if VX doesn't equal VY.
209		// (Usually the next instruction is a jump to skip a code
210		// block)
211		if c.v[x] != c.v[y] {
212			c.pc += 2
213		}
214		c.pc += 2
215	case 0xA000:
216		// ANNN set index to NNN position
217		c.index = nnn
218		c.pc += 2
219		break
220	case 0xB000:
221		// BNNN Jumps to the address NNN plus V0
222		c.pc = nnn + uint16(c.v[0])
223	case 0xC000:
224		// CXNN Sets VX to the result of a bitwise and operation on a
225		// random number (Typically: 0 to 255) and NN
226		r := byte(rand.Intn(255))
227		c.v[x] = r & nn
228		c.pc += 2
229	case 0xD000:
230		// DXYN Draws a sprite at coordinate (VX, VY) that has a width
231		// of 8 pixels and a height of N+1 pixels. Each row of 8 pixels
232		// is read as bit-coded starting from memory location I; I
233		// value doesn’t change after the execution of this
234		// instruction. As described above, VF is set to 1 if any
235		// screen pixels are flipped from set to unset when the sprite
236		// is drawn, and to 0 if that doesn’t happen
237
238		height := c.opcode & 0x000F
239
240		var pixel byte
241		c.v[0xF] = 0
242
243		for yline := uint16(0); yline < height; yline++ {
244			pixel = c.memory[c.index+yline]
245			for xline := uint16(0); xline < 8; xline++ {
246				if (pixel & (0x80 >> xline)) != 0 {
247					if c.gfx[(uint16(c.v[x])+xline)+((uint16(c.v[y])+yline)*w)] == 1 {
248						c.v[0xF] = 1
249					}
250					c.gfx[(uint16(c.v[x])+xline)+((uint16(c.v[y])+yline)*w)] ^= 1
251				}
252			}
253
254		}
255
256		c.drawFlag = true
257		c.pc += 2
258		break
259	case 0xE000:
260		switch c.opcode & 0x00FF {
261		case 0x009E:
262			// EX9E Skips the next instruction if the key stored in
263			// VX is pressed. (Usually the next instruction is a
264			// jump to skip a code block)
265			if c.key[c.v[x]] != 0 {
266				c.pc += 2
267			}
268			c.pc += 2
269			break
270		case 0x00A1:
271			// EXA1 Skips the next instruction if the key stored in
272			// VX isn't pressed. (Usually the next instruction is a
273			// jump to skip a code block)
274			if c.key[c.v[x]] != 1 {
275				c.pc += 2
276			}
277			c.pc += 2
278		default:
279			fmt.Printf("Unknown opcode [0xE000]: 0x%X\n", c.opcode)
280		}
281		break
282	case 0xF000:
283		switch c.opcode & 0x00FF {
284		case 0x0007:
285			// FX07 Sets VX to the value of the delay timer
286			c.v[x] = c.delayTimer
287			c.pc += 2
288		case 0x000A:
289			// FX0A A key press is awaited, and then stored in VX.
290			// (Blocking Operation. All instruction halted until
291			// next key event)
292			pressed := false
293			for i := 0; i < 16; i++ {
294				if c.key[i] == 1 {
295					c.v[x] = byte(i)
296					pressed = true
297				}
298			}
299			if !pressed {
300				return
301			}
302			c.pc += 2
303		case 0x0015:
304			// FX15 Sets the delay timer to VX
305			c.delayTimer = c.v[x]
306			c.pc += 2
307		case 0x0018:
308			// FX18 Sets the sound timer to VX
309			c.soundTimer = c.v[x]
310			c.pc += 2
311		case 0x001E:
312			// FX1E Adds VX to I. VF is not affected
313			c.index += uint16(c.v[x])
314			c.pc += 2
315		case 0x0029:
316			// 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
317			c.index = uint16(c.v[x]) * 5
318			c.pc += 2
319			break
320		case 0x0033:
321			c.memory[c.index] = c.v[x] / 100
322			c.memory[c.index+1] = (c.v[x] / 10) % 10
323			c.memory[c.index+2] = (c.v[x] / 100) % 10
324			c.pc += 2
325			break
326		case 0x0055:
327			// FX55 Stores V0 to VX (including VX) in memory
328			// starting at address I. The offset from I is
329			// increased by 1 for each value written, but I itself
330			// is left unmodified
331			for i := uint16(0); i <= uint16(x); i++ {
332				c.memory[c.index+i] = c.v[i]
333			}
334			c.pc += 2
335		case 0x0065:
336			// 0xFX65 Fills V0 to VX (including VX) with values
337			// from memory starting at address I. The offset from I
338			// is increased by 1 for each value written, but I
339			// itself is left unmodified
340			for i := uint16(0); i < uint16(x)+1; i++ {
341				c.v[i] = c.memory[c.index+i]
342			}
343			c.pc += 2
344			break
345		default:
346			fmt.Printf("Unknown opcode [0xF000]: 0x%X\n", c.opcode)
347		}
348		break
349	default:
350		fmt.Printf("Unknown opcode: 0x%X\n", c.opcode)
351	}
352
353	// Update timers
354	if c.delayTimer > 0 {
355		c.delayTimer--
356	}
357	if c.soundTimer > 0 {
358		if c.soundTimer == 1 {
359			fmt.Printf("Beep!\n")
360		}
361		c.soundTimer--
362	}
363}
364
365func (c *chip8) loadGame(filepath string) error {
366	buffer, err := ioutil.ReadFile(filepath)
367	if err != nil {
368		return err
369	}
370
371	for i := 0; i < len(buffer); i++ {
372		// 0x200 == 512
373		c.memory[512+i] = buffer[i]
374	}
375	return nil
376}
377
378var fontSet = [80]byte{
379	0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
380	0x20, 0x60, 0x20, 0x20, 0x70, // 1
381	0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
382	0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
383	0x90, 0x90, 0xF0, 0x10, 0x10, // 4
384	0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
385	0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
386	0xF0, 0x10, 0x20, 0x40, 0x40, // 7
387	0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
388	0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
389	0xF0, 0x90, 0xF0, 0x90, 0x90, // A
390	0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
391	0xF0, 0x80, 0x80, 0x80, 0xF0, // C
392	0xE0, 0x90, 0x90, 0x90, 0xE0, // D
393	0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
394	0xF0, 0x80, 0xF0, 0x80, 0x80, // F
395}
396
397func (c *chip8) setupInput() {
398}
399
400func (c *chip8) setupGraphics() {
401	fmt.Println(c.zoom)
402	window, err := sdl.CreateWindow("go-chip8", sdl.WINDOWPOS_UNDEFINED,
403		sdl.WINDOWPOS_UNDEFINED, w*c.zoom, h*c.zoom, sdl.WINDOW_SHOWN)
404	if err != nil {
405		panic(err)
406	}
407	c.renderer, err = sdl.CreateRenderer(window, -1, 0)
408	if err != nil {
409		panic(err)
410	}
411}
412
413func (c *chip8) drawGraphics() {
414	// x := 0
415	// y := 0
416	// for i := 0; i < len(c.gfx); i++ {
417	//         if i%w == 0 {
418	//                 x = 0
419	//                 y++
420	//                 fmt.Println("")
421	//         }
422	//         if c.gfx[i]^1 == 0 {
423	//                 c.renderer.DrawPoint(int32(x), int32(y))
424	//                 fmt.Print("x")
425	//         } else {
426	//                 fmt.Print(" ")
427	//         }
428	//         x++
429	// }
430
431	// for y := 0; y < h; y++ {
432	//         for x := 0; x < w; x++ {
433	//                 pixel := c.gfx[y*w+x]
434	//                 if pixel != 0 {
435	//                         c.renderer.DrawPoint(int32(x), int32(y))
436	//                 }
437	//         }
438	// }
439
440	c.renderer.SetDrawColor(0, 0, 0, 1)
441	c.renderer.Clear()
442	c.renderer.SetDrawColor(255, 255, 255, 1)
443	for y := 0; y < h; y++ {
444		for x := 0; x < w; x++ {
445			pixel := c.gfx[y*w+x]
446			if pixel != 0 {
447				c.renderer.FillRect(&sdl.Rect{
448					X: int32(x) * c.zoom,
449					Y: int32(y) * c.zoom,
450					W: c.zoom,
451					H: c.zoom,
452				})
453			}
454		}
455	}
456
457	c.renderer.Present()
458	c.drawFlag = false
459}
460
461func (c *chip8) setKeys() {
462	for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
463		switch t := event.(type) {
464		case *sdl.QuitEvent:
465			println("Quit")
466			os.Exit(0)
467			// running = false
468			// break
469		case *sdl.KeyboardEvent:
470			switch t.Type {
471			case sdl.KEYDOWN:
472				if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
473					fmt.Println("down", t.Keysym.Sym)
474					c.key[keyHex] = 1
475					fmt.Println(keyHex, c.key[keyHex])
476				}
477			case sdl.KEYUP:
478				if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
479					fmt.Println("up", t.Keysym.Sym)
480					c.key[keyHex] = 0
481					fmt.Println(keyHex, c.key[keyHex])
482				}
483				if t.Keysym.Sym == sdl.K_ESCAPE {
484					fmt.Println("EXIT")
485					os.Exit(0)
486				}
487			}
488		}
489	}
490
491}
492
493var validKeys = map[sdl.Keycode]byte{
494	sdl.K_0: 0x00,
495	sdl.K_1: 0x01,
496	sdl.K_2: 0x02,
497	sdl.K_3: 0x03,
498	sdl.K_4: 0x04,
499	sdl.K_5: 0x05,
500	sdl.K_6: 0x06,
501	sdl.K_7: 0x07,
502	sdl.K_8: 0x08,
503	sdl.K_9: 0x09,
504	sdl.K_a: 0x0a,
505	sdl.K_b: 0x0b,
506	sdl.K_c: 0x0c,
507	sdl.K_d: 0x0d,
508	sdl.K_e: 0x0e,
509	sdl.K_f: 0x0f,
510}
511
512func main() {
513	filepath := flag.String("file", "file-path", "file path of the input file")
514
515	flag.Parse()
516
517	var c chip8
518	c.initialize()
519	c.setupGraphics()
520	c.setupInput()
521
522	err := c.loadGame(*filepath)
523	if err != nil {
524		panic(err)
525	}
526
527	for {
528		c.emulateCycle()
529		if c.drawFlag {
530			c.drawGraphics()
531		}
532		c.setKeys()
533	}
534}