Чип 8 эмулятор на JavaScript


В последнее время я был заинтересован в эмуляции. Во время моего свободного времени я работал в этот чип 8 эмулятор.

Я считаю, что у меня хороший чип 8 реализации, но мой холст и использовать функции рисования, возможно, потребуется пересмотреть.

Чип 8 Реализации:

var chip8 = function(){
    var version = "1.0.0";

    var chip8_fontset = new Uint8Array(
        [
            0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
          0x20, 0x60, 0x20, 0x20, 0x70, // 1
          0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
          0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
          0x90, 0x90, 0xF0, 0x10, 0x10, // 4
          0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
          0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
          0xF0, 0x10, 0x20, 0x40, 0x40, // 7
          0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
          0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
          0xF0, 0x90, 0xF0, 0x90, 0x90, // A
          0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
          0xF0, 0x80, 0x80, 0x80, 0xF0, // C
          0xE0, 0x90, 0x90, 0x90, 0xE0, // D
          0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
          0xF0, 0x80, 0xF0, 0x80, 0x80  // F
        ]
    );

    var chip8 = {
        initialized: false,
        opcode :  0,
        memory :  0,
        V :  0,
        I :  0,
        pc :  0,
        gfx :  0,
        delay_timer :  0,
        sound_timer :  0,
        stack :  0,
        sp :  0,
        key :  0,
        romSize: 0,
        dFlags: {
            d: false,
            x: 0,
            y: 0,
            w: 0,
            h: 0
        },
        shouldRaisePC : true,
        version: version
    };
    var self = chip8;

    chip8.initialize = function() {
        self.memory = new Uint8Array(4096);
        self.V = new Uint8Array(16);
        self.pc = 0x200;
        self.opcode = 0;
        self.I = 0;
        self.sp = 0;
        self.gfx = new Uint8Array(64 * 32);
        self.stack = new Uint16Array(16);
        self.key = new Uint8Array(16);

        // Clear display
        // Clear stack
        // clear register V0-VF
        // clear memory
        // all those are not needed since we create new arrays for them :)

        // load fontset
        for(var i = 0; i < 80; i++){
            self.memory[i] = chip8_fontset[i];
        }

        self.initialized = true;
    };

    chip8.emulateCycle = function() {
        var s = self;
        var opcode = s.memory[s.pc] << 8 | s.memory[s.pc + 1];
        var i;
        var X = (opcode & 0x0f00) >> 8;
        var Y = (opcode & 0x00f0) >> 4;
        var NNN = opcode & 0x0fff;
        var NN = opcode & 0x00ff;
        var N = opcode & 0x000f;
        var VX = s.V[X];
        var VY = s.V[Y];
        var keycode;
        var keystate;

        if(s.delay_timer > 0)
            --s.delay_timer;

        if(s.sound_timer > 0)
            --s.sound_timer;

        console.log();

        switch(opcode & 0xF000){
            case 0x0000: // multiple things can happen there
                switch(opcode & 0x000F){
                    case 0x0000: // 0x00e0: clears the screen
                        // do something there
                        logOpCode(opcode, "[sc]!!screen cleared!!");
                        for(i = 0; i < s.gfx.length; i++){
                            s.gfx[i] = 0;
                        }
                        s.dFlags.d = true;
                        s.dFlags.x = 0;
                        s.dFlags.y = 0;
                        s.dFlags.w = 64;
                        s.dFlags.h = 32;
                        break;
                    case 0x000e: // 0x00ee: returns from subroutine
                        logOpCode(opcode, "[st]stack decrement!!");
                        console.log("[st]old sp: " + s.sp + " old pc: " + s.pc);


                        s.pc = s.stack[--s.sp];
                        // I've commented this to stop an infinite loop of stack incremente and decrement
                        //s.shouldRaisePC = false;

                        console.log("[st]new sp: " + s.sp + " new pc " + s.pc);
                        break;
                    default:
                        logUnknowOp(opcode, s.pc);
                        break;
                }
                break;

            // 1NNN: jumps to adress NNN
            case 0x1000:
                logOpCode(opcode, "[cf]goto");

                s.pc = NNN;
                s.shouldRaisePC = false;

                console.log("[cf]Jumped to: " + s.pc);
                break;

            //0x2NNN: call subroutine at address NNN
            case 0x2000:
                logOpCode(opcode, "[st]stack increment!!");
                console.log("[st]Stack[" + s.sp +"] = " + s.pc);

                s.stack[s.sp++] = s.pc;
                s.pc = NNN;
                s.shouldRaisePC = false;


                console.log("[st]changed pc: " + s.pc);
                break;

            // 0x3XNN: skips next instruction if VX==NN
            case 0x3000:
                logOpCode(opcode, "[cf]skip VX==NN");
                console.log("[cf]VX: " + VX + " NN: "+ NN);

                if(VX === NN){
                    s.pc+=4;
                    s.shouldRaisePC = false;

                    console.log("[cf]Skiped to pc: " + s.pc);
                }
                break;

            // 0x4XNN: skips next instruction if VX!=NN
            case 0x4000:
                logOpCode(opcode, "[cf]skip !=");
                console.log("[cf]VX: " + VX + " NN: "+ NN);

                if(VX !== NN){
                    s.pc+=4;
                    s.shouldRaisePC = false;

                    console.log("[cf]Skiped to pc: " + s.pc);
                }
                break;

            // 0x5XY0: skips instruction if VX==VY
            case 0x5000:
                logOpCode(opcode, "[cf]skip VX==VY");
                console.log("[cf]VX: " + VX + " VY: "+ VY);

                if(VX === VY){
                    s.pc+=4;
                    s.shouldRaisePC = false;

                    console.log("[cf]Skiped to pc: " + s.pc);
                }
                break;

            // 6XNN: sets VX to NN
            case 0x6000:
                logOpCode(opcode, "[va]vx=nn" );

                s.V[X] = NN;

                console.log("[va]V["+ X+"]="+ NN);
                break;

            // 7XNN: adds to VX NN
            case 0x7000:
                logOpCode(opcode, "[va]vx+=nn" );

                console.log("[va]V["+X+"]="+s.V[X]+" + " + NN);

                s.V[X] += NN;
                break;

            case 0x8000: // multiple thing there
                switch(opcode & 0x000f){

                    // 0x8XY0: assing vx to the value of vy
                    case 0x0000:
                        logOpCode(opcode, "[mt]VX=VY");

                        s.V[X] = VY;

                        console.log("[mt]V["+ X +"]="+ VY);
                        break;

                    // 0x8XY1: sets VX to (VX or VY)
                    case 0x0001:
                        logOpCode(opcode, "[bo]VX=VX|VY");

                        var OR = VX | VY;
                        s.V[X] = OR;

                        console.log("[bo]" + VX.toString(2));
                        console.log("[bo]" + VY.toString(2));
                        console.log("[bo]" + OR.toString(2));
                        break;

                    // 0x8XY2: VX = (VX and VY)
                    case 0x0002:
                        logOpCode(opcode, "[bo]VX=VX&VY");

                        var AND = VX & VY;
                        s.V[X] = AND;

                        console.log("[bo]" + VX.toString(2));
                        console.log("[bo]" + VY.toString(2));
                        console.log("[bo]" + AND.toString(2));
                        break;

                    // 0x8XY2: VX = (VX xor VY)
                    case 0x0003:
                        logOpCode(opcode, "[bo]VX=VX^VY");

                        var XOR = VX ^ VY;
                        s.V[X] = XOR;

                        console.log("[bo]" + VX.toString(2));
                        console.log("[bo]" + VY.toString(2));
                        console.log("[bo]" + XOR.toString(2));
                        break;

                    // 0x8XY4: vx += vy, sets vf to 1 if carry, 0 if not
                    case 0x0004:
                        logOpCode(opcode, "[mt]VX+=VY");
                        if(VY > (0xff - VX)){
                            s.V[0xf] = 1;
                        } else {
                            s.V[0xf] = 0;
                        }
                        s.V[X] += s.V[Y];
                        break;

                    // 0x8XY5: vx -= vy, sets vf to 0 if borrow, 1 if not
                    case 0x0005:
                        logOpCode(opcode, "[mt]VX-=VY");

                        s.V[0xf] = 1;
                        if(VY > VX)
                            s.V[0xf] = 0;

                        s.V[X] -= VY;
                        console.log("[mt]VX: " + VX + " VY: " + VY);
                        break;

                    //8XY6: VF = lsb, vx = vx >> 1
                    case 0x0006:
                        logOpCode(opcode, "[bo]VX = VX>>1");

                        var lsb = VY & 1;

                        s.V[X] = VY >> 1;
                        s.V[0xf]=lsb;

                        console.log("[bo]VX:" + VY + ">>1: " + (VY >> 1).toString(2) + " lsb: " + lsb );
                        break;

                    // 0x8XY7: vx=vy - vx, sets vf to 0 if borrow, 1 if not
                    case 0x0007:
                        logOpCode(opcode, "[mt]VX=VY-VX");

                        s.V[0xf] = 1;
                        if(VX>VY)
                            s.V[0xf] = 0;

                        s.V[X] = VY - VX;
                        console.log("[mt]VY: " + VY + " VX: " + VX);
                        break;

                        //8XYE: VF = msb, vx = VX << 1
                        // GOTTA add a simulation for the quirksss
                    case 0x000E:
                        logOpCode(opcode, "[bo]VX = VX<<1");

                        var msb = VX & 128;

                        s.V[X] = VX << 1;
                        s.V[0xf] = msb;

                        console.log("[bo]VX:" + VX + "<<1: " + (VX << 1).toString(2) + " msb: " + msb );
                        break;

                    default:
                        logUnknowOp(opcode, s.pc);
                        break;
                    }
                break;


            // 9XY0: skips iv VX!=VY
            case 0x9000:
                logOpCode(opcode, "[cf]skip VX!=VY");

                VX = s.V[X];
                VY = s.V[Y];

                if(VX !== VY){
                    s.pc += 4;
                    s.shouldRaisePC = false;

                    console.log("[cf] pc skipped to: " + s.pc);
                }
                break;

            // ANNN: sets I to the address NNN
            case 0xa000:
                // Execute opcode
                logOpCode(opcode, "[mem]I=NNN");
                s.I = opcode & 0x0FFF;
                console.log("[mem]New I: " + s.I);
                break;

            // BNNN: jumps to NNN + V0
            case 0xb000:
                logOpCode(opcode, "[cf]pc=NNN + v0");

                s.pc =  s.V[0x0] + NNN;
                s.shouldRaisePC = false;

                console.log("[cf]new pc: " + s.pc);
                break;

            // CXNN: VX = rand & NN
            case 0xc000:
                logOpCode(opcode, "[rnd]VX = rand & NN");

                var rnd = Math.floor(Math.random() * 256) & NN;
                s.V[X] = rnd;

                console.log("V[" + X +"] = " +rnd);
                break;

            // DXYN draws sprite
            case 0xd000:
                logOpCode(opcode, "[gfx]!!!Fucking draw!!!");
                var xStart = s.V[X];
                var yStart = s.V[Y];
                console.log("[gfx]I: "+ s.I +" x: " + xStart + " y: " + yStart + " h: " + N);
                var pixel;

                s.V[0xF] = 0;
                for(var yline = 0; yline < N; yline++){
                    pixel = s.memory[s.I + yline];

                    for(var xline = 0; xline < 8; xline++){
                        if((pixel & (128 >> xline)) !== 0) {
                            var gfxOffset = xStart + xline +((yStart + yline) * 64);
                            s.gfx[gfxOffset] ^= 1;
                            if(s.gfx[gfxOffset] === 0) s.V[0xf] = 1;
                        }
                    }
                }

                s.dFlags.d = true;
                s.dFlags.x = xStart;
                s.dFlags.y = yStart;
                s.dFlags.w = 8;
                s.dFlags.h = N;

                break;

            // keyboard things
            case 0xe000:
                switch(opcode & 0x00ff){
                    // EX9E: Skips the next instruction
                    // if the key stored in VX is pressed
                    case 0x009e:
                        logOpCode(opcode, "[kb]skip if vx pressed");
                        keycode = s.V[X];
                        keystate = s.key[keycode];

                        console.log("[kb]we are at pc: " + s.pc);
                        console.log("[kb]key["+ keycode +"] : " + keystate);
                        console.log("[kb]we should skip to:" + (s.pc+4));

                        if(keystate!==0){
                            s.pc += 4;
                            s.shouldRaisePC = false;

                            console.log("[kb]so we skiped to pc: " + s.pc);
                        }
                        break;

                    // EXA1: Skips the next instruction
                    // if the key stored in VX is not pressed
                    case 0x00a1:
                        logOpCode(opcode, "[kb]skip if vx not pressed");
                        keycode = s.V[X];
                        keystate = s.key[keycode];

                        console.log("[kb]key["+ keycode +"] : " + keystate);

                        if(keystate===0){
                            s.pc += 4;
                            s.shouldRaisePC = false;

                            console.log("[kb]so we skiped to pc: " + s.pc);
                        }
                        break;

                    default:
                        logUnknowOp(opcode, s.pc);
                        break;
                }
                break;

            // multiple things
            case 0xf000:
                switch(opcode & 0x00ff){
                    // FX07: VX = delay timer
                    case 0x0007:
                        logOpCode(opcode, "[tm]VX = delay");

                        s.V[X] = s.delay_timer;

                        console.log("[tm]V["+X+"] = " +s.delay_timer);
                        break;

                    // FX0A: wait for key press (blocking)
                    case 0x000a:
                        logOpCode(opcode, "[kb]wait for keypress");

                        s.shouldRaisePC = false;
                        for(i = 0; i < s.key.length; i++){
                            if(s.key[i] === 1){
                                s.shouldRaisePC = true;
                                s.V[X] = i;

                                console.log("[kb]found at: " + s.key[i]);
                                break;
                            }
                        }
                        break;

                    // FX15: delay = VX
                    case 0x0015:
                        logOpCode(opcode, "[tm]delay = VX");

                        VX = s.V[X];
                        s.delay_timer = VX;

                        console.log("[tm]delay = " + VX);
                        break;

                    // FX18: sound = VX
                    case 0x0018:
                        logOpCode(opcode, "[tm]sound = VX");

                        VX = s.V[X];
                        s.sound_timer = VX;

                        console.log("[tm]sound = " + VX);
                        break;

                    // FX1E: I += VX
                    case 0x001e:
                        logOpCode(opcode, "[mem]I += VX");

                        VX = s.V[X];
                        s.I += VX;
                        s.V[0xf] = 0;
                        if(s.I > 0xfff)
                            s.V[0xf] = 1;

                        console.log("[mem]now I = " + s.I);
                        break;

                    // FX29: I = to font char of VX 0-f
                    case 0x0029:
                        logOpCode(opcode, "[mem]I = font char");

                        VX = s.V[X];
                        s.I = VX * 5;

                        console.log("[mem]now I = " + s.I);
                        break;

                    // FX33: too long to be described there check the chip8
                    // instruction set.
                    case 0x0033:
                        VX = s.V[X];
                        logOpCode(opcode);
                        s.memory[s.I] = Math.floor(VX / 100);
                        s.memory[s.I+1] = Math.floor(VX / 10) % 10;
                        s.memory[s.I+2] = (VX % 100) % 10;
                        break;

                    // FX55: starting at I = from V0 to VX
                    case 0x0055:
                        logOpCode(opcode, "[mem]starting at I = V0 to VX");

                        for(i = 0; i<=X; i++){
                            s.memory[s.I + i] = s.V[i];
                        }

                        s.I += VX + 1;
                        break;

                    // FX65: starting at I = from V0 to VX
                    case 0x0065:
                        logOpCode(opcode, "[mem]V0 to VX = starting at I");

                        for(i = 0; i<=X; i++){
                            s.V[i] = s.memory[s.I + i];
                        }

                        s.I += VX + 1;
                        break;

                    default:
                        logUnknowOp(opcode, s.pc);
                        break;
                }
                break;

            default:
                logUnknowOp(opcode, s.pc);
                break;
        }

        console.log("old pc:" + s.pc);
        if(s.shouldRaisePC)
            s.pc += 2;
        s.shouldRaisePC = true;
        console.log("new pc:" + s.pc);
    };


    /**
    *
    *
    */
    chip8.loadRom = function() {
        s = self;
        if(!s.initialized) {
            alert("first initialize the instace of chip8");
            return;
        }
        var file = document.getElementById("rom").files;
        console.log("Rom name: " + file[0].name);
        var view;
        if(file.length < 1) {
            alert("Please select a rom file");
            return;
        }

        var reader = new FileReader();
        reader.onload = function(e){
            view = new DataView(e.target.result);
            s.romSize = view.byteLength;
            for(var i = 0; i < view.byteLength; i++){
                s.memory[0x200 + i] = view.getUint8(i);
            }
            console.log("finished rom load to memory, loaded: "+ view.byteLength + " bytes");
        };
        reader.readAsArrayBuffer(file[0]);
    };

    var logOpCode = function(opcode, extra ){
        extra = (typeof extra !== 'undefined') ? extra : "";
        console.log("pc: " + self.pc + " know opcode: " + opcode.toString(16).toUpperCase() + extra);
    };

    var logUnknowOp = function(opcode, pc){
        console.log("Unknown opcode: " + opcode.toString(16).toUpperCase() + " at pc: "+ pc);
    };

    logOpCode = function() {};

    logUnknowOp = function(){};

    return chip8;

};

