index.js (6360B)
1 const fs = require('fs'); 2 3 const directions = { 4 '0,-1': '^', 5 '1,0' : '>', 6 '0,1' : 'v', 7 '-1,0': '<', 8 }; 9 10 function mod(n, m) { 11 return ((n%m)+m)%m; 12 } 13 14 function clone(subject) { 15 return JSON.parse(JSON.stringify(subject)); 16 } 17 18 function drawmap(width, height, init, elf, blizzards) { 19 for(let y=-1; y<=height; y++) { 20 for(let x=-1; x<=width; x++) { 21 22 if (elf[0] == x && elf[1] == y) { 23 if(blizzards.find(blizzard => blizzard.x == x && blizzard.y == y)) { 24 process.stdout.write('D'); 25 } else { 26 process.stdout.write('E'); 27 } 28 continue; 29 } 30 31 let here = blizzards.filter(blizzard => blizzard.x == x && blizzard.y == y); 32 if (here.length > 9) { process.stdout.write('X'); continue; } 33 if (here.length > 1) { process.stdout.write(here.length.toString()); continue; } 34 if (here.length == 1) { process.stdout.write(directions[here[0].d.join(',')]); continue; } 35 36 if (init.start[0] == x && init.start[1] == y) { 37 process.stdout.write('.'); 38 continue; 39 } 40 41 if (init.target[0] == x && init.target[1] == y) { 42 process.stdout.write('.'); 43 continue; 44 } 45 46 if (y == -1 ) { process.stdout.write('#'); continue; } 47 if (x == -1 ) { process.stdout.write('#'); continue; } 48 if (y == height) { process.stdout.write('#'); continue; } 49 if (x == width ) { process.stdout.write('#'); continue; } 50 51 process.stdout.write('.'); 52 } 53 process.stdout.write('\n'); 54 } 55 } 56 57 // const input = fs.readFileSync('sample1', 'utf-8') 58 // const input = fs.readFileSync('sample2', 'utf-8') 59 const input = fs.readFileSync('input', 'utf-8') 60 .split('\n') 61 .filter(l => l) 62 63 // Parse starting location 64 const init = {}; 65 init.start = [ input.shift().indexOf('.') - 1, -1]; 66 init.target = [ input.pop().indexOf('.') - 1, input.length ]; 67 init.blizzards = []; 68 69 // Fetch map size 70 const height = input.length; 71 const width = input[0].length - 2; 72 73 // Parse blizzards 74 for(let y=0; y<input.length; y++) { 75 const line = input[y].substr(1, input[y].length - 2); 76 for(let x=0; x<line.length; x++) { 77 switch(line.charAt(x)) { 78 case '^': init.blizzards.push({ d: [ 0,-1], x, y }); break; 79 case '>': init.blizzards.push({ d: [ 1, 0], x, y }); break; 80 case 'v': init.blizzards.push({ d: [ 0, 1], x, y }); break; 81 case '<': init.blizzards.push({ d: [-1, 0], x, y }); break; 82 } 83 } 84 } 85 86 // console.log(init); 87 // console.log(blizzards); 88 89 function findPath(from, to, starttime) { 90 const had = []; 91 let mintime = Infinity; 92 let minpath = null; 93 let openset = [[...from, starttime, [from]]]; 94 let counter = 0; 95 let logscore = Infinity; 96 let logdist = Infinity; 97 let logtime = Infinity; 98 let loglength = Infinity; 99 while(openset.length) { 100 const p = openset.pop(); 101 let [x,y,time,hist] = p; 102 103 // Calculate distance to go 104 const dist = Math.abs(x - to[0]) + Math.abs(y - to[1]); 105 106 // // Handle logging to show progress 107 // if (dist < logdist ) logdist = dist; 108 // if (openset.length < loglength) loglength = openset.length; 109 // if (time < logtime ) logtime = time; 110 // if ((time+dist) < logscore ) logscore = (time+dist); 111 // if (counter++ >= 1e3) { 112 // console.log({ 113 // score: logscore, 114 // l: loglength, 115 // time: logtime, 116 // dist: logdist, 117 // }); 118 // logdist = loglength = logtime = logscore = Infinity; 119 // counter = 0; 120 // } 121 122 // Stop if we're certainly not making the record 123 if ((dist + time) > mintime) continue; 124 time++; 125 126 // Calculate state at location 127 const blizzards = clone(init.blizzards); 128 for(const blizzard of blizzards) { 129 blizzard.x = mod(blizzard.x + (blizzard.d[0] * time), width ); 130 blizzard.y = mod(blizzard.y + (blizzard.d[1] * time), height); 131 } 132 133 // Calculate possible moves 134 let tries = [ 135 [ p[0] , p[1] - 1 ], 136 [ p[0] - 1, p[1] ], [ p[0] , p[1] ], [ p[0] + 1, p[1] ], 137 [ p[0] , p[1] + 1 ], 138 ]; 139 140 for(const pos of tries) { 141 142 // Found target 143 if (pos[0] == to[0] && pos[1] == to[1]) { 144 // console.log(`finished in ${time} minutes`); 145 if (time < mintime) { 146 // console.log('new record', time); 147 mintime = time; 148 minpath = [...hist, pos]; 149 } 150 continue; 151 } 152 153 // Out-of-bounds 154 const isStart = pos[0] == from[0] && pos[1] == from[1]; 155 if (pos[0] < 0 && !isStart) continue; 156 if (pos[1] < 0 && !isStart) continue; 157 if (pos[0] >= width && !isStart) continue; 158 if (pos[1] >= height && !isStart) continue; 159 160 // Skip already added 161 had[pos[1]] = had[pos[1]] || []; 162 had[pos[1]][pos[0]] = had[pos[1]][pos[0]] || []; 163 if (had[pos[1]][pos[0]][time]) continue; 164 had[pos[1]][pos[0]][time] = true; 165 166 // Occupied by blizzard 167 if (blizzards.find(blizzard => blizzard.x == pos[0] && blizzard.y == pos[1])) { 168 continue; 169 } 170 171 // Add to the open set 172 openset.push([...pos, time, [...hist, pos]]); 173 } 174 175 // Order ASC by xdist + ydist 176 openset.sort((a,b) => { 177 const ad = Math.abs(a[0] - to[0]) + Math.abs(a[1] - to[1]); 178 const bd = Math.abs(b[0] - to[0]) + Math.abs(b[1] - to[1]); 179 return bd - ad; 180 }); 181 // openset.sort((a, b) => a[2] - b[2]); 182 // console.log(openset.map(s => Math.abs(s[0] - to[0]) + Math.abs(s[1] - to[1]) + s[2])); 183 184 // Limit openset length by removing worst options 185 while (openset.length > 1e4) { 186 openset.shift(); 187 } 188 } 189 190 return minpath.length - 1; 191 } 192 193 // const pass1 = 299; 194 const pass1 = findPath(init.start , init.target, 0); 195 const pass2 = findPath(init.target, init.start , pass1); 196 const pass3 = findPath(init.start , init.target, pass1 + pass2); 197 198 // const minblizzards = clone(init.blizzards); 199 // for(let i=0; i<minpath.length; i++) { 200 201 // process.stdout.write(`\n== Minute ${i} ==\n`); 202 // drawmap(width, height, init, minpath[i], minblizzards); 203 204 // for(const blizzard of minblizzards) { 205 // blizzard.x = mod(blizzard.x + blizzard.d[0], width ); 206 // blizzard.y = mod(blizzard.y + blizzard.d[1], height); 207 // } 208 // } 209 210 console.log({ 211 pass1, 212 pass2, 213 pass3, 214 total: pass1 + pass2 + pass3, 215 });