@@ -3,6 +3,8 @@ const enumify = require('enumify');
3
3
4
4
class MapObject extends enumify . Enum { }
5
5
6
+ const DEFAULT_POWER = 3 ;
7
+
6
8
MapObject . initEnum ( {
7
9
WALL : {
8
10
get available ( ) { return false ; } ,
@@ -63,6 +65,10 @@ class Map {
63
65
get alivePlayers ( ) {
64
66
return this . players . filter ( player => player . isAlive ) ;
65
67
}
68
+
69
+ getElves ( ) {
70
+ return this . players . filter ( p => p . symbol == 'E' ) ;
71
+ }
66
72
}
67
73
68
74
class Player {
@@ -96,109 +102,72 @@ class Player {
96
102
}
97
103
98
104
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 ) {
121
115
this . x = nextMove . x ;
122
116
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*/);
138
117
}
139
118
}
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
- } ) ;
147
119
}
148
120
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 = { } ;
157
126
let visited = { } ;
158
- visited [ `${ player . x } ,${ player . y } ` ] = true ;
159
127
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 ;
175
147
}
176
- visited [ xy ] = true ; // mark as visited so other paths ignore it
177
- } ) ;
148
+ }
178
149
} ) ;
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
+ }
179
157
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 ] ;
196
163
}
164
+ return { x :+ prevStep . split ( ',' ) [ 0 ] , y :+ prevStep . split ( ',' ) [ 1 ] } ;
197
165
}
198
166
199
167
class Elf extends Player {
200
168
constructor ( x , y ) {
201
169
super ( x , y ) ;
170
+ this . power = elfAttackPower ;
202
171
}
203
172
get symbol ( ) { return 'E' ; }
204
173
}
@@ -212,28 +181,30 @@ class Goblin extends Player {
212
181
}
213
182
214
183
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 ) {
216
200
map = new Map ( input ) ;
217
- //map.print();
218
201
let finished = false ;
219
202
let rounds = 0 ;
220
- while ( ! finished ) {
203
+ while ( ! ( finished || rounds >= roundCap ) ) {
221
204
rounds ++ ;
222
205
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
- }
232
206
}
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 ;
237
208
}
238
209
239
210
function round ( ) {
@@ -260,26 +231,17 @@ function round() {
260
231
261
232
function getAdjacentAvailableSpace ( x , y ) {
262
233
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 }
267
238
] ;
239
+
268
240
return adjacents
269
241
. 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
282
245
}
283
246
284
- module . exports . game = game ;
285
- module . exports . getPlayer1InRange = getPlayer1InRange ;
247
+ module . exports . game = game ;
0 commit comments