Skip to content

Commit 861a4b6

Browse files
committed
Tidied up Day 15
1 parent 301dda0 commit 861a4b6

File tree

2 files changed

+175
-145
lines changed

2 files changed

+175
-145
lines changed

day-15-beverage-bandits/combat.js

+82-120
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const enumify = require('enumify');
33

44
class MapObject extends enumify.Enum {}
55

6+
const DEFAULT_POWER = 3;
7+
68
MapObject.initEnum({
79
WALL: {
810
get available() { return false; },
@@ -63,6 +65,10 @@ class Map {
6365
get alivePlayers() {
6466
return this.players.filter(player => player.isAlive);
6567
}
68+
69+
getElves() {
70+
return this.players.filter(p => p.symbol == 'E');
71+
}
6672
}
6773

6874
class Player {
@@ -96,109 +102,72 @@ class Player {
96102
}
97103

98104
move() {
99-
// let inRange = new Set(this.getEnemies().map(player => getAdjacentAvailableSpace(player.x, player.y))
100-
// .reduce((acc, spaces) => acc.concat(...spaces),[]));
101-
// let targets = {};
102-
// inRange.forEach(position => {
103-
// targets[`${position.x},${position.y}`] = undefined;
104-
// });
105-
// //console.log('inRange',inRange);
106-
// //Find out which paths we can get to
107-
108-
// //console.log('targets',targets);
109-
// getPaths(targets, new Set(), this.x,this.y, []);
110-
111-
// let orderedTargets = Object.values(targets).filter(x => x).map(array => array.slice(1)).sort((a,b) => a.length - b.length || a[0].y - b[0].y || a[0].x - b[0].x);
112-
let altMove = findNextMovement(this);
113-
// if (orderedTargets.length > 0) {
114-
// if (orderedTargets[0][0].x != altMove.x || orderedTargets[0][0].y != altMove.y) {
115-
// console.log('orderedTargets',orderedTargets[0][0], altMove);
116-
// }
117-
// const nextMove = orderedTargets[0][0];
118-
if (altMove != null) {
119-
let nextMove = altMove;
120-
//console.log('next move',orderedTargets[0][0]);
105+
let inRange = new Set(this.getEnemies().map(player => getAdjacentAvailableSpace(player.x, player.y))
106+
.reduce((acc, spaces) => acc.concat(...spaces),[]));
107+
let targets = {};//Change to set
108+
inRange.forEach(position => {
109+
targets[`${position.x},${position.y}`] = undefined;
110+
});
111+
112+
let nextMove = findPaths(targets, this.x, this.y);
113+
114+
if (nextMove != null) {
121115
this.x = nextMove.x;
122116
this.y = nextMove.y;
123-
} else if(altMove != null) {
124-
console.log('>>>>>>>>>no ordered targets:',altMove);
125-
}
126-
}
127-
}
128-
129-
function getPaths(targets, seen, x, y, pathSoFar) {
130-
if (pathSoFar.length > 100) return;
131-
let curr =`${x},${y}`;
132-
seen.add(curr);
133-
pathSoFar.push({x:x,y:y});
134-
if (Object.keys(targets).includes(curr)) {
135-
if (!targets[curr] || pathSoFar.length < targets[curr].length) {
136-
targets[curr] = pathSoFar.slice();
137-
//console.log('found better target', curr, 'path', pathSoFar/*, targets*/);
138117
}
139118
}
140-
//console.log('Now at', curr, /*Object.keys(targets).includes(curr), 'targets',targets,*/ pathSoFar/*, seen*/);
141-
let newAdj = getAdjacentAvailableSpace(x, y).filter(pos => !seen.has(`${pos.x},${pos.y}`));
142-
//console.log(curr,':newAdj', newAdj);
143-
newAdj.forEach(pos => {
144-
//console.log(curr,'about to visit',pos);
145-
getPaths(targets, new Set(seen), pos.x, pos.y, pathSoFar.slice());
146-
});
147119
}
148120

149-
//copied from https://github.com/albertobastos/advent-of-code-2018-nodejs/blob/master/src/d15.js - same result as mine, but much much faster
150-
function findNextMovement(player) {
151-
let targetKeys = {}; // "x,y" ==> { x, y } of alive enemy
152-
player.getEnemies()
153-
.map(p => getAdjacentAvailableSpace(p.x, p.y))
154-
.reduce((acc, list) => acc.concat(...list), [])
155-
.forEach(pos => (targetKeys[`${pos.x},${pos.y}`] = pos));
156-
121+
//Algo from https://www.geeksforgeeks.org/shortest-path-unweighted-graph/
122+
function findPaths(targets, x, y) {
123+
let queue = [];
124+
let dist = {};
125+
let pred = {};
157126
let visited = {};
158-
visited[`${player.x},${player.y}`] = true;
159127

160-
let paths = [[{x: player.x,y:player.y}]];
161-
while (true) {
162-
let newPaths = [];
163-
let targetPaths = [];
164-
paths.forEach(path => {
165-
let adjacents = getAdjacentAvailableSpace(path[path.length - 1].x, path[path.length - 1].y);
166-
adjacents.forEach(adj => {
167-
let xy = `${adj.x},${adj.y}`;
168-
if (targetKeys[xy]) {
169-
// found a path to a target!
170-
// add it so at the end of the iteration we chose the right one based on enemy order
171-
targetPaths.push([...path, adj, targetKeys[xy]]);
172-
} else if (!visited[xy]) {
173-
// new extended path to explore at next iteration
174-
newPaths.push([...path, adj]);
128+
let posString = `${x},${y}`;
129+
visited[posString] = true;
130+
dist[posString] = 0;
131+
queue.push({x:x, y:y});
132+
133+
while(queue.length > 0) {
134+
let position = queue.shift();
135+
let predString = `${position.x},${position.y}`;
136+
137+
getAdjacentAvailableSpace(position.x, position.y).forEach(adj => {
138+
let posString = `${adj.x},${adj.y}`;
139+
if (visited[posString] !== true) {
140+
visited[posString] = true;
141+
dist[posString] = dist[predString] + 1;
142+
pred[posString] = predString;
143+
queue.push({x:adj.x, y:adj.y});
144+
145+
if (Object.keys(targets).filter(target => visited[target] !== true).length == 0) {
146+
return true;
175147
}
176-
visited[xy] = true; // mark as visited so other paths ignore it
177-
});
148+
}
178149
});
150+
}
151+
let orderedTargets = Object.keys(targets).filter(target => dist[target] != undefined).sort((a,b) => {
152+
return dist[a] - dist[b] || a.split(',')[1] - b.split(',')[1] || a.split(',')[0] - b.split(',')[0];
153+
});
154+
if (orderedTargets.length == 0) {
155+
return null;
156+
}
179157

180-
if (targetPaths.length > 0) {
181-
// we got one or more paths reaching a target for the first time, here is where our search ends
182-
// if we found multiple shortest paths, use the one that reaches the first target according top-to-bottom/left-to-right order
183-
targetPaths = targetPaths.sort((p1, p2) =>
184-
p1[p1.length - 1].y === p2[p2.length - 1].y
185-
? p1[p1.length - 1].x - p2[p2.length - 1].x
186-
: p1[p1.length - 1].y - p2[p2.length - 1].y
187-
);
188-
189-
// return the first step to take for the shortest path ([0] is the player current position)
190-
return targetPaths[0][1];
191-
}
192-
193-
// no paths to a target found yet, keep iterating with the paths after one more step
194-
paths = newPaths;
195-
if (paths.length < 1) return null; // no reachables targets, search ends without a result
158+
let crawl = orderedTargets[0];
159+
let prevStep;
160+
while (pred[crawl] && crawl != `${x},${y}`) {
161+
prevStep = crawl;
162+
crawl = pred[crawl];
196163
}
164+
return {x:+prevStep.split(',')[0], y:+prevStep.split(',')[1]};
197165
}
198166

199167
class Elf extends Player {
200168
constructor(x, y) {
201169
super(x, y);
170+
this.power=elfAttackPower;
202171
}
203172
get symbol() {return 'E'; }
204173
}
@@ -212,28 +181,30 @@ class Goblin extends Player {
212181
}
213182

214183
let map;
215-
function game(input) {
184+
let elfAttackPower = DEFAULT_POWER;
185+
function game(input,noElfDies=false, roundCap = 9999999) {
186+
elfAttackPower = DEFAULT_POWER;
187+
let rounds = playGame(input,roundCap);
188+
while (noElfDies && map.getElves().filter(player => !player.isAlive).length > 0) {
189+
elfAttackPower++;
190+
rounds = playGame(input,roundCap);
191+
}
192+
193+
let hpSum = map.alivePlayers.map(player => player.hitPoints).reduce((acc, curr) => acc + curr, 0);
194+
rounds--;
195+
console.log('elfPower',elfAttackPower,'rounds', rounds,'sum',hpSum);
196+
return hpSum * rounds;
197+
}
198+
199+
function playGame(input,roundCap) {
216200
map = new Map(input);
217-
//map.print();
218201
let finished = false;
219202
let rounds = 0;
220-
while (!finished) {
203+
while (!(finished || rounds >= roundCap)) {
221204
rounds++;
222205
finished = round();
223-
//console.log('finished', finished);
224-
//check if either side is completed.
225-
//finished = new Goblin(0,0).getEnemies().length == 0 || new Elf(0,0).getEnemies().length == 0;
226-
if (rounds % 1 == 0) {
227-
console.log('After',rounds,'rounds');
228-
map.print();
229-
console.log(map.alivePlayers.sort((a, b) => a.y - b.y || a.x - b.x).map(player => `${player.symbol} ${player.x} ${player.y} ${player.hitPoints}`).join("\n"));
230-
console.log('');
231-
}
232206
}
233-
let hpSum = map.alivePlayers.map(player => player.hitPoints).reduce((acc, curr) => acc + curr, 0);
234-
rounds--;
235-
console.log('rounds', rounds,'sum',hpSum);
236-
return hpSum * rounds;
207+
return rounds;
237208
}
238209

239210
function round() {
@@ -260,26 +231,17 @@ function round() {
260231

261232
function getAdjacentAvailableSpace(x, y) {
262233
const adjacents = [
263-
{ x: x, y: y - 1 },
264-
{ x: x - 1, y: y },
265-
{ x: x + 1, y: y },
266-
{ x: x, y: y + 1 }
234+
{ x: +x, y: +y - 1 },
235+
{ x: +x - 1, y: +y },
236+
{ x: +x + 1, y: +y },
237+
{ x: +x, y: +y + 1 }
267238
];
239+
268240
return adjacents
269241
.filter(position => map.getGrid(position.x, position.y) == MapObject.EMPTY) //No wall
270-
.filter(position => !map.alivePlayers.some(player => {
271-
return player.x === position.x && player.y === position.y;
272-
})); //NO players
273-
}
274-
275-
function getPlayer1InRange(input) {
276-
map = new Map(input);
277-
map.print();
278-
let player1 = map.alivePlayers.sort((a, b) => a.y - b.y || a.x - b.x)[0];
279-
console.log('player1',player1);
280-
player1.move();
281-
map.print();
242+
.filter(position => !map.alivePlayers.some(player =>
243+
player.x == position.x && player.y == position.y
244+
)); //NO players
282245
}
283246

284-
module.exports.game = game;
285-
module.exports.getPlayer1InRange = getPlayer1InRange;
247+
module.exports.game = game;

0 commit comments

Comments
 (0)