|
|
@@ -0,0 +1,210 @@
|
|
|
+
|
|
|
+function readAll(cb) {
|
|
|
+ require("fs").readFile('./input', 'utf8', (err, data) => {
|
|
|
+ var world = new World(),
|
|
|
+ j =0;
|
|
|
+ data.split("\n").forEach(l => {
|
|
|
+ if (l.length) {
|
|
|
+ var line = [];
|
|
|
+ for (var i =0, len = l.length; i < len; ++i) {
|
|
|
+ var c = l.charAt(i);
|
|
|
+ line.push(c == '#' ? '#' : '.');
|
|
|
+ if ("#.".indexOf(c) == -1) {
|
|
|
+ var u = new Unit(c, i, j);
|
|
|
+ world.units.push(u);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ world.area.push(line);
|
|
|
+ ++j;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ cb(world);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function World() {
|
|
|
+ this.area = [];
|
|
|
+ this.units = [];
|
|
|
+ this.age = -1;
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.sortUnits = function() {
|
|
|
+ this.units.sort((a, b) => (a.py == b.py ? a.px -b.px : a.py -b.py));
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.step = function() {
|
|
|
+ this.sortUnits();
|
|
|
+ this.units.forEach(u => u.hp ? u.step(this) : null);
|
|
|
+ ++this.age;
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.isEmpty = function(px, py) {
|
|
|
+ if (this.area[py][px] == '#')
|
|
|
+ return false;
|
|
|
+ return !this.units.some(u => u.hp && u.px == px && u.py == py);
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.coordExists = function(px, py) {
|
|
|
+ return px >= 0 && py >= 0 &&
|
|
|
+ px < this.area[0].length &&
|
|
|
+ py < this.area.length;
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.isReachable = function(from, to) {
|
|
|
+ var map = [],
|
|
|
+ toGo = [{ pos: from, dist: 0}],
|
|
|
+ done = {};
|
|
|
+ if (from[0] == to[0] && from[1] == to[1])
|
|
|
+ return { step: from, dist: 0 };
|
|
|
+ while (toGo.length) {
|
|
|
+ var pos = toGo[0].pos,
|
|
|
+ dist = toGo[0].dist,
|
|
|
+ moves = [[ 0, -1 ], [ -1, 0 ], [ 1, 0 ], [ 0, 1 ]];
|
|
|
+ for (var i =0; i < moves.length; ++i) {
|
|
|
+ var newCoords = [pos[0] +moves[i][0], pos[1] +moves[i][1]];
|
|
|
+ if (this.coordExists(newCoords[0], newCoords[1])) {
|
|
|
+ if (newCoords[0] == to[0] && newCoords[1] == to[1])
|
|
|
+ return { step: pos, dist: dist +1 };
|
|
|
+ toGo.push({pos: newCoords, dist: dist +1})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ done[pos[0]+'x'+pos[1]] = dist;
|
|
|
+ toGo.splice(0, 1);
|
|
|
+ toGo = toGo.filter(c =>
|
|
|
+ !done[c.pos[0]+'x'+c.pos[1]] &&
|
|
|
+ this.isEmpty(c.pos[0], c.pos[1]));
|
|
|
+ toGo.sort((a, b) => a.dist -b.dist);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.print = function() {
|
|
|
+ var w = [];
|
|
|
+
|
|
|
+ this.sortUnits();
|
|
|
+ this.area.forEach(l => w.push(l.slice()));
|
|
|
+ this.units.forEach(u => u.hp ? w[u.py][u.px] = u.type : null);
|
|
|
+ w.forEach(l => {
|
|
|
+ var lStr = "";
|
|
|
+ l.forEach(c => lStr += c);
|
|
|
+ console.log(lStr);
|
|
|
+ });
|
|
|
+ console.log(this.units.filter(u => u.hp));
|
|
|
+ console.log("Age: ", this.age);
|
|
|
+ var sumHp = 0;
|
|
|
+ this.units.forEach(u => sumHp += u.hp);
|
|
|
+ console.log("Sum hp: ", sumHp);
|
|
|
+ console.log("Outcome: ", sumHp *this.age);
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.remainingTypes = function() {
|
|
|
+ var types = {};
|
|
|
+ this.units.forEach(u => u.hp ? types[u.type] = true : false);
|
|
|
+ return Object.keys(types);
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.hasOpponents = function() {
|
|
|
+ return this.remainingTypes().length > 1;
|
|
|
+}
|
|
|
+
|
|
|
+World.prototype.run = function(stopFnc) {
|
|
|
+ while (stopFnc(this))
|
|
|
+ this.step();
|
|
|
+}
|
|
|
+
|
|
|
+function Unit(type, px, py) {
|
|
|
+ this.type = type;
|
|
|
+ this.px = px;
|
|
|
+ this.py = py;
|
|
|
+ this.hp = 200;
|
|
|
+ this.atk = 3;
|
|
|
+}
|
|
|
+
|
|
|
+Unit.prototype.step = function(world) {
|
|
|
+ this.move(world);
|
|
|
+ this.attack(world);
|
|
|
+}
|
|
|
+
|
|
|
+Unit.prototype.range = function(world, except) {
|
|
|
+ var range = [],
|
|
|
+ moves = [[ 0, -1 ], [ -1, 0 ], [ 1, 0 ], [ 0, 1 ]];
|
|
|
+ moves.forEach(m => {
|
|
|
+ if (world.coordExists(this.px +m[0], this.py +m[1]) && (world.isEmpty(this.px +m[0], this.py +m[1]) || (except && (except.px == this.px +m[0] && except.py == this.py +m[1]))))
|
|
|
+ range.push([this.px +m[0], this.py +m[1]]);
|
|
|
+ });
|
|
|
+ return range;
|
|
|
+}
|
|
|
+
|
|
|
+Unit.prototype.move = function(world) {
|
|
|
+ var inRange = [];
|
|
|
+
|
|
|
+ world.units.filter(u => u.hp && u.type !== this.type).forEach(t => t.range(world, this).forEach(r => inRange.push({ unit: t, pos: r })));
|
|
|
+ var reachable = inRange.filter(r => { r.dist = world.isReachable(r.pos, [this.px, this.py]); return r.dist !== false; });
|
|
|
+ var dest = reachable.sort((a, b) => {
|
|
|
+ if (a.dist.dist != b.dist.dist) return a.dist.dist -b.dist.dist;
|
|
|
+ if (a.pos[1] == b.pos[1]) return a.pos[0] -b.pos[0];
|
|
|
+ return a.pos[1] -b.pos[1];
|
|
|
+ })[0];
|
|
|
+ if (dest) {
|
|
|
+ this.px = dest.dist.step[0];
|
|
|
+ this.py = dest.dist.step[1];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+Unit.prototype.attack = function(world) {
|
|
|
+ var units = world.units.filter(u => u.hp && u.type !== this.type && (Math.abs(u.px -this.px) +Math.abs(u.py -this.py)) == 1);
|
|
|
+ if (units.length) {
|
|
|
+ units.sort((a, b) => {
|
|
|
+ var diff = a.hp -b.hp;
|
|
|
+ if (diff) return diff;
|
|
|
+ diff = a.py -b.py;
|
|
|
+ if (diff) return diff;
|
|
|
+ return a.px -b.px;
|
|
|
+ });
|
|
|
+ units[0].takeDamage(this);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+Unit.prototype.takeDamage = function(from) {
|
|
|
+ this.hp = Math.max(0, this.hp -from.atk);
|
|
|
+}
|
|
|
+
|
|
|
+function ex1() {
|
|
|
+ readAll(world => {
|
|
|
+ world.run(world => world.hasOpponents());
|
|
|
+ world.print();
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function ex2(min, max) {
|
|
|
+ readAll(world => {
|
|
|
+ var stopReason = -1,
|
|
|
+ current = Math.floor((min +max) /2);
|
|
|
+ world.units.forEach(u => u.type === 'E' ? u.atk = current : 0);
|
|
|
+ world.run(world => {
|
|
|
+ if (!world.hasOpponents()) {
|
|
|
+ stopReason = 'E';
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (world.units.filter(u => u.hp === 0 && u.type === 'E').length) {
|
|
|
+ stopReason = 'G';
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ if (max == min || (min +1 == current && stopReason === 'E')) {
|
|
|
+ console.log("Min atk: " +(stopReason === 'E' ? min : min +1));
|
|
|
+ world.print();
|
|
|
+ } else if (stopReason == 'G') {
|
|
|
+ console.log("Atk " +current +" not enough");
|
|
|
+ ex2(current +1, max);
|
|
|
+ } else {
|
|
|
+ console.log("Atk " +current +" too much");
|
|
|
+ ex2(min, current);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+ex1();
|
|
|
+ex2(4, 300);
|
|
|
+
|