jsdoでchip8　その5

#概要

jsdoでchip8やってみた。
マウスで操作してみた。

#写真

![image](https://qiita-image-store.s3.amazonaws.com/0/18104/1c0055e5-e2b9-0f0a-38d6-ebf56e337c33.png)


#サンプルコード

```
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);

```

#成果物

http://jsdo.it/ohisama1/yLTo/

以上。