Code viewer for World: OpenChip8 (Pong) - AI Envi...
/*
 * 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;
};