Основной цикл, холст рисовать и нажатия клавиш:

// some setup
var c8 = chip8();
var ctx;
var control = {
    fps : 120,
    intervalId: 0
};

// key map
// Keypad                   Keyboard
// +-+-+-+-+                +-+-+-+-+
// |1|2|3|C|                |1|2|3|4|
// +-+-+-+-+                +-+-+-+-+
// |4|5|6|D|                |Q|W|E|R|
// +-+-+-+-+       =>       +-+-+-+-+
// |7|8|9|E|                |A|S|D|F|
// +-+-+-+-+                +-+-+-+-+
// |A|0|B|F|                |Z|X|C|V|
// +-+-+-+-+                +-+-+-+-+
var keyMap = {
    49:0x1, 50:0x2, 51:0x3, 52:0xc,
    81:0x4, 87:0x5, 69:0x6, 82:0xd,
    65:0x7, 83:0x8, 68:0x9, 70:0xe,
    90:0xa, 88:0x0, 67:0xb, 86:0xf
};

var keyDown = function(e){
    var keycode = keyMap[e.keyCode];
    if (typeof keycode !== 'undefined') {
        c8.key[keycode] = 1;
    }

};

var keyUp = function(e){
    var keycode = keyMap[e.keyCode];
    if (typeof keycode !== 'undefined') {
        c8.key[keycode] = 0;
    }

};

