#概要
jsdoでchip8やってみた。
マウスで操作してみた。
#写真
#サンプルコード
const hx = (opcode) => ('0000' + opcode.toString(16)).slice(-4);
var chip8 = function(ctx) {
this.ctx = ctx;
this.opcode = new Uint16Array(1);
this.memory = new Uint8Array(4096);
this.V = new Uint8Array(16);
this.pc = new Uint16Array(1);
this.I = new Uint16Array(1);
this.gfx = new Uint8Array(64 * 32);
this.delay_timer = new Uint8Array(1);
this.sound_timer = new Uint8Array(1);
this.stack = new Uint16Array(16);
this.sp = new Uint8Array(1);
this.fontset = new Uint8Array([0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70,
0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0, 0x10, 0xF0,
0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0,
0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40,
0xF0, 0x90, 0xF0, 0x90, 0xF0, 0xF0, 0x90, 0xF0, 0x10, 0xF0,
0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0,
0xF0, 0x80, 0x80, 0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0,
0xF0, 0x80, 0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80]);
this.PC_START = 0x200;
this.keypad = new Uint8Array(16).fill(0);
this.initChip8();
};
chip8.prototype.initChip8 = function() {
this.pc[0] = this.PC_START;
this.opcode.fill(0);
this.I.fill(0);
this.sp.fill(0);
this.memory.fill(0);
this.V.fill(0);
this.stack.fill(0);
for (var i = 0; i < 80; i++) this.memory[i] = this.fontset[i];
this.drawFlag = true;
this.delay_timer.fill(0);
this.sound_timer.fill(0);
};
chip8.prototype.NNN = function(op) {
return (op & 0x0fff);
};
chip8.prototype.NN = function(op) {
return (op & 0x00ff);
}
chip8.prototype.N = function(op) {
return (op & 0x000f);
};
chip8.prototype.X = function(op) {
return ((op & 0x0F00) >> 8);
};
chip8.prototype.Y = function(op) {
return ((op & 0x00F0) >> 4);
};
chip8.prototype.L = function(op) {
return (op & 0xf000);
};
chip8.prototype.disp_clear = function() {
gfx.fill(0);
};
chip8.prototype.rand = function() {
return Math.floor(Math.random() * 11);
};
chip8.prototype.inputKey = function(key, n) {
this.keypad[key] = n;
};
chip8.prototype.emulateCycle = function() {
this.opcode = (this.memory[this.pc[0]] << 8) | this.memory[this.pc[0] + 1];
switch (this.L(this.opcode))
{
case 0x0000:
switch (this.opcode & 0x00FF)
{
case 0x00E0:
this.disp_clear();
this.drawFlag = true;
this.pc[0] += 2;
break;
case 0x00EE:
this.sp[0] -= 1;
this.pc[0] = this.stack[this.sp[0]];
this.pc[0] += 2;
break;
default:
//alert(hx(this.opcode));
}
break;
case 0x1000:
this.pc[0] = this.NNN(this.opcode);
break;
case 0x2000:
this.stack[this.sp[0]] = this.pc[0];
this.sp[0] += 1;
this.pc[0] = this.NNN(this.opcode);
break;
case 0x3000:
this.pc[0] += (this.V[this.X(this.opcode)] == this.NN(this.opcode)) ? 4 : 2;
break;
case 0x4000:
this.pc[0] += (this.V[this.X(this.opcode)] != this.NN(this.opcode)) ? 4 : 2;
break;
case 0x5000:
this.pc[0] += (this.V[this.X(this.opcode)] == this.V[this.Y(this.opcode)]) ? 4 : 2;
break;
case 0x6000:
this.V[this.X(this.opcode)] = this.NN(this.opcode);
this.pc[0] += 2;
break;
case 0x7000:
this.V[this.X(this.opcode)] += this.NN(this.opcode);
this.pc[0] += 2;
break;
case 0x8000:
switch (this.N(this.opcode))
{
case 0x0000:
this.V[this.X(this.opcode)] = this.V[this.Y(this.opcode)];
break;
case 0x0001:
this.V[this.X(this.opcode)] |= this.V[this.Y(this.opcode)];
this.V[0xF] = 0;
break;
case 0x0002:
this.V[this.X(this.opcode)] &= this.V[this.Y(this.opcode)];
this.V[0xF] = 0;
break;
case 0x0003:
this.V[this.X(this.opcode)] ^= this.V[this.Y(this.opcode)];
this.V[0xF] = 0;
break;
case 0x0004:
this.V[0xF] = this.V[this.Y(this.opcode)] > (0xFF - this.V[this.X(this.opcode)]) ? 1 : 0;
this.V[this.X(this.opcode)] += this.V[this.Y(this.opcode)];
break;
case 0x0005:
this.V[0xF] = this.V[this.Y(this.opcode)] > this.V[this.X(this.opcode)] ? 0 : 1;
this.V[this.X(this.opcode)] -= this.V[this.Y(this.opcode)];
break;
case 0x0006:
this.V[0xF] = this.V[this.X(this.opcode)] & 0x1;
this.V[this.X(this.opcode)] >>= 0x01;
break;
case 0x0007:
this.V[0xF] = this.V[this.Y(this.opcode)] < this.V[this.X(this.opcode)] ? 0 : 1;
this.V[this.X(this.opcode)] = this.V[this.Y(this.opcode)] - this.V[this.X(this.opcode)];
break;
case 0x000E:
this.V[0xF] = this.V[this.X(this.opcode)] >> 0x7;
this.V[this.X(this.opcode)] <<= 0x01;
break;
default:
alert(hx(this.opcode));
}
this.pc[0] += 2;
break;
case 0x9000:
this.pc[0] += this.V[this.X(this.opcode)] != this.V[this.Y(this.opcode)] ? 4 : 2;
break;
case 0xA000:
this.I[0] = this.NNN(this.opcode);
this.pc[0] += 2;
break;
case 0xB000:
this.pc[0] = this.V[0] + this.NNN(this.opcode);
break;
case 0xC000:
this.V[this.X(this.opcode)] = this.NN(this.rand()) & this.NN(this.opcode);
this.pc[0] += 2;
break;
case 0xD000:
{
let x = this.V[this.X(this.opcode)];
let y = this.V[this.Y(this.opcode)];
let h = this.N(this.opcode);
this.V[0xF] = 0;
for (let yline = 0; yline < h; yline++)
{
let pixel = this.memory[this.I[0] + yline];
for (let xline = 0; xline < 8; xline++)
{
if ((pixel & (0x80 >> xline)) != 0)
{
if (this.gfx[xline + x + ((y + yline) * 64)] == 1)
this.V[0xF] = 1;
this.gfx[xline + x + ((y + yline) * 64)] ^= 1;
}
}
}
this.drawFlag = true;
this.pc[0] += 2;
}
break;
case 0xE000:
switch (this.NN(this.opcode))
{
case 0x009E:
this.pc[0] += this.keypad[this.V[this.X(this.opcode)]] != 0 ? 4 : 2;
break;
case 0x00A1:
this.pc[0] += this.keypad[this.V[this.X(this.opcode)]] == 0 ? 4 : 2;
break;
default:
alert(hx(this.opcode));
}
break;
case 0xF000:
switch (this.NN(this.opcode))
{
case 0x0007:
this.V[this.X(this.opcode)] = this.delay_timer[0];
this.pc[0] += 2;
break;
case 0x000A:
{
var keyPressed0 = false;
for (var i = 0; i < 16; i++)
{
if (this.keypad[i] != 0)
{
this.V[this.X(this.opcode)] = i;
keyPressed0 = true;
}
}
if (keyPressed0) this.pc[0] += 2;
}
break;
case 0x0015:
this.delay_timer[0] = this.V[this.X(this.opcode)];
this.pc[0] += 2;
break;
case 0x0018:
this.sound_timer[0] = this.V[this.X(this.opcode)];
this.pc[0] += 2;
break;
case 0x001E:
this.V[0xF] = ((this.I[0] + this.V[this.X(this.opcode)]) > 0xFFF) ? 1 : 0;
this.I[0] += this.V[this.X(this.opcode)];
this.pc[0] += 2;
break;
case 0x0029:
this.I[0] = this.V[this.X(this.opcode)] * 0x5;
this.pc[0] += 2;
break;
case 0x0033:
{
this.memory[this.I[0] + 0] = this.V[this.X(this.opcode)] / 100;
this.memory[this.I[0] + 1] = (this.V[this.X(this.opcode)] / 10) % 10;
this.memory[this.I[0] + 2] = (this.V[this.X(this.opcode)] % 100) % 10;
this.pc[0] += 2;
}
break;
case 0x0055:
{
for (var i = 0; i <= this.X(this.opcode); i++) this.memory[this.I[0] + i] = this.V[i];
this.I[0] = I[0] + (this.X(this.opcode)) + 1;
this.pc[0] += 2;
}
break;
case 0x0065:
{
for (var i = 0; i <= this.X(this.opcode); i++) this.V[i] = this.memory[this.I[0] + i];
this.I[0] = this.I[0] + (this.X(this.opcode)) + 1;
this.pc[0] += 2;
}
break;
default:
alert(hx(this.opcode));
}
break;
default:
alert(hx(this.opcode));
}
if (this.delay_timer[0] > 0) this.delay_timer[0]--;
if (this.sound_timer[0] > 0)
{
if (this.sound_timer[0] == 1) console.log('BEEP\n');
this.sound_timer[0]--;
}
};
var loop = function() {
vm.emulateCycle();
var scale = 4;
var imageData = vm.ctx.createImageData(64 * scale, 32 * scale);
var data = imageData.data;
for (var i = 0; i < data.length; i += 4)
{
var w = 64 * 4 * scale;
var y = Math.floor(i / w);
var x = i % w / 4;
if (vm.gfx[64 * Math.floor(y / scale) + Math.floor(x / scale)])
{
data[i + 0] = data[i + 1] = data[i + 2] = 220;
}
else
{
data[i + 0] = data[i + 1] = data[i + 2] = 20;
}
data[i + 3] = 255;
}
vm.ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(loop);
};
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "16px 'Courier New'";
var vm = new chip8(ctx);
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://jsrun.it/assets/y/6/t/z/y6tzO', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var dv = new DataView(xhr.response);
for (var i = 0; i < dv.byteLength; i++)
{
vm.memory[vm.PC_START + i] = dv.getUint8(i * Uint8Array.BYTES_PER_ELEMENT, false);
}
loop();
}
xhr.send();
var $panel = $('#panel');
function mouseClick (el, state) {
var $el = $(el),
key = Number($el.data('key'));
if (state)
{
//alert(key + " keyDown");
vm.inputKey(key, 1);
}
else
{
//alert(key + " keyUp");
vm.inputKey(key, 0);
}
$el.toggleClass('click', state);
}
function onKeyDown(e) {
markKey(e, true);
}
function onKeyUp(e) {
markKey(e, false);
}
function onMouseDown() {
mouseClick(this, true);
}
function onMouseUp() {
mouseClick(this, false);
}
$panel.find('.key').mousedown(onMouseDown).mouseup(onMouseUp).mouseleave(onMouseUp);
#成果物
以上。