UNPKG

karel

Version:

Compilador y evaluador de Karel en javascript

1,674 lines (1,441 loc) 41 kB
if (typeof Event === 'undefined') { var Event = function (type) { this.type = type; }; } /** * A class that implements the W3C DOM's EventTarget interface. * http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget */ var EventTarget = function () { var self = this; self.listeners = {}; }; EventTarget.prototype.addEventListener = function (type, listener) { var self = this; if (!self.listeners.hasOwnProperty(type)) { self.listeners[type] = []; } self.listeners[type].push(listener); }; EventTarget.prototype.removeEventListener = function (type, listener) { var self = this; if (self.listeners.hasOwnProperty(type)) { var index = self.listeners[type].indexOf(listener); if (index > -1) { self.listeners[type].splice(index, 1); } } }; EventTarget.prototype.dispatchEvent = function (evt) { var self = this; if (self.listeners.hasOwnProperty(evt._type)) { for (var i = 0; i < self.listeners[evt._type].length; i++) { self.listeners[evt._type][i](evt); } } }; EventTarget.prototype.fireEvent = function (type, properties) { var self = this; var evt = null; // IE<11 does not support the construction of custom events through // standard means. ugh. if (typeof document != 'undefined' && document.createEvent) { evt = document.createEvent('Event'); } else if (typeof document != 'undefined' && document.createEventObject) { evt = document.createEventObject(); } else { evt = new Event(type); } if (properties) { for (var p in properties) { if (properties.hasOwnProperty(p)) { evt[p] = properties[p]; } } evt.runtime = properties.target; } evt._type = type; self.dispatchEvent(evt); }; /** * A class that holds the state of computation and executes opcodes. * * The Karel Virtual Machine is a simple, stack-based virtual machine with * a small number of opcodes, based loosely on the Java Virtual Machine. * All opcodes are represented as an array where the first element is the * opcode name, followed by zero or one parameters. */ var Runtime = function (world) { var self = this; self.debug = false; self.world = world; self.disableStackEvents = false; self.load([['HALT']]); }; Runtime.prototype = new EventTarget(); Runtime.HALT = 0; Runtime.LINE = 1; Runtime.LEFT = 2; Runtime.WORLDWALLS = 3; Runtime.ORIENTATION = 4; Runtime.ROTL = 5; Runtime.ROTR = 6; Runtime.MASK = 7; Runtime.NOT = 8; Runtime.AND = 9; Runtime.OR = 10; Runtime.EQ = 11; Runtime.EZ = 12; Runtime.JZ = 13; Runtime.JMP = 14; Runtime.FORWARD = 15; Runtime.WORLDBUZZERS = 16; Runtime.BAGBUZZERS = 17; Runtime.PICKBUZZER = 18; Runtime.LEAVEBUZZER = 19; Runtime.LOAD = 20; Runtime.POP = 21; Runtime.DUP = 22; Runtime.DEC = 23; Runtime.INC = 24; Runtime.CALL = 25; Runtime.RET = 26; Runtime.PARAM = 27; Runtime.prototype.load = function (opcodes) { var self = this; var opcode_mapping = [ 'HALT', 'LINE', 'LEFT', 'WORLDWALLS', 'ORIENTATION', 'ROTL', 'ROTR', 'MASK', 'NOT', 'AND', 'OR', 'EQ', 'EZ', 'JZ', 'JMP', 'FORWARD', 'WORLDBUZZERS', 'BAGBUZZERS', 'PICKBUZZER', 'LEAVEBUZZER', 'LOAD', 'POP', 'DUP', 'DEC', 'INC', 'CALL', 'RET', 'PARAM', ]; var error_mapping = ['WALL', 'WORLDUNDERFLOW', 'BAGUNDERFLOW', 'INSTRUCTION']; self.raw_opcodes = opcodes; var function_map = {}; self.function_names = []; var function_idx = 0; self.program = new Int32Array(new ArrayBuffer(opcodes.length * 3 * 4)); for (var i = 0; i < opcodes.length; i++) { self.program[3 * i] = opcode_mapping.indexOf(opcodes[i][0]); if (opcodes[i].length > 1) { self.program[3 * i + 1] = opcodes[i][1]; } if (opcodes[i][0] == 'CALL') { if (!function_map.hasOwnProperty(opcodes[i][2])) { function_map[opcodes[i][2]] = function_idx; self.function_names[function_idx++] = opcodes[i][2]; } self.program[3 * i + 2] = function_map[opcodes[i][2]]; } else if (opcodes[i][0] == 'EZ') { self.program[3 * i + 1] = error_mapping.indexOf(opcodes[i][1]); if (self.program[3 * i + 1] == -1) { throw new Error('Invalid error: ' + opcodes[i][1]); } } } self.reset(); }; Runtime.prototype.start = function () { var self = this; self.fireEvent('start', { target: self, world: self.world }); }; Runtime.prototype.reset = function () { var self = this; self.state = { pc: 0, sp: -1, fp: -1, line: -1, ic: 0, stack: new Int32Array(new ArrayBuffer((0xffff * 16 + 40) * 4)), stackSize: 0, // Instruction counts moveCount: 0, turnLeftCount: 0, pickBuzzerCount: 0, leaveBuzzerCount: 0, // Flags jumped: false, running: true, }; if (self.debug) { self.fireEvent('debug', { target: self, message: JSON.stringify(self.raw_opcodes), debugType: 'program', }); } }; Runtime.prototype.step = function () { var self = this; while (self.state.running) { try { if (self.program[3 * self.state.pc] == Runtime.LINE) { self.next(); break; } self.next(); } finally { if (!self.state.running) { self.fireEvent('stop', { target: self, world: self.world }); } } } return self.state.running; }; Runtime.prototype.next = function () { var self = this; if (!self.state.running) return; var world = self.world; if (self.state.ic >= world.maxInstructions) { self.state.running = false; self.state.error = 'INSTRUCTION'; return false; } else if (self.state.stackSize >= self.world.maxStackSize) { self.state.running = false; self.state.error = 'STACK'; return false; } var rot; var di = [0, 1, 0, -1]; var dj = [-1, 0, 1, 0]; var param, newSP, op1, op2, fname; try { if (self.debug) { self.fireEvent('debug', { target: self, message: JSON.stringify( self.program[3 * self.state.pc] + ' ' + self.raw_opcodes[self.state.pc], ), debugType: 'opcode', }); } switch (self.program[3 * self.state.pc]) { case Runtime.HALT: { self.state.running = false; break; } case Runtime.LINE: { self.state.line = self.program[3 * self.state.pc + 1]; break; } case Runtime.LEFT: { self.state.ic++; self.world.orientation--; if (self.world.orientation < 0) { self.world.orientation = 3; } self.world.dirty = true; self.state.turnLeftCount++; if ( self.world.maxTurnLeft >= 0 && self.state.turnLeftCount > self.world.maxTurnLeft ) { self.state.running = false; self.state.error = 'INSTRUCTION'; } break; } case Runtime.WORLDWALLS: { self.state.stack[++self.state.sp] = world.walls(world.i, world.j); break; } case Runtime.ORIENTATION: { self.state.stack[++self.state.sp] = world.orientation; break; } case Runtime.ROTL: { rot = self.state.stack[self.state.sp] - 1; if (rot < 0) { rot = 3; } self.state.stack[self.state.sp] = rot; break; } case Runtime.ROTR: { rot = self.state.stack[self.state.sp] + 1; if (rot > 3) { rot = 0; } self.state.stack[self.state.sp] = rot; break; } case Runtime.MASK: { self.state.stack[self.state.sp] = 1 << self.state.stack[self.state.sp]; break; } case Runtime.NOT: { self.state.stack[self.state.sp] = self.state.stack[self.state.sp] === 0 ? 1 : 0; break; } case Runtime.AND: { op2 = self.state.stack[self.state.sp--]; op1 = self.state.stack[self.state.sp--]; self.state.stack[++self.state.sp] = op1 & op2 ? 1 : 0; break; } case Runtime.OR: { op2 = self.state.stack[self.state.sp--]; op1 = self.state.stack[self.state.sp--]; self.state.stack[++self.state.sp] = op1 | op2 ? 1 : 0; break; } case Runtime.EQ: { op2 = self.state.stack[self.state.sp--]; op1 = self.state.stack[self.state.sp--]; self.state.stack[++self.state.sp] = op1 == op2 ? 1 : 0; break; } case Runtime.EZ: { if (self.state.stack[self.state.sp--] === 0) { self.state.error = ['WALL', 'WORLDUNDERFLOW', 'BAGUNDERFLOW'][ self.program[3 * self.state.pc + 1] ]; self.state.running = false; } break; } case Runtime.JZ: { self.state.ic++; if (self.state.stack[self.state.sp--] === 0) { self.state.pc += self.program[3 * self.state.pc + 1]; } break; } case Runtime.JMP: { self.state.ic++; self.state.pc += self.program[3 * self.state.pc + 1]; break; } case Runtime.FORWARD: { self.state.ic++; self.world.i += di[self.world.orientation]; self.world.j += dj[self.world.orientation]; self.world.dirty = true; self.state.moveCount++; if ( self.world.maxMove >= 0 && self.state.moveCount > self.world.maxMove ) { self.state.running = false; self.state.error = 'INSTRUCTION'; } break; } case Runtime.WORLDBUZZERS: { self.state.stack[++self.state.sp] = self.world.buzzers( world.i, world.j, ); break; } case Runtime.BAGBUZZERS: { self.state.stack[++self.state.sp] = self.world.bagBuzzers; break; } case Runtime.PICKBUZZER: { self.state.ic++; self.world.pickBuzzer(self.world.i, self.world.j); self.state.pickBuzzerCount++; if ( self.world.maxPickBuzzer >= 0 && self.state.pickBuzzerCount > self.world.maxPickBuzzer ) { self.state.running = false; self.state.error = 'INSTRUCTION'; } break; } case Runtime.LEAVEBUZZER: { self.state.ic++; self.world.leaveBuzzer(self.world.i, self.world.j); self.state.leaveBuzzerCount++; if ( self.world.maxLeaveBuzzer >= 0 && self.state.leaveBuzzerCount > self.world.maxLeaveBuzzer ) { self.state.running = false; self.state.error = 'INSTRUCTION'; } break; } case Runtime.LOAD: { self.state.stack[++self.state.sp] = self.program[3 * self.state.pc + 1]; break; } case Runtime.POP: { self.state.sp--; break; } case Runtime.DUP: { self.state.stack[++self.state.sp] = self.state.stack[self.state.sp - 1]; break; } case Runtime.DEC: { self.state.stack[self.state.sp]--; break; } case Runtime.INC: { self.state.stack[self.state.sp]++; break; } case Runtime.CALL: { self.state.ic++; // sp, pc, param param = self.state.stack[self.state.sp--]; newSP = self.state.sp; fname = self.function_names[self.program[3 * self.state.pc + 2]]; self.state.stack[++self.state.sp] = self.state.fp; self.state.stack[++self.state.sp] = newSP; self.state.stack[++self.state.sp] = self.state.pc; self.state.stack[++self.state.sp] = param; self.state.fp = newSP + 1; self.state.pc = self.program[3 * self.state.pc + 1]; self.state.jumped = true; self.state.stackSize++; if (self.state.stackSize >= self.world.maxStackSize) { self.state.running = false; self.state.error = 'STACK'; } else if (!self.disableStackEvents) { self.fireEvent('call', { function: fname, param: param, line: self.state.line, target: self, }); } break; } case Runtime.RET: { if (self.state.fp < 0) { self.state.running = false; break; } self.state.pc = self.state.stack[self.state.fp + 2]; self.state.sp = self.state.stack[self.state.fp + 1]; self.state.fp = self.state.stack[self.state.fp]; self.state.stackSize--; if (!self.disableStackEvents) { self.fireEvent('return', { target: self }); } break; } case Runtime.PARAM: { self.state.stack[++self.state.sp] = self.state.stack[ self.state.fp + 3 + self.program[3 * self.state.pc + 1] ]; break; } default: { self.state.running = false; if (self.debug) { self.fireEvent('debug', { target: self, message: 'Missing opcode ' + self.raw_opcodes[self.state.pc][0], debugType: 'opcode', }); } self.state.error = 'INVALIDOPCODE'; return false; } } if (self.state.jumped) { self.state.jumped = false; } else { self.state.pc++; } if (self.debug) { var copy = { pc: self.state.pc, stackSize: self.state.stackSize, expressionStack: Array.from( self.state.stack.slice(self.state.fp + 4, self.state.sp + 1), ), line: self.state.line, ic: self.state.ic, running: self.state.running, }; self.fireEvent('debug', { target: self, message: JSON.stringify(copy), debugType: 'state', }); } } catch (e) { self.state.running = false; console.error(e); console.log(e.stack); throw e; } return true; }; var World = function (w, h) { var self = this; self.init(w, h); }; World.prototype.reset = function () { var self = this; self.init(self.w, self.h); }; World.prototype.createMaps = function () { var self = this; if (ArrayBuffer) { var len = (self.w + 2) * (self.h + 2); self.map = new Int32Array(new ArrayBuffer(len * 4)); self.currentMap = new Int32Array(new ArrayBuffer(len * 4)); self.wallMap = new Uint8Array(new ArrayBuffer(len)); } else { self.map = []; self.currentMap = []; self.wallMap = []; for (var i = 0; i <= self.h; i++) { for (var j = 0; j <= self.w; j++) { self.map.push(0); self.currentMap.push(0); self.wallMap.push(0); } } } }; World.prototype.init = function (w, h) { var self = this; self.w = w; self.h = h; self.runtime = new Runtime(self); self.createMaps(); self.clear(); }; World.prototype.resize = function (w, h) { var self = this; // Eliminamos las paredes del borde for (var i = 1; i <= self.h; i++) { self.wallMap[self.w * i + 1] &= ~(1 << 0); self.wallMap[self.w * (i + 1)] &= ~(1 << 2); } for (var j = 1; j <= self.w; j++) { self.wallMap[self.w * self.h + j] &= ~(1 << 1); self.wallMap[self.w + j] &= ~(1 << 3); } var oldW = self.w; var oldH = self.h; var oldMap = self.map; var oldCurrentMap = self.oldCurrentMap; var oldWallMap = self.wallMap; var oldDumpCells = self.dumpCells; self.w = w; self.h = h; self.createMaps(); self.addBorderWalls(); // Copiamos todas las paredes y zumbadores for (var i = 1; i <= oldH; i++) { for (var j = 1; j <= oldW; j++) { self.setCellWalls(i, j, oldWallMap[oldW * i + j]); self.setBuzzers(i, j, oldMap[oldW * i + j]); } } // Vaciamos dumpCells y la llenamos de nuevo self.dumpCells = []; for (var dumpPos = 0; dumpPos < oldDumpCells.length; dumpPos++) { if ( oldDumpCells[dumpPos][0] <= self.h && oldDumpCells[dumpPos][1] <= self.w ) { self.setDumpCell( oldDumpCells[dumpPos][0], oldDumpCells[dumpPos][1], true, ); } } // Checamos si karel sigue dentro del mundo if (self.start_i > self.h) self.start_i = self.i = self.h; if (self.start_j > self.w) self.start_j = self.j = self.w; self.dirty = true; }; World.prototype.clear = function () { var self = this; for (var i = 0; i < self.wallMap.length; i++) { self.wallMap[i] = 0; } for (var i = 0; i < self.map.length; i++) { self.map[i] = self.currentMap[i] = 0; } self.addBorderWalls(); self.orientation = 1; self.startOrientation = 1; self.start_i = 1; self.i = 1; self.start_j = 1; self.j = 1; self.startBagBuzzers = 0; self.bagBuzzers = 0; self.dumps = {}; self.dumpCells = []; self.maxInstructions = 10000000; self.maxMove = -1; self.maxTurnLeft = -1; self.maxPickBuzzer = -1; self.maxLeaveBuzzer = -1; self.maxKarelBeepers = -1; self.maxBeepers = -1; self.maxStackSize = 65000; self.worldName = 'mundo_0'; self.programName = 'p1'; self.preValidators = []; self.postValidators = []; self.dirty = true; }; World.DUMP_WORLD = 'mundo'; World.DUMP_POSITION = 'posicion'; World.DUMP_ORIENTATION = 'orientacion'; World.DUMP_INSTRUCTIONS = 'instrucciones'; World.DUMP_ALL_BUZZERS = 'universo'; World.DUMP_BAG = 'mochila'; World.DUMP_MOVE = 'avanza'; World.DUMP_LEFT = 'gira_izquierda'; World.DUMP_PICK_BUZZER = 'coge_zumbador'; World.DUMP_LEAVE_BUZZER = 'deja_zumbador'; World.ERROR_MAPPING = { BAGUNDERFLOW: 'ZUMBADOR INVALIDO', WALL: 'MOVIMIENTO INVALIDO', WORLDUNDERFLOW: 'ZUMBADOR INVALIDO', STACK: 'STACK OVERFLOW', INSTRUCTION: 'LIMITE DE INSTRUCCIONES', }; World.prototype.validate = function (script, callbacks) { var self = this; return new Promise(function (resolve, reject) { var sandbox = $.sandbox({ url: 'sandbox.html', // timeout: 2000, input: { script: 'data:application/javascript,' + encodeURIComponent(script), mundo: self.save(), }, callback: function (data, error) { if (error !== undefined) { sandbox.terminate(); sandbox = null; reject(error); } else { if (data.type == 'result') { sandbox.terminate(); sandbox = null; resolve(data.value); } else { callbacks(data); } } }, }); }); }; World.prototype.preValidate = function (callbacks) { var self = this; var promises = []; for (idx in self.preValidators) { if (!self.preValidators.hasOwnProperty(idx)) continue; promises.push(self.validate(self.preValidators[idx], callbacks)); } return new Promise(function (resolve, reject) { Promise.all(promises).then(function (results) { if ( results.every(function (x) { return !!x; }) ) { resolve(promises.length); } else { reject(''); } }, reject); }); }; World.prototype.postValidate = function (callbacks) { var self = this; var promises = []; for (idx in self.postValidators) { if (!self.postValidators.hasOwnProperty(idx)) continue; promises.push(self.validate(self.postValidators[idx], callbacks)); } return new Promise(function (resolve, reject) { Promise.all(promises).then(function (results) { if ( results.every(function (x) { return !!x; }) ) { resolve(promises.length); } else { reject(''); } }, reject); }); }; World.prototype.walls = function (i, j) { var self = this; if (0 > i || i > self.h || 0 > j || j > self.w) return 0; return self.wallMap[self.w * i + j]; }; World.prototype.toggleWall = function (i, j, orientation) { var self = this; if ( (j == 1 && orientation === 0) || (i == 1 && orientation == 3) || (i == self.h && orientation == 1) || (j == self.w && orientation == 2) ) { return; } if ( orientation < 0 || orientation >= 4 || 0 > i || i > self.h || 0 > j || j > self.w ) return; // Needed to prevent Karel from traversing walls from one direction, but not // from the other. self.wallMap[self.w * i + j] ^= 1 << orientation; if (orientation === 0 && j > 1) { self.wallMap[self.w * i + (j - 1)] ^= 1 << 2; } else if (orientation === 1 && i < self.h) { self.wallMap[self.w * (i + 1) + j] ^= 1 << 3; } else if (orientation === 2 && j < self.w) { self.wallMap[self.w * i + (j + 1)] ^= 1 << 0; } else if (orientation === 3 && i > 1) { self.wallMap[self.w * (i - 1) + j] ^= 1 << 1; } self.dirty = true; }; World.prototype.addBorderWalls = function () { var self = this; for (var i = 1; i <= self.h; i++) { self.addWall(i, 1, 0); self.addWall(i, self.w, 2); } for (var j = 1; j <= self.w; j++) { self.addWall(self.h, j, 1); self.addWall(1, j, 3); } }; World.prototype.setCellWalls = function (i, j, wallMask) { var self = this; for (var pos = 0; pos < 4; pos++) { if (wallMask & (1 << pos)) self.addWall(i, j, pos); } }; World.prototype.addWall = function (i, j, orientation) { var self = this; if ( orientation < 0 || orientation >= 4 || 0 > i || i > self.h || 0 > j || j > self.w ) return; self.wallMap[self.w * i + j] |= 1 << orientation; if (orientation === 0 && j > 1) self.wallMap[self.w * i + (j - 1)] |= 1 << 2; else if (orientation === 1 && i < self.h) self.wallMap[self.w * (i + 1) + j] |= 1 << 3; else if (orientation === 2 && j < self.w) self.wallMap[self.w * i + (j + 1)] |= 1 << 0; else if (orientation === 3 && i > 1) self.wallMap[self.w * (i - 1) + j] |= 1 << 1; self.dirty = true; }; World.prototype.setBuzzers = function (i, j, count) { var self = this; if (0 > i || i > self.h || 0 > j || j > self.w) return; self.map[self.w * i + j] = self.currentMap[self.w * i + j] = count == 0xffff ? -1 : count; self.dirty = true; }; World.prototype.buzzers = function (i, j) { var self = this; if (0 > i || i > self.h || 0 > j || j > self.w) return 0; return self.currentMap[self.w * i + j]; }; World.prototype.pickBuzzer = function (i, j) { var self = this; if (0 > i || i > self.h || 0 > j || j > self.w) return; if (self.currentMap[self.w * i + j] != -1) { self.currentMap[self.w * i + j]--; } if (self.bagBuzzers != -1) { self.bagBuzzers++; } self.dirty = true; }; World.prototype.leaveBuzzer = function (i, j) { var self = this; if (0 > i || i > self.h || 0 > j || j > self.w) return; if (self.currentMap[self.w * i + j] != -1) { self.currentMap[self.w * i + j]++; } if (self.bagBuzzers != -1) { self.bagBuzzers--; } self.dirty = true; }; World.prototype.setDumpCell = function (i, j, dumpState) { var self = this; var dumpPos = -1; if (0 > i || i > self.h || 0 > j || j > self.w) return; for (dumpPos = 0; dumpPos < self.dumpCells.length; dumpPos++) { if (self.dumpCells[dumpPos][0] == i && self.dumpCells[dumpPos][1] == j) { break; } } if (dumpPos < self.dumpCells.length) { if (dumpState) return; self.dumpCells.splice(dumpPos, 0); } else { if (!dumpState) return; self.dumpCells.push([i, j]); } self.dumps[World.DUMP_WORLD] = self.dumpCells.length !== 0; }; World.prototype.toggleDumpCell = function (i, j) { var self = this; var dumpPos = 0; if (0 > i || i > self.h || 0 > j || j > self.w) return; for (; dumpPos < self.dumpCells.length; dumpPos++) { if (self.dumpCells[dumpPos][0] == i && self.dumpCells[dumpPos][1] == j) { break; } } if (dumpPos < self.dumpCells.length) { self.dumpCells.splice(dumpPos, 1); } else { self.dumpCells.push([i, j]); } self.dumps[World.DUMP_WORLD] = self.dumpCells.length !== 0; }; World.prototype.getDumpCell = function (i, j) { var self = this; var dumpPos = -1; for (dumpPos = 0; dumpPos < self.dumpCells.length; dumpPos++) { if (self.dumpCells[dumpPos][0] == i && self.dumpCells[dumpPos][1] == j) { return true; } } return false; }; World.prototype.getDumps = function (d) { var self = this; return self.dumps.hasOwnProperty(d.toLowerCase()) && self.dumps[d]; }; World.prototype.setDumps = function (d, v) { var self = this; self.dumps[d] = v; }; World.prototype.toggleDumps = function (d) { var self = this; self.setDumps(d, !self.getDumps(d)); }; World.prototype.load = function (doc) { var self = this; self.clear(); var rules = { mundo: function (mundo) { var alto = mundo.getAttribute('alto'); var ancho = mundo.getAttribute('ancho'); if (!alto || !ancho) { return; } alto = parseInt(alto, 10); ancho = parseInt(ancho, 10); if (!alto || !ancho) { return; } self.resize(ancho, alto); }, condiciones: function (condiciones) { self.maxInstructions = parseInt( condiciones.getAttribute('instruccionesMaximasAEjecutar'), 10, ) || 10000000; self.maxStackSize = parseInt(condiciones.getAttribute('longitudStack'), 10) || 65000; }, comando: function (comando) { var name = comando.getAttribute('nombre'); var val = parseInt(comando.getAttribute('maximoNumeroDeEjecuciones'), 10); if (!name || !val) { return; } if (name == 'AVANZA') { self.maxMove = val; } else if (name == 'GIRA_IZQUIERDA') { self.maxTurnLeft = val; } else if (name == 'COGE_ZUMBADOR') { self.maxPickBuzzer = val; } else if (name == 'DEJA_ZUMBADOR') { self.maxLeaveBuzzer = val; } }, monton: function (monton) { var i = parseInt(monton.getAttribute('y'), 10); var j = parseInt(monton.getAttribute('x'), 10); var zumbadores = monton.getAttribute('zumbadores'); if (zumbadores == 'INFINITO') { zumbadores = -1; } else { zumbadores = parseInt(zumbadores, 10); if (isNaN(zumbadores)) zumbadores = 0; } self.setBuzzers(i, j, zumbadores); }, pared: function (pared) { var i = parseInt(pared.getAttribute('y1'), 10) + 1; var j = parseInt(pared.getAttribute('x1'), 10) + 1; if (pared.getAttribute('x2')) { var j2 = parseInt(pared.getAttribute('x2'), 10) + 1; if (j2 > j) { self.addWall(i, j, 3); } else { self.addWall(i, j2, 3); } } else if (pared.getAttribute('y2')) { var i2 = parseInt(pared.getAttribute('y2'), 10) + 1; if (i2 > i) { self.addWall(i, j, 0); } else { self.addWall(i2, j, 0); } } }, despliega: function (despliega) { self.dumps[despliega.getAttribute('tipo').toLowerCase()] = true; }, posicionDump: function (dump) { self.dumpCells.push([ parseInt(dump.getAttribute('y'), 10), parseInt(dump.getAttribute('x'), 10), ]); }, validador: function (validador) { var src = null; if (validador.getAttribute('src')) { src = $.ajax({ type: 'GET', url: validador.getAttribute('src'), async: false, }).responseText; } else { src = validador.firstChild.nodeValue; } if (validador.getAttribute('tipo') == 'post') { self.postValidators.push(src); } else { self.preValidators.push(src); } }, programa: function (programa) { var xKarel = parseInt( programa.getAttribute('xKarel') || programa.getAttribute('xkarel'), 10, ); var yKarel = parseInt( programa.getAttribute('yKarel') || programa.getAttribute('ykarel'), 10, ); self.di = self.h / 2 - yKarel; self.dj = self.w / 2 - xKarel; self.rotate( programa.getAttribute('direccionKarel') || programa.getAttribute('direccionkarel'), ); self.worldName = programa.getAttribute('mundoDeEjecucion') || programa.getAttribute('mundodeejecucion'); self.programName = programa.getAttribute('nombre'); self.move(yKarel, xKarel); var bagBuzzers = programa.getAttribute('mochilaKarel') || programa.getAttribute('mochilakarel') || 0; if (bagBuzzers == 'INFINITO') { self.setBagBuzzers(-1); } else { self.setBagBuzzers(parseInt(bagBuzzers)); } }, }; function traverse(node) { var type = node.nodeName; if (rules.hasOwnProperty(type)) { rules[type](node); } for (var i = 0; i < node.childNodes.length; i++) { if ( node.childNodes.item(i).nodeType === (node.ELEMENT_NODE || DOMNode.ELEMENT_NODE) ) { traverse(node.childNodes.item(i)); } } } traverse(doc); self.reset(); }; World.prototype.serialize = function (node, name, indentation) { var self = this; var result = ''; for (var i = 0; i < indentation; i++) { result += '\t'; } if (typeof node === 'string' || typeof node === 'number') { return result + node; } if (Array.isArray(node)) { result = ''; for (var i = 0; i < node.length; i++) { result += self.serialize(node[i], name, indentation); } } else { var childResult = ''; for (var p in node) { if (node.hasOwnProperty(p)) { if (p[0] == '#') { continue; } else { childResult += self.serialize(node[p], p, indentation + 1); } } } result += '<' + name; if (node.hasOwnProperty('#attributes')) { for (var p in node['#attributes']) { if (node['#attributes'].hasOwnProperty(p)) { result += ' ' + p + '="' + node['#attributes'][p] + '"'; } } } if (node.hasOwnProperty('#text')) { result += '>' + node['#text'] + '</' + name + '>\n'; } else if (childResult == '') { result += '/>\n'; } else { result += '>\n'; result += childResult; for (var i = 0; i < indentation; i++) { result += '\t'; } result += '</' + name + '>\n'; } } return result; }; World.prototype.save = function () { var self = this; var result = { condiciones: { '#attributes': { instruccionesMaximasAEjecutar: self.maxInstructions, longitudStack: self.maxStackSize, }, }, mundos: { mundo: { '#attributes': { nombre: self.worldName, ancho: self.w, alto: self.h }, monton: [], pared: [], posicionDump: [], }, }, programas: { '#attributes': { tipoEjecucion: 'CONTINUA', intruccionesCambioContexto: 1, milisegundosParaPasoAutomatico: 0, }, programa: { '#attributes': { nombre: self.programName, ruta: '{$2$}', mundoDeEjecucion: self.worldName, xKarel: self.j, yKarel: self.i, direccionKarel: ['OESTE', 'NORTE', 'ESTE', 'SUR'][self.orientation], mochilaKarel: self.bagBuzzers == -1 ? 'INFINITO' : self.bagBuzzers, }, despliega: [], }, }, }; for (var i = 1; i <= self.h; i++) { for (var j = 1; j <= self.w; j++) { var buzzers = self.buzzers(i, j); if (buzzers !== 0) { result.mundos.mundo.monton.push({ '#attributes': { x: j, y: i, zumbadores: buzzers == -1 ? 'INFINITO' : buzzers, }, }); } } } for (var i = 1; i <= self.h; i++) { for (var j = 1; j <= self.w; j++) { var walls = self.walls(i, j); for (var k = 2; k < 8; k <<= 1) { if (i == self.h && k == 2) continue; if (j == self.w && k == 4) continue; if ((walls & k) == k) { if (k == 2) { result.mundos.mundo.pared.push({ '#attributes': { x1: j - 1, y1: i, x2: j }, }); } else if (k == 4) { result.mundos.mundo.pared.push({ '#attributes': { x1: j, y1: i - 1, y2: i }, }); } } } } } for (var i = 0; i < self.dumpCells.length; i++) { result.mundos.mundo.posicionDump.push({ '#attributes': { x: self.dumpCells[i][1], y: self.dumpCells[i][0] }, }); } for (var p in self.dumps) { if (self.dumps.hasOwnProperty(p) && self.dumps[p]) { result.programas.programa.despliega.push({ '#attributes': { tipo: p.toUpperCase() }, }); } } if (self.preValidators || self.postValidators) { result.validadores = []; for (var i = 0; i < self.preValidators.length; i++) { result.validadores.push({ validador: { '#attributes': { tipo: 'pre' }, '#text': '<![CDATA[' + self.preValidators[i] + ']]>', }, }); } for (var i = 0; i < self.postValidators.length; i++) { result.validadores.push({ validador: { '#attributes': { tipo: 'post' }, '#text': '<![CDATA[' + self.postValidators[i] + ']]>', }, }); } } return self.serialize(result, 'ejecucion', 0); }; World.prototype.output = function () { var self = this; var result = {}; if (self.dumps[World.DUMP_WORLD] || self.dumps[World.DUMP_ALL_BUZZERS]) { result.mundos = { mundo: { '#attributes': { nombre: self.worldName }, linea: [] }, }; var dumpCells = {}; for (var i = 0; i < self.dumpCells.length; i++) { if (!dumpCells.hasOwnProperty(self.dumpCells[i][0])) { dumpCells[self.dumpCells[i][0]] = {}; } dumpCells[self.dumpCells[i][0]][self.dumpCells[i][1]] = true; } for (var i = self.h; i > 0; i--) { var printCoordinate = true; var line = ''; for (var j = 1; j <= self.w; j++) { if ( (dumpCells[i] && dumpCells[i][j]) || self.dumps[World.DUMP_ALL_BUZZERS] ) { if (self.buzzers(i, j) !== 0) { if (printCoordinate) { line += '(' + j + ') '; } // TODO: Este es un bug en karel.exe. line += (self.buzzers(i, j) & 0xffff) + ' '; } printCoordinate = self.buzzers(i, j) == 0; } } if (line !== '') { result.mundos.mundo.linea.push({ '#attributes': { fila: i, compresionDeCeros: 'true' }, '#text': line, }); } } } result.programas = { programa: { '#attributes': { nombre: self.programName } }, }; result.programas.programa['#attributes'].resultadoEjecucion = self.errorMap( self.runtime.state.error, ); if (self.dumps[World.DUMP_POSITION]) { result.programas.programa.karel = { '#attributes': { x: self.j, y: self.i }, }; } if (self.dumps[World.DUMP_ORIENTATION]) { result.programas.programa.karel = result.programas.programa.karel || { '#attributes': {}, }; result.programas.programa.karel['#attributes'].direccion = [ 'OESTE', 'NORTE', 'ESTE', 'SUR', ][self.orientation]; } if (self.dumps[World.DUMP_BAG]) { result.programas.programa.karel = result.programas.programa.karel || { '#attributes': {}, }; result.programas.programa.karel['#attributes'].mochila = self.bagBuzzers == -1 ? 'INFINITO' : self.bagBuzzers; } if (self.dumps[World.DUMP_MOVE]) { result.programas.programa.instrucciones = result.programas.programa .instrucciones || { '#attributes': {} }; result.programas.programa.instrucciones['#attributes'].avanza = self.runtime.state.moveCount; } if (self.dumps[World.DUMP_LEFT]) { result.programas.programa.instrucciones = result.programas.programa .instrucciones || { '#attributes': {} }; result.programas.programa.instrucciones['#attributes'].gira_izquierda = self.runtime.state.turnLeftCount; } if (self.dumps[World.DUMP_PICK_BUZZER]) { result.programas.programa.instrucciones = result.programas.programa .instrucciones || { '#attributes': {} }; result.programas.programa.instrucciones['#attributes'].coge_zumbador = self.runtime.state.pickBuzzerCount; } if (self.dumps[World.DUMP_LEAVE_BUZZER]) { result.programas.programa.instrucciones = result.programas.programa .instrucciones || { '#attributes': {} }; result.programas.programa.instrucciones['#attributes'].deja_zumbador = self.runtime.state.leaveBuzzerCount; } return self.serialize(result, 'resultados', 0); }; World.prototype.errorMap = function (s) { if (!s) return 'FIN PROGRAMA'; if (World.ERROR_MAPPING.hasOwnProperty(s)) { return World.ERROR_MAPPING[s]; } else { return s; } }; World.prototype.move = function (i, j) { var self = this; self.i = self.start_i = i; self.j = self.start_j = j; self.dirty = true; }; World.prototype.rotate = function (orientation) { var self = this; var orientations = ['OESTE', 'NORTE', 'ESTE', 'SUR']; if (!orientation) { orientation = orientations[(self.orientation + 1) % 4]; } self.orientation = self.startOrientation = Math.max( 0, orientations.indexOf(orientation), ); self.dirty = true; }; World.prototype.setBagBuzzers = function (buzzers) { var self = this; if (isNaN(buzzers)) buzzers = 0; self.bagBuzzers = self.startBagBuzzers = buzzers == 0xffff ? -1 : buzzers; self.dirty = true; }; World.prototype.reset = function () { var self = this; self.orientation = self.startOrientation; self.move(self.start_i, self.start_j); self.bagBuzzers = self.startBagBuzzers; for (var i = 0; i < self.currentMap.length; i++) { self.currentMap[i] = self.map[i]; } self.runtime.reset(); self.dirty = true; }; World.prototype.import = function (mdo, kec) { var self = this; if (mdo.length < 20 || kec.length < 30) { throw new Error('Invalid file format'); } if ( mdo[0] != 0x414b || mdo[1] != 0x4552 || mdo[2] != 0x204c || mdo[3] != 0x4d4f || mdo[4] != 0x2e49 ) { throw new Error('Invalid magic number'); } // var x1 = mdo[5]; var width = mdo[6]; var height = mdo[7]; self.init(width, height); self.setBagBuzzers(mdo[8]); self.move(mdo[10], mdo[9]); self.orientation = self.startOrientation = mdo[11] % 4; var wallcount = mdo[12]; var heapcount = mdo[13]; // var x10 = mdo[14]; if (kec[0]) { self.maxInstructions = kec[1]; } if (kec[3]) { self.maxMove = kec[4]; } if (kec[6]) { self.maxTurnLeft = kec[7]; } if (kec[9]) { self.maxPickBuzzer = kec[10]; } if (kec[12]) { self.maxLeaveBuzzer = kec[13]; } if (kec[15]) { self.maxKarelBuzzers = kec[16]; } if (kec[18]) { self.maxBuzzers = kec[19]; } if (kec[21]) { self.setDumps(World.DUMP_POSITION, true); } if (kec[24]) { self.setDumps(World.DUMP_ORIENTATION, true); } var dumpcount = kec[27] ? kec[28] : 0; if (dumpcount) { self.setDumps(World.DUMP_WORLD, true); } function decodeWalls(tx, ty, tmask) { for (var i = 0; i < 4; i++) { if (tmask & (1 << i)) { self.addWall(ty, tx, (i + 1) % 4); } } } for (var i = 15; i < 15 + 3 * wallcount; i += 3) { decodeWalls(mdo[i], mdo[i + 1], mdo[i + 2]); } for ( var i = 15 + 3 * wallcount; i < 15 + 3 * (wallcount + heapcount); i += 3 ) { self.setBuzzers(mdo[i + 1], mdo[i], mdo[i + 2]); } for (var i = 30; i < 30 + 3 * dumpcount; i += 3) { self.setDumpCell(kec[i + 1], kec[i], true); } }; function detectLanguage(code) { var rules = [ /^\s+/, /^\/\/[^\n]*/, /^#[^\n]*/, /^(?:\/\*(?:[^*]|\*[^)])*\*\/)/, /^{[^}]*}/, /^\(\*([^*]|\*[^)])*\*\)/, /^[^a-zA-Z0-9_-]+/, /^[a-zA-Z0-9_-]+/, ]; var i = 0; while (i < code.length) { for (var j = 0; j < rules.length; j++) { var m = rules[j].exec(code.substring(i)); if (m !== null) { if (j == rules.length - 1) { // el primer token de verdad. if (m[0] == 'class') { return 'java'; } else if (m[0].toLowerCase() == 'iniciar-programa') { return 'pascal'; } else { return 'ruby'; } } else { // comentario o no-token. i += m[0].length; break; } } } } return 'none'; } function compile(code) { var lang = detectLanguage(code); var parser = null; switch (lang) { case 'java': parser = require('../js/kareljava.js').parse; break; case 'pascal': parser = require('../js/karelpascal.js').parse; break; case 'ruby': parser = require('../js/karelruby.js').parse; break; default: return null; } return parser(code); } if (typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.World = World; exports.detectLanguage = detectLanguage; exports.compile = compile; } // vim: set noexpandtab:ts=2:sw=2