icfp15/js/mine.js

288 lines
6.6 KiB
JavaScript

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;
}