advent-of-code

Entries to advent of code, multiple years
git clone git://git.finwo.net/misc/advent-of-code
Log | Files | Refs

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