window.addEventListener("keydown", keyDown, false);
window.addEventListener("keyup", keyUp, false);

// our callback function that handles the udpates
var tick = function(){
    // the keys are event bounded so no need for special code there
    // look for window.addEventListener("keydown", keyDown, false);

    c8.emulateCycle();

    if(c8.dFlags.d)
        requestAnimationFrame(draw.draw);

    if(c8.pc > (c8.romSize + 0x200)){
        console.log("exiting execution since theres no thing to be accesed outside of rom memory");
        clearInterval(control.intervalId);
    }
};

// load the room and starts the emulation
document.getElementById("load").onclick = function(){
    console.clear();
    //Set up render system and register input callbacks
    initGFX();

    // initialize things
    c8.initialize();
    c8.loadRom();

    control.intervalId = setInterval(tick, 1000 / control.fps);

};

// stops emulation
document.getElementById("stop").onclick = function(){
    clearInterval(control.intervalId)
};

// Canvas creation
var initGFX = function() {
    var c = document.getElementById("screen");
    ctx = c.getContext("2d");
    draw.clearScren();
};

// Canvas abstraction
var draw = {};
// draws a point
draw.point = function(x, y, color){
    var scaleFactor = 4;

    ctx.fillStyle = color;
    // in this case 4 cuz ive set the scale of the screen to 4x
    ctx.fillRect(x * scaleFactor,y * scaleFactor, scaleFactor, scaleFactor);
};

