var Mine = function() { var ALIVE = 0, LOST = 1, ABORTED = 2, WON = 3; function defaultMeta() { return { Water: 0, Flooding: 0, Waterproof: 10, } } function repeat(str, n) { return new Array(n+1).join(str); } function Mine(map) { this.parse(map) }; Mine.ALIVE = ALIVE; Mine.LOST = LOST; Mine.ABORTED = ABORTED; Mine.WON = WON; Mine.prototype.parse = function(map) { var lines, i, width, height, pair, x, line; this.orig_map = map; lines = map.split(/\r\n?|\r?\n/); width = 0; height = lines.length; for (i = 0; i < lines.length; ++i) { if (0 == lines[i].length) { height = i; lines.splice(i, 1); break; } width = Math.max(width, lines[i].length); } this.height = height; this.width = width; this.map = map = lines.splice(0, height).reverse(); this.lambdas = 0; this.found_lambdas = 0; this.moves = 0; this.score = 0; this.moves_below_water = 0; this.lift = this.robot = false; for (i = 0; i < height; ++i) { // padding if (map[i].length < width) map[i] += repeat(' ', width - map[i].length); line = map[i] = map[i].split(''); // scan for (x = 0; x < width; ++x) { switch (line[x]) { case ' ': case '*': case '#': case '.': break; case '\\': this.lambdas++; break; case 'L': if (this.lift !== false) throw "Only one lift is allowed"; this.lift = { x: x, y: i }; break; case 'R': if (this.robot !== false) throw "Only one robot is allowed"; this.robot = { x: x, y: i }; break; } } } // if (this.lift === false) throw "Need a lift"; if (this.robot === false) throw "Need a robot"; // meta data this.meta = defaultMeta(); for (i = 0; i < lines.length; ++i) { if (0 == lines[i].length) continue; pair = lines[i].split(/ +/); this.meta[pair[0]] = pair.splice(1).join(" "); } this.state = ALIVE; }; Mine.prototype.get = function (x, y) { if (x < 0 || y < 0 || x >= this.width || y >= this.height) return '#'; return this.map[y][x]; } Mine.prototype.validMove = function (command) { if (this.state != ALIVE) return false; var n; command = command.toUpperCase(); switch (command) { case 'L': case 'R': n = (command == 'L' ? -1 : 1); if (this.robot.x + n < 0) return false; if (this.robot.x + n >= this.width) return false; switch (this.map[this.robot.y][this.robot.x+n]) { case '#': return false; case ' ': case '.': case '\\': return true; case 'L': return 0 == this.lambdas; case '*': if (this.robot.x + 2*n < 0) return false; if (this.robot.x + 2*n >= this.width) return false; if (' ' == this.map[this.robot.y][this.robot.x+2*n]) return true; } break; case 'U': case 'D': n = (command == 'D' ? -1 : 1); if (this.robot.y + n < 0) return false; if (this.robot.y + n >= this.height) return false; switch (this.map[this.robot.y+n][this.robot.x]) { case '#': return false; case ' ': case '.': case '\\': return true; case 'L': return 0 == this.lambdas; case '*': return false; " style="} break; case 'W': case 'A': return true; } return false; }; Mine.prototype.move = function(command) { if (this.state != ALIVE) return false; var n; command = command.toUpperCase(); if (this.validMove(command)) { switch (command.toUpperCase()) { case 'L': case 'R': n = (command == 'L' ? -1 : 1); this.map[this.robot.y][this.robot.x] = ' '; switch (this.map[this.robot.y][this.robot.x+n]) { case '*': this.map[this.robot.y][this.robot.x+2*n] = '*'; break; case '\\': this.lambdas--; this.found_lambdas++; break; case 'L': this._foundLift(); break; } this.robot.x += n; this.map[this.robot.y][this.robot.x] = 'R'; break; case 'U': case 'D': n = (command == 'D' ? -1 : 1); this.map[this.robot.y][this.robot.x] = ' '; switch (this.map[this.robot.y+n][this.robot.x]) { case '\\': this.lambdas--; this.found_lambdas++; break; case 'L': this._foundLift(); break; } this.robot.y += n; this.map[this.robot.y][this.robot.x] = 'R'; break; case 'A': this._abort(); return; } } this.moves++; var newMap = [], x, y, below; for (y = 0; y < this.height; ++y) { newMap[y] = this.map[y].slice(); } for (y = 0; y < this.height; ++y) { for (x = 0; x < this.width; ++x) { if ('*' == this.map[y][x]) { below = this.get(x, y-1); if (' ' == below) { // fall down newMap[y-1][x] = '*'; newMap[y][x] = ' '; if ('R' == this.get(x, y-2)) this._crushed(); } else if ((below == '*' || below == '\\') && ' ' == this.get(x+1, y-1) && ' ' == this.map[y][x+1]) { // fall right newMap[y-1][x+1] = '*'; newMap[y][x] = ' '; if ('R' == this.get(x+1, y-2)) this._crushed(); } else if ((below == '*') && ' ' == this.get(x-1, y-1) && ' ' == this.map[y][x-1]) { // fall left newMap[y-1][x-1] = '*'; newMap[y][x] = ' '; if ('R' == this.get(x-1, y-2)) this._crushed(); } } } } this.map = newMap; if (this.robot.y < this.meta['Water']) { this.moves_below_water++; if (this.moves_below_water > this.meta['Waterproof']) this._drown(); } else { this.moves_below_water = 0; } if (this.meta['Flooding'] > 0 && 0 == (this.moves % this.meta['Flooding'])) { this.meta['Water']++; } switch (this.state) { case LOST: this.score = 25*this.found_lambdas - this.moves; break; case WON: this.score = 75*this.found_lambdas - this.moves; break; case ALIVE: case ABORTED: this.score = 50*this.found_lambdas - this.moves; break; } }; Mine.prototype._drown = function() { if (this.state < LOST) { this.state = LOST; this.reason = "Drowned"; } }; Mine.prototype._crushed = function() { if (this.state < LOST) { this.state = LOST; this.reason = "Crushed by rock"; } }; Mine.prototype._foundLift = function() { if (this.state < WON) { this.state = WON; this.reason = "Found lift"; } }; Mine.prototype._abort = function() { if (this.state < ABORTED) { this.state = ABORTED; this.reason = "Aborted"; } }; Mine.prototype.toString = function() { return this.map.map(function(l) { return l.join(''); }).reverse().join("\n"); }; Mine.prototype.metaText = function() { var k, keys = [], lines = []; for (k in this.meta) { if (this.meta.hasOwnProperty(k)) keys.push(k); } keys.sort(); for (k = 0; k < keys.length; ++k) { lines[k] = keys[k] + " " + this.meta[keys[k]]; } return lines.join("\n"); }; return Mine; }(); if (typeof exports !== "undefined") { exports.Mine = Mine; }