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