// clear the screen
draw.clearScren = function() {
    var scaleFactor = 4;
    ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, 64 * scaleFactor, 32 * scaleFactor)
};

// this is run everytime the draw flag is activated
draw.draw = function() {
    var x = s.dFlags.x;
    var y = s.dFlags.y;
    var w = s.dFlags.w;
    var h = s.dFlags.h;
    var color;
    for(var i = 0; i < w; i++){
        for(var j = 0; j < h; j++){
            var offset = x + i +((y + j) * 64);
            if(c8.gfx[offset] === 1){
                color = "#000";
            } else {
                color = "#fff";
            }
            draw.point(x + i, y + j, color);
        }
    }
    s.dFlags.d = false;
};

//translates to uint into an opcode number
var t2Uint = function(a, b){
    return (a << 8) | b;
};

//checks the opcode at certain memory spot
var g2mUint = function(start){
    return t2Uint(c8.memory[start], c8.memory[start + 1]);
};

//gets the uint at certain memoery spot
var g1mUint = function(start){
    return c8.memory[start];
};

//checks the sprite at certain memory spot
var gsprite = function(start, height){
    for(var i = 0; i < height; i++){
        console.log( g1mUint(start + (i*2) ).toString(2) );
    }
};

HTML-код

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <input type="file" id="rom" name="rom" />
        <a id="load" href="#">Load Rom</a>
        <a id="stop" href="#">Stop</a>
        <br />
        <canvas id="screen" width="256" height="128" style="border: 1px solid black;"></canvas>

        <script src="chip8.js"></script>
        <script src="main.js"></script>
    </body>
