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