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 (6411B)


      1 const fs           = require('fs');
      2 const through      = require('through2');
      3 const line_by_line = require('../common/line-for-line');
      4 
      5 String.prototype.startsWith = function(subject) {
      6   return this.substr(0, subject.length) === subject;
      7 };
      8 
      9 function s(nodes, l) {
     10   return nodes.map(n => [n[0]+2,n[1]+l+3]);
     11 }
     12 
     13 let cave  = [];
     14 // const cave  = [ // Test pattern
     15 //   0b000000000,
     16 //   0b001110100,
     17 //   0b000000000,
     18 //   0b001000100,
     19 //   0b001111100,
     20 //   0b001000100,
     21 //   0b000000000,
     22 //   0b001111100,
     23 //   0b000010000,
     24 //   0b001111100,
     25 //   0b000000000,
     26 // ];
     27 const rocks = [
     28   [
     29     0b1111000,
     30   ],
     31   [
     32     0b010000,
     33     0b111000,
     34     0b010000,
     35   ],
     36   [
     37     0b111000, // Yes, it's flipped XY
     38     0b100000, // LSB = left
     39     0b100000, // i0 = bottom
     40   ],
     41   [
     42     0b1000,
     43     0b1000,
     44     0b1000,
     45     0b1000,
     46   ],
     47   [
     48     0b11000,
     49     0b11000,
     50   ],
     51 ];
     52 
     53 function printCave() {
     54   let i = cave.length + 6;
     55   while(--i >= 0) {
     56     process.stdout.write('|');
     57     for(let x=1; x<=7; x++) {
     58       if ( (rock[i - rp]|0) & (1<<x) ) {
     59         process.stdout.write('@');
     60       } else if ( (cave[i]|0) & (1<<x) ) {
     61         process.stdout.write('#');
     62       } else {
     63         process.stdout.write('.');
     64         // process.stdout.write(' ');
     65       }
     66     }
     67     process.stdout.write('|\n');
     68   }
     69   process.stdout.write('+-------+\n');
     70 }
     71 
     72 // How large should our cache key be
     73 const lookback = 40;
     74 
     75 // Fetch jet pattern
     76 const pattern = fs.readFileSync('input', 'utf-8');
     77 // const pattern = fs.readFileSync('sample', 'utf-8');
     78 const jets    = [];
     79 for(const c of pattern.split('')) {
     80   if (c == '>') jets.push(2);
     81   if (c == '<') jets.push(0);
     82 }
     83 
     84 // Tracker for the next jet
     85 let   j  = 0;
     86 const jl = jets.length;
     87 const J  = pattern.trim().repeat(3);
     88 
     89 // Rock statistics
     90 let   rockCount  = 0;
     91 // const rockTarget = 10000000000;
     92 const rockTarget = 1000000000000;
     93 // const rockTarget = 2022;
     94 let   rockHeight = 0;
     95 
     96 
     97 // Tracker for falling rocks
     98 let   r    = 0;
     99 const rl   = rocks.length;
    100 let   rp   = 0;
    101 let   rock = false;
    102 
    103 let storeCache = 0;
    104 const cache = {};
    105 const cycle = jets.length * rocks.length;
    106 let  cc = 0;
    107 
    108 // Time tracking to give an estimate & start
    109 const startTime = Date.now();
    110 while(rockCount < rockTarget) {
    111 
    112   let cacheKey = r.toString(16) + cave.map(l => ('0'+l.toString(16)).substr(-2)).join('');
    113   if (cache[cacheKey]) {
    114 
    115     // Merge 2 cache keys, skipping a simulation step
    116     const nextKey = cache[cacheKey].n;
    117     if (cache[cache[cacheKey].n]) {
    118       const nextKey = cache[cacheKey].n;
    119       cache[cacheKey].cave  = cache[nextKey].cave;
    120       cache[cacheKey].j    += cache[nextKey].j;
    121       cache[cacheKey].c    += cache[nextKey].c;
    122       cache[cacheKey].h    += cache[nextKey].h;
    123       cache[cacheKey].n     = cache[nextKey].n;
    124     }
    125 
    126     if (rockCount + cache[cacheKey].c <= rockTarget) {
    127       cave        = [...cache[cacheKey].cave];
    128       j           = (j + cache[cacheKey].j) % jl;
    129       r           = (r + cache[cacheKey].c) % rl;
    130       rockCount  += cache[cacheKey].c;
    131       rockHeight += cache[cacheKey].h;
    132       cacheKey    = cache[cacheKey].n;
    133       continue;
    134     } else {
    135       // console.log("FAIL", cacheKey, cache[cacheKey]);
    136     }
    137   } else {
    138     // console.log("MISS", cacheKey);
    139   }
    140 
    141   if (cc > cycle) {
    142     storeCache = true;
    143   } else {
    144     cc += 1;
    145   }
    146 
    147 
    148   // let stateKey = r.toString(16);
    149   // for(let i=0; i<cave.length; i++) {
    150   //   stateKey += ('0' + cave[i].toString(16)).substr(-2);
    151   //   stateKey += cave[i].toString(16);
    152   // }
    153   // if ((cave.length == lookback) && cache[stateKey]) {
    154   //   let hit = false;
    155   //   // console.log({ J, sub: J.substr(j, cache[stateKey].J.length), j: cache[stateKey].J })
    156   //   if (J.substr(j, cache[stateKey].j) == cache[stateKey].J) {
    157   //     cave        = [...cache[stateKey].cave];
    158   //     j           = (j + cache[stateKey].j) % jl;
    159   //     rockCount  += 1;
    160   //     rockHeight += cache[stateKey].h;
    161   //     // console.log({ c: cache[stateKey] });
    162   //     continue;
    163   //   }
    164   //   // printCave();
    165   //   // continue;
    166   // }
    167 
    168   let rockJets = '';
    169 
    170   // if (cache[stateKey]) console.log(`Got state cache for ${stateKey} (${Object.values(cache).length})`);
    171   // console.log( stateKey );
    172   // cache[stateKey] = true;
    173 
    174   // Spawn new rock
    175   rock = rocks[r];
    176   r = (r+1) % rl;
    177   rp = cave.length + 3;
    178 
    179   // console.log(`\nA new rock begins falling:`);
    180   // printCave();
    181 
    182   while(rock) {
    183 
    184     // Fetch jet movement
    185     const jet = jets[j];
    186     j = (j+1) % jl;
    187     rockJets += jet ? '>' : '<';
    188 
    189     // Calculate jet-based movement
    190     let newRock = [];
    191     for(let i=0; i<rock.length; i++) {
    192       newRock[i] = (rock[i] >> 1) << jet;
    193     }
    194 
    195     // Wall collision
    196     let hitWall = false;
    197     for(let i=0; i<newRock.length; i++) {
    198       const check = 1 | (cave[i+rp]|0) | 256; // Bitmap of current height
    199       if (newRock[i] & check) { hitWall = true; break; }
    200     }
    201 
    202     // Move if possible
    203     if (!hitWall) rock = newRock;
    204 
    205     // console.log(`\nJet of gas pushes the rock ${jet ? 'right' : 'left'}${(rock === newRock) ? '' : ', but nothing happens'}:`);
    206     // printCave();
    207 
    208     // Calculate gravity-based movement
    209     rp--;
    210     let hitBottom = rp < 0; // Floor check
    211     if (!hitBottom) {
    212       for(let i=0; i<rock.length; i++) {
    213         const check = 1 | (cave[i+rp]|0) | 256; // Bitmap of current height
    214         if (rock[i] & check) { hitBottom = true; break; } // Rock check
    215       }
    216     }
    217 
    218     // Move or block
    219     if (hitBottom) {
    220       rp++; // Move back
    221 
    222       // Solidify
    223       const before = cave.length;
    224       for(let i=0; i<rock.length; i++) {
    225         if (!cave[rp+i]) cave[rp+i] = 0; // Initialize cave area
    226         cave[rp+i] |= rock[i];           // Overlay rock
    227       }
    228       rock = false;
    229 
    230       rockCount  += 1;
    231       const diff = cave.length - before;
    232       rockHeight += diff;
    233 
    234       cave = cave.slice(-lookback);
    235       let newKey = (r%rl).toString(16) + cave.map(l => ('0'+l.toString(16)).substr(-2)).join('');
    236 
    237       if (storeCache) {
    238         cache[cacheKey] = {
    239           n: newKey,
    240           c: 1,
    241           cave: [...cave],
    242           h: diff,
    243           j: rockJets.length,
    244         };
    245       }
    246 
    247 
    248       // console.log(`\nRock falls 1 unit, causing it to come to rest:`);
    249       // printCave();
    250 
    251     } else {
    252       // console.log(`\nRock falls 1 unit:`);
    253       // printCave();
    254     }
    255 
    256   }
    257 
    258 }
    259 
    260 console.log(`\nStatus report:`);
    261 console.log(`  Rock count - ${rockCount}`);
    262 console.log(`  Height     - ${rockHeight}`);