/*
* CHIP8 EMULATOR
* The aim of this world is to be an environment as OpenAI is.
* Here are some of the Chip8 games : http://www.zophar.net/pdroms/chip8/chip-8-games-pack.html
* It means people can start programming AI on those game without having to make the game first.
*/
// Taken from Mozilla's Javascript documentation
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function Chip8(game_binary) {
this.memory = new Uint8Array(4096).fill(0);
this.v = new Int8Array(16).fill(0); // registers
this.vi = 0;
this.vdelay = 0; // delay timer register
this.vsound = 0; // sound timer register
this.pc = 0x200; // program counter
this.sp = 0; // stack pointer
this.stack = new Int16Array(16).fill(0);
this.screen = new Array(64 * 32).fill(0);
this.keys = new Array(16).fill(false);
this.score = 0;
this.wait_keypress = false;
this.set_key_in_register = null;
const sprites = [
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
];
var i = 0;
for(i = 0; i < sprites.length; ++i)
this.memory[i] = sprites[i];
for(i = 0; i < game_binary.length; ++i)
this.memory[0x200 + i] = game_binary[i];
}
Chip8.prototype.set_key = function(key) {
this.keys[key] = true;
if(this.wait_keypress)
this.set_key_in_register(key);
};
Chip8.prototype.perform_cycle = function() {
const opcode = ((this.memory[this.pc] << 8) | this.memory[this.pc + 1]) & 0xFFFF;
console.log('OPCODE ' + opcode.toString(16));
this.pc += 2;
switch((opcode >> 12) & 0xF)
{
case 0x0:
switch(opcode & 0xFFF)
{
case 0x0E0:
// clear threejs ?
this.screen.fill(0);
break;
case 0x0EE:
this.pc = this.stack[--this.sp]; // ?????????, --this ?
break;
default: // 0nnn ignored by modern emulators
break;
}
break;
case 0x1:
this.pc = opcode & 0xFFF;
break;
case 0x2:
this.stack[this.sp++] = this.pc;//++ti
this.pc = opcode & 0xFFF;
break;
case 0x3:
if(this.v[(opcode >> 8) & 0xF] === (opcode & 0xFF))
this.pc += 2;
break;
case 0x4:
if(this.v[(opcode >> 8) & 0xF] !== (opcode & 0xFF))
this.pc += 2;
break;
case 0x5:
if(this.v[(opcode >> 8) & 0xF] === this.v[(opcode >> 4) & 0xF])
this.pc += 2;
break;
case 0x6:
this.v[(opcode >> 8) & 0xF] = opcode & 0xFF;
break;
case 0x7:
this.v[(opcode >> 8) & 0xF] = (this.v[(opcode >> 8) & 0xF] + opcode & 0xFF) % 256;
break;
case 0x8:
let x = (opcode >> 8) & 0xF;
let y = (opcode >> 4) & 0xF;
switch(opcode & 0xF)
{
case 0x0:
this.v[x] = this.v[y];
break;
case 0x1:
this.v[x] |= this.v[y];
break;
case 0x2:
this.v[x] &= this.v[y];
break;
case 0x3:
this.v[x] ^= this.v[y];
break;
case 0x4:
const res = (this.v[x] + this.v[y]) % 256;
if(res > 0xFF)
this.v[0xF] = 1;
else
this.v[0xF] = 0;
this.v[x] = res & 0xFF;
break;
case 0x5:
if(this.v[x] > this.v[y])
this.v[0xF] = 1;
else
this.v[0xF] = 0;
this.v[x] = (this.v[x] - this.v[y]) % 256;
break;
case 0x6:
this.v[0xF] = this.v[x] & 0x1;
this.v[x] >>= 1;
break;
case 0x7:
if(this.v[y] > this.v[x])
this.v[0xF] = 1;
else
this.v[0xF] = 0;
this.v[x] = (this.v[y] - this.v[x]) % 256;
break;
case 0xE:
this.v[0xF] = (this.v[x] >> 7) & 0x1;
this.v[x] = (this.v[x] << 1) % 256;
break;
default: //error
break;
}
break;
case 0x9:
if((opcode & 0xF) === 0)
{
let x = (opcode >> 8) & 0xF;
let y = (opcode >> 4) & 0xF;
if(this.v[x] !== this.v[y])
this.pc += 2;
}
else
{ko;} // error
break;
case 0xA:
this.vi = opcode & 0xFFF;
break;
case 0xB:
this.pc = (opcode & 0xFFF) + this.v[0x0];
break;
case 0xC:
this.v[(opcode >> 8)] = getRandomInt(0, 255) & (opcode & 0xFF);
break;
case 0xD:
let dx = (opcode >> 8) & 0xF;
let dy = (opcode >> 4) & 0xF;
this.v[0xF] = 0;
// @TODO
for(var j = 0; j < (opcode & 0xF); ++j)
{
for(var i = 0; i < 8; ++i)
{
let pxloc = (this.v[dx] + i) % 64 + ((this.v[dy] + j) % 32) * 64; // ???
if((this.screen[pxloc] ^ ((this.memory[this.vi + j] >> (7 - i)) & 0x1)) === this.screen[pxloc])
this.v[0xF] = 1;
this.screen[pxloc] ^= ((this.memory[this.vi + j] >> (7 - i)) & 0x1);
}
}
break;
case 0xE:
switch(opcode & 0xFF)
{
case 0x9E:
if(this.keys[this.v[(opcode >> 8) & 0xF]])
this.pc += 2;
break;
case 0xA1:
if(!this.keys[this.v[(opcode >> 8) & 0xF]])
this.pc += 2;
break;
default:
break; // error
}
break;
case 0xF:
switch(opcode & 0xFF)
{
case 0x07:
this.v[(opcode >> 8) & 0xF] = this.vdelay;
break;
case 0x0A:
this.wait_keypress = true;
this.set_key_in_register = function(key) {
this.v[(opcode >> 8) & 0xF] = key;
this.set_key_in_register = null;
this.wait_keypress = false;
}.bind(this);
break;
case 0x15:
this.vdelay = this.v[(opcode >> 8) & 0xF];
break;
case 0x18:
this.vsound = this.v[(opcode >> 8) & 0xF];
break;
case 0x1E:
this.vi += this.v[(opcode >> 8) & 0xF];
break;
case 0x29:
this.vi = this.v[(opcode >> 8) & 0xF] * 5;
break;
case 0x33:
let n = this.v[(opcode >> 8) & 0xF];
this.score = n; // heuristic
for(var k = 2; k >= 0; --k)
{
this.memory[this.vi + k] = n % 10;
n /= 10;
}
break;
case 0x55:
for(var i = 0; i <= ((opcode >> 8) & 0xF); ++i)
this.memory[this.vi + i] = this.v[i];
break;
case 0x65:
for(var i = 0; i <= ((opcode >> 8) & 0xF); ++i)
this.v[i] = this.memory[this.vi + i];
break;
default:
break; // error
}
break;
default:
break;
}
if(this.vdelay > 0)
--this.vdelay;
if(this.vsound > 0)
--this.vsound;
};
function get_request(url, response_type, callback) {
let req = new XMLHttpRequest();
req.open('GET', url, false); // sync request
req.responseType = response_type;
req.onreadystatechange = function() {
if(req.status === 200)
callback(this.response);
}
req.send();
}
const CLOCKTICK = 10; // Speed of run: Step every n milliseconds.
const MAXSTEPS = 100000; // Length of run: Maximum length of run in steps.
function World() {
this.endCondition = false;
this.chip8 = null;
this.three_screen = [];
}
World.prototype.init_screen = function() {
for(var j = 0; j < 32; ++j)
for(var i = 0; i < 64; ++i)
{
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshBasicMaterial({ color : 0xFFFFFF});
let cube = new THREE.Mesh(geometry, material);
cube.position.z = j - 16;
cube.position.x = i - 32;
threeworld.scene.add(cube);
this.three_screen.push(cube);
}
};
World.prototype.newRun = function() {
this.endCondition = false;
if(true)
{
threeworld.init3d(2,50, 0x5BA27F);
this.init_screen();
}
/*get_request('/uploads/eyyub/pong.foo', 'arraybuffer', function(game_binary) {
this.chip8 = new Chip8(game_binary);
}.bind(this));*/ // upload dont work actually
this.chip8 = new Chip8
/* MISSILE */ //([18, 25, 77, 73, 83, 83, 73, 76, 69, 32, 98, 121, 32, 68, 97, 118, 105, 100, 32, 87, 73, 78, 84, 69, 82, 108, 12, 96, 0, 97, 0, 101, 8, 102, 10, 103, 0, 110, 1, 162, 173, 208, 20, 112, 8, 48, 64, 18, 41, 96, 0, 97, 28, 162, 176, 208, 20, 162, 176, 208, 20, 62, 1, 18, 73, 112, 4, 64, 56, 110, 0, 18, 79, 112, 252, 64, 0, 110, 1, 208, 20, 252, 21, 251, 7, 59, 0, 18, 83, 98, 8, 226, 158, 18, 149, 60, 0, 124, 254, 99, 27, 130, 0, 162, 176, 210, 49, 100, 0, 210, 49, 115, 255, 210, 49, 63, 0, 100, 1, 51, 3, 18, 109, 210, 49, 52, 1, 18, 145, 119, 5, 117, 255, 130, 0, 99, 0, 162, 173, 210, 52, 69, 0, 18, 151, 118, 255, 54, 0, 18, 57, 162, 180, 247, 51, 242, 101, 99, 27, 100, 13, 241, 41, 211, 69, 115, 5, 242, 41, 211, 69, 18, 171, 16, 56, 56, 16, 56, 124, 254]);
/* PONG */ ([106, 2, 107, 12, 108, 63, 109, 12, 162, 234, 218, 182, 220, 214, 110, 0, 34, 212, 102, 3, 104, 2, 96, 96, 240, 21, 240, 7, 48, 0, 18, 26, 199, 23, 119, 8, 105, 255, 162, 240, 214, 113, 162, 234, 218, 182, 220, 214, 96, 1, 224, 161, 123, 254, 96, 4, 224, 161, 123, 2, 96, 31, 139, 2, 218, 182, 96, 12, 224, 161, 125, 254, 96, 13, 224, 161, 125, 2, 96, 31, 141, 2, 220, 214, 162, 240, 214, 113, 134, 132, 135, 148, 96, 63, 134, 2, 97, 31, 135, 18, 70, 2, 18, 120, 70, 63, 18, 130, 71, 31, 105, 255, 71, 0, 105, 1, 214, 113, 18, 42, 104, 2, 99, 1, 128, 112, 128, 181, 18, 138, 104, 254, 99, 10, 128, 112, 128, 213, 63, 1, 18, 162, 97, 2, 128, 21, 63, 1, 18, 186, 128, 21, 63, 1, 18, 200, 128, 21, 63, 1, 18, 194, 96, 32, 240, 24, 34, 212, 142, 52, 34, 212, 102, 62, 51, 1, 102, 3, 104, 254, 51, 1, 104, 2, 18, 22, 121, 255, 73, 254, 105, 255, 18, 200, 121, 1, 73, 2, 105, 1, 96, 4, 240, 24, 118, 1, 70, 64, 118, 254, 18, 108, 162, 242, 254, 51, 242, 101, 241, 41, 100, 20, 101, 0, 212, 85, 116, 21, 242, 41, 212, 85, 0, 238, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0])
/* SPACE INVADER */ //([18, 37, 83, 80, 65, 67, 69, 32, 73, 78, 86, 65, 68, 69, 82, 83, 32, 118, 48, 46, 57, 32, 66, 121, 32, 68, 97, 118, 105, 100, 32, 87, 73, 78, 84, 69, 82, 96, 0, 97, 0, 98, 8, 163, 211, 208, 24, 113, 8, 242, 30, 49, 32, 18, 45, 112, 8, 97, 0, 48, 64, 18, 45, 105, 5, 108, 21, 110, 0, 35, 135, 96, 10, 240, 21, 240, 7, 48, 0, 18, 75, 35, 135, 126, 1, 18, 69, 102, 0, 104, 28, 105, 0, 106, 4, 107, 10, 108, 4, 109, 60, 110, 15, 0, 224, 35, 107, 35, 71, 253, 21, 96, 4, 224, 158, 18, 125, 35, 107, 56, 0, 120, 255, 35, 107, 96, 6, 224, 158, 18, 139, 35, 107, 56, 57, 120, 1, 35, 107, 54, 0, 18, 159, 96, 5, 224, 158, 18, 233, 102, 1, 101, 27, 132, 128, 163, 207, 212, 81, 163, 207, 212, 81, 117, 255, 53, 255, 18, 173, 102, 0, 18, 233, 212, 81, 63, 1, 18, 233, 212, 81, 102, 0, 131, 64, 115, 3, 131, 181, 98, 248, 131, 34, 98, 8, 51, 0, 18, 201, 35, 115, 130, 6, 67, 8, 18, 211, 51, 16, 18, 213, 35, 115, 130, 6, 51, 24, 18, 221, 35, 115, 130, 6, 67, 32, 18, 231, 51, 40, 18, 233, 35, 115, 62, 0, 19, 7, 121, 6, 73, 24, 105, 0, 106, 4, 107, 10, 108, 4, 125, 244, 110, 15, 0, 224, 35, 71, 35, 107, 253, 21, 18, 111, 247, 7, 55, 0, 18, 111, 253, 21, 35, 71, 139, 164, 59, 18, 19, 27, 124, 2, 106, 252, 59, 2, 19, 35, 124, 2, 106, 4, 35, 71, 60, 24, 18, 111, 0, 224, 164, 211, 96, 20, 97, 8, 98, 15, 208, 31, 112, 8, 242, 30, 48, 44, 19, 51, 240, 10, 0, 224, 166, 244, 254, 101, 18, 37, 163, 183, 249, 30, 97, 8, 35, 95, 129, 6, 35, 95, 129, 6, 35, 95, 129, 6, 35, 95, 123, 208, 0, 238, 128, 224, 128, 18, 48, 0, 219, 198, 123, 12, 0, 238, 163, 207, 96, 28, 216, 4, 0, 238, 35, 71, 142, 35, 35, 71, 96, 5, 240, 24, 240, 21, 240, 7, 48, 0, 19, 127, 0, 238, 106, 0, 141, 224, 107, 4, 233, 161, 18, 87, 166, 2, 253, 30, 240, 101, 48, 255, 19, 165, 106, 0, 107, 4, 109, 1, 110, 1, 19, 141, 165, 0, 240, 30, 219, 198, 123, 8, 125, 1, 122, 1, 58, 7, 19, 141, 0, 238, 60, 126, 255, 255, 153, 153, 126, 255, 255, 36, 36, 231, 126, 255, 60, 60, 126, 219, 129, 66, 60, 126, 255, 219, 16, 56, 124, 254, 0, 0, 127, 0, 63, 0, 127, 0, 0, 0, 1, 1, 1, 3, 3, 3, 3, 0, 0, 63, 32, 32, 32, 32, 32, 32, 32, 32, 63, 8, 8, 255, 0, 0, 254, 0, 252, 0, 254, 0, 0, 0, 126, 66, 66, 98, 98, 98, 98, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 125, 0, 65, 125, 5, 125, 125, 0, 0, 194, 194, 198, 68, 108, 40, 56, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 247, 16, 20, 247, 247, 4, 4, 0, 0, 124, 68, 254, 194, 194, 194, 194, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 239, 32, 40, 232, 232, 47, 47, 0, 0, 249, 133, 197, 197, 197, 197, 249, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 190, 0, 32, 48, 32, 190, 190, 0, 0, 247, 4, 231, 133, 133, 132, 244, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 127, 0, 63, 0, 127, 0, 0, 0, 239, 40, 239, 0, 224, 96, 111, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 254, 0, 252, 0, 254, 0, 0, 0, 192, 0, 192, 192, 192, 192, 192, 0, 0, 252, 4, 4, 4, 4, 4, 4, 4, 4, 252, 16, 16, 255, 249, 129, 185, 139, 154, 154, 250, 0, 250, 138, 154, 154, 155, 153, 248, 230, 37, 37, 244, 52, 52, 52, 0, 23, 20, 52, 55, 54, 38, 199, 223, 80, 80, 92, 216, 216, 223, 0, 223, 17, 31, 18, 27, 25, 217, 124, 68, 254, 134, 134, 134, 252, 132, 254, 130, 130, 254, 254, 128, 192, 192, 192, 254, 252, 130, 194, 194, 194, 252, 254, 128, 248, 192, 192, 254, 254, 128, 240, 192, 192, 192, 254, 128, 190, 134, 134, 254, 134, 134, 254, 134, 134, 134, 16, 16, 16, 16, 16, 16, 24, 24, 24, 72, 72, 120, 156, 144, 176, 192, 176, 156, 128, 128, 192, 192, 192, 254, 238, 146, 146, 134, 134, 134, 254, 130, 134, 134, 134, 134, 124, 130, 134, 134, 134, 124, 254, 130, 254, 192, 192, 192, 124, 130, 194, 202, 196, 122, 254, 134, 254, 144, 156, 132, 254, 192, 254, 2, 2, 254, 254, 16, 48, 48, 48, 48, 130, 130, 194, 194, 194, 254, 130, 130, 130, 238, 56, 16, 134, 134, 150, 146, 146, 238, 130, 68, 56, 56, 68, 130, 130, 130, 254, 48, 48, 48, 254, 2, 30, 240, 128, 254, 0, 0, 0, 0, 6, 6, 0, 0, 0, 96, 96, 192, 0, 0, 0, 0, 0, 0, 24, 24, 24, 24, 0, 24, 124, 198, 12, 24, 0, 24, 0, 0, 254, 254, 0, 0, 254, 130, 134, 134, 134, 254, 8, 8, 8, 24, 24, 24, 254, 2, 254, 192, 192, 254, 254, 2, 30, 6, 6, 254, 132, 196, 196, 254, 4, 4, 254, 128, 254, 6, 6, 254, 192, 192, 192, 254, 130, 254, 254, 2, 2, 6, 6, 6, 124, 68, 254, 134, 134, 254, 254, 130, 254, 6, 6, 6, 68, 254, 68, 68, 254, 68, 168, 168, 168, 168, 168, 168, 168, 108, 90, 0, 12, 24, 168, 48, 78, 126, 0, 18, 24, 102, 108, 168, 90, 102, 84, 36, 102, 0, 72, 72, 24, 18, 168, 6, 144, 168, 18, 0, 126, 48, 18, 168, 132, 48, 78, 114, 24, 102, 168, 168, 168, 168, 168, 168, 144, 84, 120, 168, 72, 120, 108, 114, 168, 18, 24, 108, 114, 102, 84, 144, 168, 114, 42, 24, 168, 48, 78, 126, 0, 18, 24, 102, 108, 168, 114, 84, 168, 90, 102, 24, 126, 24, 78, 114, 168, 114, 42, 24, 48, 102, 168, 48, 78, 126, 0, 108, 48, 84, 78, 156, 168, 168, 168, 168, 168, 168, 168, 72, 84, 126, 24, 168, 144, 84, 120, 102, 168, 108, 42, 48, 90, 168, 132, 48, 114, 42, 168, 216, 168, 0, 78, 18, 168, 228, 162, 168, 0, 78, 18, 168, 108, 42, 84, 84, 114, 168, 132, 48, 114, 42, 168, 222, 156, 168, 114, 42, 24, 168, 12, 84, 72, 90, 120, 114, 24, 102, 168, 114, 24, 66, 66, 108, 168, 114, 42, 0, 114, 168, 114, 42, 24, 168, 48, 78, 126, 0, 18, 24, 102, 108, 168, 48, 78, 12, 102, 24, 0, 108, 24, 168, 114, 42, 24, 48, 102, 168, 30, 84, 102, 12, 24, 156, 168, 36, 84, 84, 18, 168, 66, 120, 12, 60, 168, 174, 168, 168, 168, 168, 168, 168, 168, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
};
World.prototype.getState = function() {
return {screen : this.chip8.screen};//, this.chip8.get_score()};
};
World.prototype.takeAction = function(actions) {
// console.log(actions);
this.chip8.keys.fill(false); // erase pressed key
actions.forEach(function(action) {
this.chip8.set_key(action);
}.bind(this));
this.chip8.perform_cycle();
// for(var j = 0; j < 64; ++j)
// console.log(this.chip8.screen.slice(j * 64, j * 64 + 32).toString());
// console.log('screen:');
// console.log(this.chip8.screen);
// console.log('len');
// console.log(this.three_screen.length);
// console.log('score ' + this.chip8.score);
if (true)
{
for(var k = 0; k < 64 * 32; ++k)
this.three_screen[k].material.color.setHex((this.chip8.screen[k] === 1) ? 0xFFFFFF : 0x000000);
}
if ( /*some_condition*/false ) // Optional: Check for condition that will end the run early.
{
this.endCondition = true; // This will end the run.
}
};
World.prototype.endRun = function() {
};
World.prototype.getScore = function() {
return this.chip8.score / 10;
};