</html>


394
8
задан 31 марта 2018 в 02:03 Источник Поделиться
Комментарии
1 ответ

Первое, что я заметил, было то, что свой (по умолчанию) с рейтами кажется крайне низкой, и я думаю, одна из вещей, которые вызвала эта (из-за возможной путаницы больше ничего), был тот факт, что переменные, влияющие на нее называлось fps:

var control = {
fps : 120, // <---
intervalId: 0
};

С рейтами чип-8 не напрямую влияют на частоту кадров, ведь кадры действовать немного по-другому, чем обычные в том, что не все игры будут обязательно четкое и перерисовки экрана. Пожалуй, лучшего имени для этого было бы oprate или tickrate а может быть стоит увеличивать его, чтобы получить более играбельный опыт.


Что-то я менее уверен, так как я не внедрили чип-эмулятор 8 в некоторое время, что это не правильно 100%, хотя некоторые игры, конечно, играть.

Насколько это работает, я выбрал несколько игр, чтобы увидеть, как они побежали, я не имел никаких проблем с:


  • 15PUZZLE

  • Блиц

Следующие не работает, как ожидалось:


  • И connect4 (жетоны не приземлиться на верхней части друг друга, когда-либо был использован только нижний ряд)

  • Скрытая (большинство созданных плитки появились в результате иксы какие-то странные игры и в конце концов он не завершенный, так как 2 оставшиеся плитки не совпадает, когда переворачивается)
    enter image description here

Однако мне не повезло выполняется:


  • Блинки (иногда сердце нарисуйте в верхнем углу, но ничего больше)

Это означает, что несколько ваших опкодов может быть неправильно функционирует (хотя это может быть более серьезная проблема), может быть стоит работает эмулятор вместе с другой, чтобы увидеть, как они соотносятся.

1
ответ дан 2 апреля 2018 в 01:04 Источник Поделиться