LoginSignup
0
0

More than 5 years have passed since last update.

概要

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

写真

image

サンプルコード

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

成果物

以上。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0