├── .gitignore ├── README.md ├── index.html ├── roms ├── 15PUZZLE ├── BLINKY ├── BLITZ ├── BRIX ├── CONNECT4 ├── GUESS ├── HIDDEN ├── IBM ├── INVADERS ├── KALEID ├── MAZE ├── MERLIN ├── MISSILE ├── PONG ├── PONG2 ├── PUZZLE ├── SYZYGY ├── TANK ├── TETRIS ├── TICTAC ├── UFO ├── VBRIX ├── VERS └── WIPEOFF └── scripts ├── chip8.js ├── gamepad.js ├── polyfills.js └── renderer.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chip-8 Emulator 2 | ====================== 3 | 4 | This is a simple Chip-8 interpreter written in JavaScript. 5 | 6 | The ROM loader, graphics renderer and input device controller are independent to the emulator. 7 | 8 | [Test it online](https://rawgit.com/alexanderdickson/Chip-8-Emulator/master/index.html) 9 | 10 | [Further reading](http://blog.alexanderdickson.com/javascript-chip-8-emulator) 11 | 12 | License 13 | ------- 14 | 15 | MIT 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chip 8 Emulator 4 | 5 | 6 | 7 | 98 | 99 | 100 | 101 |

Chip-8 Emulator

102 | 103 |
104 |
105 |
106 | 107 | 108 |
109 |
110 |
111 |
112 | 113 | 114 |
115 |
116 |
117 |
118 | 119 | 120 | 121 |
122 |
123 |
124 |
125 | 126 |
127 |
128 |
129 | 130 | 131 | 132 |
133 |
Program
134 |
None
135 |
FPS
136 |
0
137 |
138 | 139 |

Chip-8 uses a hexadecimal input. Use the keys below, or use a gamepad if enabled.

140 | 141 |
    142 |
  1. 1
  2. 143 |
  3. 2
  4. 144 |
  5. 3
  6. 145 |
  7. 4
  8. 146 |
  9. Q
  10. 147 |
  11. W
  12. 148 |
  13. E
  14. 149 |
  15. R
  16. 150 |
  17. A
  18. 151 |
  19. S
  20. 152 |
  21. D
  22. 153 |
  23. F
  24. 154 |
  25. Z
  26. 155 |
  27. X
  28. 156 |
  29. C
  30. 157 |
  31. V
  32. 158 |
159 | 160 |

161 | By Alex Dickson. Based on Cowgod's Chip-8 Technical Reference. 162 |

163 | 164 | 165 | 166 | 167 | 168 | 398 | 399 | 400 | 401 | -------------------------------------------------------------------------------- /roms/15PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/15PUZZLE -------------------------------------------------------------------------------- /roms/BLINKY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/BLINKY -------------------------------------------------------------------------------- /roms/BLITZ: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/BLITZ -------------------------------------------------------------------------------- /roms/BRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/BRIX -------------------------------------------------------------------------------- /roms/CONNECT4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/CONNECT4 -------------------------------------------------------------------------------- /roms/GUESS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/GUESS -------------------------------------------------------------------------------- /roms/HIDDEN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/HIDDEN -------------------------------------------------------------------------------- /roms/IBM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/IBM -------------------------------------------------------------------------------- /roms/INVADERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/INVADERS -------------------------------------------------------------------------------- /roms/KALEID: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/KALEID -------------------------------------------------------------------------------- /roms/MAZE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/MAZE -------------------------------------------------------------------------------- /roms/MERLIN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/MERLIN -------------------------------------------------------------------------------- /roms/MISSILE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/MISSILE -------------------------------------------------------------------------------- /roms/PONG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/PONG -------------------------------------------------------------------------------- /roms/PONG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/PONG2 -------------------------------------------------------------------------------- /roms/PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/PUZZLE -------------------------------------------------------------------------------- /roms/SYZYGY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/SYZYGY -------------------------------------------------------------------------------- /roms/TANK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/TANK -------------------------------------------------------------------------------- /roms/TETRIS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/TETRIS -------------------------------------------------------------------------------- /roms/TICTAC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/TICTAC -------------------------------------------------------------------------------- /roms/UFO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/UFO -------------------------------------------------------------------------------- /roms/VBRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/VBRIX -------------------------------------------------------------------------------- /roms/VERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/VERS -------------------------------------------------------------------------------- /roms/WIPEOFF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderdickson/Chip-8-Emulator/9a217bf640f5be37946de26676e434047c627aeb/roms/WIPEOFF -------------------------------------------------------------------------------- /scripts/chip8.js: -------------------------------------------------------------------------------- 1 | var Chip8 = function () { 2 | 3 | this.displayWidth = 64; 4 | this.displayHeight = 32; 5 | this.display = new Array(this.displayWidth * this.displayHeight); 6 | this.step = null; 7 | this.running = null; 8 | this.renderer = null; 9 | 10 | var memory = new ArrayBuffer(0x1000); 11 | 12 | this.memory = new Uint8Array(memory); 13 | this.v = new Array(16); 14 | this.i = null; 15 | this.stack = new Array(16); 16 | this.sp = null; 17 | this.delayTimer = null; 18 | this.soundTimer = null; 19 | 20 | this.keys = {}; 21 | this.reset(); 22 | 23 | }; 24 | 25 | Chip8.prototype = { 26 | loadProgram: function (program) { 27 | // Load program into memory 28 | for (i = 0; i < program.length; i++) { 29 | this.memory[i + 0x200] = program[i]; 30 | } 31 | }, 32 | 33 | setKey: function(key) { 34 | this.keys[key] = true; 35 | }, 36 | 37 | unsetKey: function(key) { 38 | delete this.keys[key]; 39 | }, 40 | 41 | setKeyState: function(key, depressed) { 42 | this[["unset", "set"][+depressed] + "Key"](key); 43 | }, 44 | 45 | setRenderer: function (renderer) { 46 | this.renderer = renderer; 47 | }, 48 | 49 | getDisplayWidth: function () { 50 | return this.displayWidth; 51 | }, 52 | 53 | getDisplayHeight: function () { 54 | return this.displayHeight; 55 | }, 56 | 57 | setPixel: function(x, y) { 58 | var location, 59 | width = this.getDisplayWidth(), 60 | height = this.getDisplayHeight(); 61 | 62 | // If the pixel exceeds the dimensions, 63 | // wrap it back around. 64 | if (x > width) { 65 | x -= width; 66 | } else if (x < 0) { 67 | x += width; 68 | } 69 | 70 | if (y > height) { 71 | y -= height; 72 | } else if (y < 0) { 73 | y += height; 74 | } 75 | 76 | location = x + (y * width); 77 | 78 | this.display[location] ^= 1; 79 | 80 | return !this.display[location]; 81 | }, 82 | 83 | reset: function () { 84 | 85 | var i; 86 | 87 | // Reset memory. 88 | for (i = 0; i < this.memory.length; i++) { 89 | this.memory[i] = 0; 90 | } 91 | 92 | var hexChars = [ 93 | 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 94 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 95 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 96 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 97 | 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 98 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 99 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 100 | 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 101 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 102 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 103 | 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 104 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 105 | 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 106 | 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 107 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 108 | 0xF0, 0x80, 0xF0, 0x80, 0x80 // F 109 | ]; 110 | 111 | for (i = 0; i < hexChars.length; i++) { 112 | this.memory[i] = hexChars[i]; 113 | } 114 | 115 | 116 | // Reset registers. 117 | for (i = 0; i < this.v.length; i++) { 118 | this.v[i] = 0; 119 | } 120 | 121 | // Reset display. 122 | for (i = 0; i < this.display.length; i++) { 123 | this.display[i] = 0; 124 | } 125 | 126 | // Reset stack pointer, I 127 | this.sp = 0; 128 | this.i = 0; 129 | 130 | // The program counter starts at 0x200, as 131 | // that is the start location of the program. 132 | this.pc = 0x200; 133 | 134 | this.delayTimer = 0; 135 | this.soundTimer = 0; 136 | 137 | this.step = 0; 138 | this.running = false; 139 | 140 | }, 141 | 142 | start: function () { 143 | 144 | var i; 145 | 146 | if (!this.renderer) { 147 | throw new Error("You must specify a renderer."); 148 | } 149 | 150 | this.running = true; 151 | 152 | var self = this; 153 | requestAnimFrame(function me() { 154 | for (var i = 0; i < 10; i++) { 155 | if (self.running) { 156 | self.emulateCycle(); 157 | } 158 | } 159 | 160 | if (self.drawFlag) { 161 | self.renderer.render(self.display); 162 | self.drawFlag = false; 163 | } 164 | 165 | if ( ! (self.step++ % 2)) { 166 | self.handleTimers(); 167 | } 168 | 169 | requestAnimFrame(me); 170 | 171 | }); 172 | 173 | 174 | }, 175 | 176 | stop: function () { 177 | this.running = false; 178 | }, 179 | 180 | handleTimers: function() { 181 | if (this.delayTimer > 0) { 182 | this.delayTimer--; 183 | } 184 | 185 | if (this.soundTimer > 0) { 186 | if (this.soundTimer == 1) { 187 | this.renderer.beep(); 188 | } 189 | this.soundTimer--; 190 | } 191 | }, 192 | 193 | emulateCycle: function () { 194 | var opcode = this.memory[this.pc] << 8 | this.memory[this.pc + 1]; 195 | var x = (opcode & 0x0F00) >> 8; 196 | var y = (opcode & 0x00F0) >> 4; 197 | 198 | this.pc += 2; 199 | 200 | // Check first nibble to determine opcode. 201 | switch (opcode & 0xf000) { 202 | 203 | case 0x0000: 204 | 205 | switch (opcode) { 206 | 207 | // CLS 208 | // OOE0 209 | // CLear the display. 210 | case 0x00E0: 211 | this.renderer.clear(); 212 | for (var i = 0; i < this.display.length; i++) { 213 | this.display[i] = 0; 214 | } 215 | break; 216 | 217 | // RET 218 | // 00EE 219 | // Return from subroutine. 220 | case 0x00EE: 221 | this.pc = this.stack[--this.sp]; 222 | break; 223 | 224 | } 225 | 226 | break; 227 | 228 | // JP addr 229 | // 1nnnn 230 | // Jump to location nnn 231 | case 0x1000: 232 | this.pc = opcode & 0xFFF; 233 | break; 234 | 235 | // CALL addr 236 | // 2nnnn 237 | // Call subroutine at nnnn. 238 | case 0x2000: 239 | this.stack[this.sp] = this.pc; 240 | this.sp++; 241 | this.pc = opcode & 0x0FFF; 242 | break; 243 | 244 | // SE Vx, byte 245 | // 2xkk 246 | // Skip next instruction if vX equals kk. 247 | case 0x3000: 248 | if (this.v[x] === (opcode & 0xFF)) { 249 | this.pc += 2; 250 | } 251 | break; 252 | 253 | // SNE Vx, byte 254 | // 4xkk 255 | // Skip next instruction if vX doesn't equal kk. 256 | case 0x4000: 257 | if (this.v[x] != (opcode & 0x00FF)) { 258 | this.pc += 2; 259 | } 260 | break; 261 | 262 | // SE Vx, Vy 263 | // 5xy0 264 | // Skip next instruction if vX equals vY. 265 | case 0x5000: 266 | if (this.v[x] === this.v[y]) { 267 | this.pc += 2; 268 | } 269 | break; 270 | 271 | // LD Vx, byte 272 | // 6xkk 273 | // Set Vx equal to kk. 274 | case 0x6000: 275 | this.v[x] = opcode & 0xFF; 276 | break; 277 | 278 | // ADD Vx, byte 279 | // 7xkk 280 | // Set Vx equal to Vx + kk. 281 | case 0x7000: 282 | var val = (opcode & 0xFF) + this.v[x] 283 | 284 | if (val > 255) { 285 | val -= 256; 286 | } 287 | 288 | this.v[x] = val; 289 | break; 290 | 291 | case 0x8000: 292 | 293 | switch (opcode & 0x000f) { 294 | 295 | // LD Vx, Vy 296 | // 8xy0 297 | // Stores register Vy in Vx 298 | case 0x0000: 299 | this.v[x] = this.v[y]; 300 | break; 301 | 302 | // OR Vx, Vy 303 | // 8xu1 304 | // Set vX equal to vX OR Vy; 305 | case 0x0001: 306 | this.v[x] |= this.v[y]; 307 | break; 308 | 309 | // AND Vx, Vy 310 | // 8xy2 311 | // Set Vx equal to Vx AMD Vy 312 | case 0x0002: 313 | this.v[x] &= this.v[y]; 314 | break; 315 | 316 | // XOR Vx, Vy 317 | // 8xy3 318 | // Set Vx equal to Vx XOR Vy. 319 | case 0x0003: 320 | this.v[x] ^= this.v[y]; 321 | break; 322 | 323 | // ADD Vx, Vy 324 | // 8xy4 325 | // Set Vx equal to Vx + Vy, set Vf equal to carry. 326 | case 0x0004: 327 | this.v[x] += this.v[y]; 328 | this.v[0xF] = +(this.v[x] > 255); 329 | if (this.v[x] > 255) { 330 | this.v[x] -= 256; 331 | } 332 | break; 333 | 334 | // SUB Vx, Vy 335 | // 8xy5 336 | // Set Vx equal to Vx - Vy, set Vf equal to NOT borrow. 337 | case 0x0005: 338 | this.v[0xF] = +(this.v[x] > this.v[y]); 339 | this.v[x] -= this.v[y]; 340 | if (this.v[x] < 0) { 341 | this.v[x] += 256; 342 | } 343 | break; 344 | 345 | // SHR Vx, Vy 346 | // 8xy6 347 | // Set Vx SHR 1. 348 | case 0x0006: 349 | this.v[0xF] = this.v[x] & 0x1; 350 | this.v[x] >>= 1; 351 | break; 352 | 353 | // SUBN Vx, Vy 354 | // 8xy7 355 | // Set Vx equal to Vy - Vx, set Vf equal to NOT borrow. 356 | case 0x0007: 357 | this.v[0xF] = +(this.v[y] > this.v[x]); 358 | this.v[x] = this.v[y] - this.v[x]; 359 | if (this.v[x] < 0) { 360 | this.v[x] += 256; 361 | } 362 | break; 363 | 364 | 365 | // SHL Vx, Vy 366 | // 8xyE 367 | // Set Vx equal to Vx SHL 1. 368 | case 0x000E: 369 | this.v[0xF] = +(this.v[x] & 0x80); 370 | this.v[x] <<= 1; 371 | if (this.v[x] > 255) { 372 | this.v[x] -= 256; 373 | } 374 | break; 375 | 376 | } 377 | 378 | break; 379 | 380 | // SNE Vx, Vy 381 | // 9xy0 382 | // Skip next instruction if Vx is not equal to Vy. 383 | case 0x9000: 384 | if (this.v[x] != this.v[y]) { 385 | this.pc += 2; 386 | } 387 | break; 388 | 389 | // LD I, addr 390 | // Annn 391 | // Set I equal to nnn. 392 | case 0xA000: 393 | this.i = opcode & 0xFFF; 394 | break; 395 | 396 | // JP V0, addr 397 | // Bnnn 398 | // Jump to location V0 + nnn. 399 | case 0xB000: 400 | this.pc = (opcode & 0xFFF) + this.v[0]; 401 | break; 402 | 403 | // RND Vx, byte 404 | // Cxkk 405 | // Set Vx equal to random byte AND kk. 406 | case 0xC000: 407 | this.v[x] = Math.floor(Math.random() * 0xFF) & (opcode & 0xFF) 408 | break; 409 | 410 | // DRW Vx, Vy, nibble 411 | // Dxyn 412 | // Display n-byte sprite starting at memory location I at (Vx, Vy), set VF equal to collision. 413 | case 0xD000: 414 | this.v[0xF] = 0; 415 | 416 | var height = opcode & 0x000F; 417 | var registerX = this.v[x]; 418 | var registerY = this.v[y]; 419 | var x, y, spr; 420 | 421 | for (y = 0; y < height; y++) { 422 | spr = this.memory[this.i + y]; 423 | for (x = 0; x < 8; x++) { 424 | if ((spr & 0x80) > 0) { 425 | if (this.setPixel(registerX + x, registerY + y)) { 426 | this.v[0xF] = 1; 427 | } 428 | } 429 | spr <<= 1; 430 | } 431 | } 432 | this.drawFlag = true; 433 | 434 | break; 435 | 436 | case 0xE000: 437 | switch (opcode & 0x00FF) { 438 | 439 | // SKP Vx 440 | // Ex9E 441 | // Skip next instruction if the key with the value Vx is pressed. 442 | case 0x009E: 443 | if (this.keys[this.v[x]]) { 444 | this.pc += 2; 445 | } 446 | break; 447 | 448 | // SKNP Vx 449 | // ExA1 450 | // Skip next instruction if the key with the value Vx is NOT pressed. 451 | case 0x00A1: 452 | if (!this.keys[this.v[x]]) { 453 | this.pc += 2; 454 | } 455 | break; 456 | 457 | } 458 | 459 | break; 460 | 461 | case 0xF000: 462 | 463 | switch (opcode & 0x00FF) { 464 | 465 | // LD Vx, DT 466 | // Fx07 467 | // Place value of DT in Vx. 468 | case 0x0007: 469 | this.v[x] = this.delayTimer; 470 | break; 471 | 472 | // LD Vx, K 473 | // Fx0A 474 | // Wait for keypress, then store it in Vx. 475 | case 0x000A: 476 | 477 | var oldKeyDown = this.setKey; 478 | var self = this; 479 | 480 | this.setKey = function(key) { 481 | self.v[x] = key; 482 | 483 | self.setKey = oldKeyDown.bind(self); 484 | self.setKey.apply(self, arguments); 485 | 486 | self.start(); 487 | } 488 | 489 | this.stop(); 490 | return; 491 | 492 | // LD DT, Vx 493 | // Fx15 494 | // DT is set to Vx. 495 | case 0x0015: 496 | this.delayTimer = this.v[x]; 497 | break; 498 | 499 | // LD ST, Vx 500 | // Fx18 501 | // Set sound timer to Vx. 502 | case 0x0018: 503 | this.soundTimer = this.v[x]; 504 | break; 505 | 506 | // ADD I, Vx 507 | // Fx1E 508 | // Set I equal to I + Vx 509 | case 0x001E: 510 | this.i += this.v[x]; 511 | break; 512 | 513 | // LD F, Vx 514 | // Fx29 515 | // Set I equal to location of sprite for digit Vx. 516 | case 0x0029: 517 | // Multiply by number of rows per character. 518 | this.i = this.v[x] * 5; 519 | break; 520 | 521 | // LD B, Vx 522 | // Fx33 523 | // Store BCD representation of Vx in memory location starting at location I. 524 | case 0x0033: 525 | var number = this.v[x], i; 526 | 527 | for (i = 3; i > 0; i--) { 528 | this.memory[this.i + i - 1] = parseInt(number % 10); 529 | number /= 10; 530 | } 531 | break; 532 | 533 | // LD [I], Vx 534 | // Fx55 535 | // Store registers V0 through Vx in memory starting at location I. 536 | case 0x0055: 537 | for (var i = 0; i <= x; i++) { 538 | this.memory[this.i + i] = this.v[i]; 539 | } 540 | break; 541 | 542 | // LD Vx, [I] 543 | // Fx65 544 | // Read registers V0 through Vx from memory starting at location I. 545 | case 0x0065: 546 | for (var i = 0; i <= x; i++) { 547 | this.v[i] = this.memory[this.i + i]; 548 | } 549 | break; 550 | 551 | } 552 | 553 | break; 554 | 555 | default: 556 | throw new Error("Unknown opcode " + opcode.toString(16) + " passed. Terminating."); 557 | } 558 | 559 | } 560 | }; 561 | -------------------------------------------------------------------------------- /scripts/gamepad.js: -------------------------------------------------------------------------------- 1 | // Support gamepads in WebKit. 2 | var GamePad = function(callback) { 3 | if (typeof callback != "function") { 4 | throw Error("Callback must implement [[call]]."); 5 | } 6 | this.callback = callback; 7 | this.running = false; 8 | }; 9 | 10 | GamePad.prototype.start = function() { 11 | if ( ! GamePad.supported) { 12 | return; 13 | } 14 | this.running = true; 15 | this.tick(); 16 | }; 17 | 18 | GamePad.prototype.stop = function() { 19 | this.running = false; 20 | }; 21 | 22 | GamePad.prototype.tick = function() { 23 | if ( ! this.running) { 24 | return; 25 | } 26 | this.callback(navigator.webkitGetGamepads()); 27 | webkitRequestAnimationFrame(this.tick.bind(this)); 28 | 29 | }; 30 | 31 | // Check to see if gamepad is supported. 32 | GamePad.supported = !!navigator.webkitGetGamepads; -------------------------------------------------------------------------------- /scripts/polyfills.js: -------------------------------------------------------------------------------- 1 | window.requestAnimFrame = (function () { 2 | return window.requestAnimationFrame || 3 | window.webkitRequestAnimationFrame || 4 | window.mozRequestAnimationFrame || 5 | window.oRequestAnimationFrame || 6 | window.msRequestAnimationFrame || 7 | function (callback) { 8 | window.setTimeout(callback, 0); 9 | }; 10 | })(); 11 | 12 | Function.prototype.bind = Function.prototype.bind || function(context) { 13 | var boundArgs = [].slice.call(arguments, 1); 14 | var boundFn = this; 15 | 16 | return function() { 17 | return boundFn.apply(context, boundArgs.concat([].slice.call(arguments))); 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /scripts/renderer.js: -------------------------------------------------------------------------------- 1 | var CanvasRenderer = function(canvas, width, height, cellSize, fgColor, bgColor) { 2 | this.ctx = canvas.getContext("2d"); 3 | this.canvas = canvas; 4 | this.width = +width; 5 | this.height = +height; 6 | this.lastRenderedData = []; 7 | this.setCellSize(cellSize); 8 | this.lastDraw = 0; 9 | this.draws = 0; 10 | 11 | this.fgColor = fgColor || "#0f0"; 12 | this.bgColor = bgColor || "transparent"; 13 | 14 | this.audioContext = window.AudioContext && new AudioContext || 15 | window.webkitAudioContext && new webkitAudioContext; 16 | 17 | }; 18 | 19 | CanvasRenderer.prototype = { 20 | 21 | clear: function () { 22 | this.ctx.clearRect(0, 0, this.width * this.cellSize, this.height * this.cellSize); 23 | }, 24 | 25 | render: function (display) { 26 | this.clear(); 27 | this.lastRenderedData = display; 28 | var i, x, y; 29 | for (i = 0; i < display.length; i++) { 30 | x = (i % this.width) * this.cellSize; 31 | y = Math.floor(i / this.width) * this.cellSize; 32 | 33 | this.ctx.fillStyle = [this.bgColor, this.fgColor][display[i]]; 34 | this.ctx.fillRect(x, y, this.cellSize, this.cellSize); 35 | } 36 | 37 | this.draws++; 38 | }, 39 | 40 | beep: function() { 41 | // If Web Audio is supported, we "beep". 42 | // Otherwise, we shake the display. 43 | if (this.audioContext) { 44 | var osc = this.audioContext.createOscillator(); 45 | osc.connect(this.audioContext.destination); 46 | osc.type = "triangle"; 47 | osc.start(); 48 | setTimeout(function() { 49 | osc.stop(); 50 | }, 100); 51 | return; 52 | } 53 | 54 | var times = 5; 55 | var interval = setInterval(function(canvas) { 56 | if ( ! times--) { 57 | clearInterval(interval); 58 | } 59 | 60 | canvas.style.left = times % 2 ? "-3px" : "3px"; 61 | 62 | }, 50, this.canvas); 63 | }, 64 | 65 | setFgColor: function(color) { 66 | this.fgColor = color; 67 | }, 68 | 69 | setCellSize: function(cellSize) { 70 | this.cellSize = +cellSize; 71 | 72 | this.canvas.width = cellSize * this.width; 73 | this.canvas.height = cellSize * this.height; 74 | 75 | this.render(this.lastRenderedData); 76 | }, 77 | 78 | getFps: function() { 79 | 80 | var fps = this.draws / (+new Date - this.lastDraw) * 1000; 81 | if (fps == Infinity) { 82 | return 0; 83 | } 84 | this.draws = 0; 85 | this.lastDraw = +new Date; 86 | return fps; 87 | } 88 | }; --------------------------------------------------------------------------------