balooga's recent activity

  1. Comment on Day 7: Laboratories in ~comp.advent_of_code

    balooga
    Link
    TypeScript Yesterday was busy for me so I'm playing catch-up now. This was the first puzzle that tripped me up! I breezed through Part 1 with a nice single-loop solution but walked right into the...

    TypeScript

    Yesterday was busy for me so I'm playing catch-up now. This was the first puzzle that tripped me up! I breezed through Part 1 with a nice single-loop solution but walked right into the trap of doing a naive recursive brute-force approach for Part 2 and hit my first OOM crash of the year.

    I guess I've "done" dynamic programming in the past but never called it that. Usually I'm thinking in terms of managing React state and minimizing re-renders by memoizing expensive function outputs, etc. I'm not used to considering this kind of problem in the same way.

    I don't think it's cheating to lean on ChatGPT a bit when you get stuck in AoC, as long as you're earnestly tackling the problem, and working through different approaches — not just generating a full solution in one go. So in full disclosure, that's the route I had to take for this Part 2. Seems like most of you figured out the right approach straightaway, but it was not intuitive to me all! Just as I predicted, lol.

    Parts 1 and 2
    import { RunFunction } from '../../../types';
    
    type InputData = string[][];
    enum Direction {
      L = -1,
      R = 1,
    }
    
    function formatInput(input: string): InputData {
      return input.split('\n').map(row => row.split(''));
    }
    
    export const run: RunFunction<InputData> = () => {
      const validatePosition = (grid: InputData, rowIndex: number, colIndex: number): boolean => {
        return rowIndex >= 0 && rowIndex < grid.length && colIndex >= 0 && colIndex < grid[0].length;
      };
    
      const countBeamSplits = (grid: InputData): number => {
        let total = 0;
        for (let rowIndex = 0; rowIndex < grid.length; rowIndex++) {
          for (let colIndex = 0; colIndex < grid[rowIndex].length; colIndex++) {
            if (grid[rowIndex][colIndex] === '|' || grid[rowIndex][colIndex] === 'S') {
              if (validatePosition(grid, rowIndex + 1, colIndex)) {
                if (grid[rowIndex + 1][colIndex] === '^') {
                  if (validatePosition(grid, rowIndex + 1, colIndex + Direction.L)) {
                    grid[rowIndex + 1][colIndex + Direction.L] = '|';
                  }
                  if (validatePosition(grid, rowIndex + 1, colIndex + Direction.R)) {
                    grid[rowIndex + 1][colIndex + Direction.R] = '|';
                  }
                  total++;
                  continue;
                }
                grid[rowIndex + 1][colIndex] = '|';
              }
            }
          }
        }
        return total;
      };
    
      const countQuantumTimelines = (grid: InputData) => {
        let total = 0n;
        const startCol = grid[0].indexOf('S');
        let curr = Array<bigint>(grid[0].length).fill(0n);
        curr[startCol] = 1n;
    
        for (let rowIndex = 0; rowIndex < grid.length; rowIndex++) {
          const next = Array<bigint>(grid[rowIndex].length).fill(0n);
    
          for (let colIndex = 0; colIndex < grid[rowIndex].length; colIndex++) {
            const count = curr[colIndex];
            if (count === 0n) {
              continue;
            }
    
            const nextRowIndex = rowIndex + 1;
            if (nextRowIndex >= grid.length) {
              total += count;
              continue;
            }
    
            if (grid[nextRowIndex][colIndex] === '^') {
              if (colIndex - 1 >= 0) {
                next[colIndex - 1] += count;
              } else {
                total += count;
              }
    
              if (colIndex + 1 < grid[rowIndex].length) {
                next[colIndex + 1] += count;
              } else {
                total += count;
              }
            } else {
              next[colIndex] += count;
            }
          }
          curr = next;
        }
    
        return total;
      };
    
      return [formatInput, countBeamSplits, countQuantumTimelines];
    };
    

    Benchmarks: The median input formatting time over 50 iterations is 22µs on my machine. Part 1 is 74µs and Part 2 is 160µs.

    1 vote
  2. Comment on Paramount launches a hostile bid for Warner Bros. Discovery. in ~movies

    balooga
    Link Parent
    I just got the hang of looking for the phrase “private equity” as an increasingly frequent warning light appearing in articles like this. Now I have to train that same muscle to watch out for...

    I just got the hang of looking for the phrase “private equity” as an increasingly frequent warning light appearing in articles like this. Now I have to train that same muscle to watch out for “sovereign wealth.”

    11 votes
  3. Comment on CGA-2025-12 🏴‍☠️🏝️🍌 INSERT CARTRIDGE 🟢 The Secret of Monkey Island in ~games

    balooga
    Link Parent
    Thanks for digging that up, that’s great. Part of me always wondered if there was some corporate mandate to include X number of unsolvable puzzles, to drive business for the pay-per-minute phone...

    Thanks for digging that up, that’s great. Part of me always wondered if there was some corporate mandate to include X number of unsolvable puzzles, to drive business for the pay-per-minute phone hint line, or to sell guide books. A bit conspiratorial but that was part of the model in those days.

    1 vote
  4. Comment on CGA-2025-12 🏴‍☠️🏝️🍌 INSERT CARTRIDGE 🟢 The Secret of Monkey Island in ~games

    balooga
    Link Parent
    I keep forgetting that Monkey Island is a part of their war chest now. I don’t want them to make a movie, but I’m morbidly curious what one would be like. In competent hands it could be great, but...

    Nowadays, Disney of course also owns both IPs.

    I keep forgetting that Monkey Island is a part of their war chest now. I don’t want them to make a movie, but I’m morbidly curious what one would be like. In competent hands it could be great, but I wouldn’t hold my breath for ten minutes about that ever happening.

    2 votes
  5. Comment on CGA-2025-12 🏴‍☠️🏝️🍌 INSERT CARTRIDGE 🟢 The Secret of Monkey Island in ~games

    balooga
    Link Parent
    I hadn’t seen that article before! It’s a really well-organized piece, I agree with all of his points. It’s interesting that he does make a case for letting the player die in adventure games, yet...

    I hadn’t seen that article before! It’s a really well-organized piece, I agree with all of his points. It’s interesting that he does make a case for letting the player die in adventure games, yet eliminated that entirely from the Monkey Island games.

    I do wonder how some egregious puzzles slipped through the cracks. The monkey wrench puzzle in MI2 is especially notable. What was Gilbert thinking when he designed that one?!

    Incidentally, I really like how he chose she/her pronouns for the proverbial “player” in this article. That was not a common choice in the ‘80s.

    1 vote
  6. Comment on Day 6: Trash Compactor in ~comp.advent_of_code

    balooga
    Link Parent
    Yeah, I just pulled up vim and pasted it in there. Thankfully it was an easy problem to solve, just a tricky one to identify in the first place.

    Yeah, I just pulled up vim and pasted it in there. Thankfully it was an easy problem to solve, just a tricky one to identify in the first place.

  7. Comment on Day 6: Trash Compactor in ~comp.advent_of_code

    balooga
    Link Parent
    Yeah, I got bit with something similar. Nothing I had coded myself, but I always get my input into place by pasting the AoC input content into a local file in my VS Code project. This method has...

    Except, in my infinite wisdom, I had given my file-reading-helper a little convenience: It trimmed the input string.

    Yeah, I got bit with something similar. Nothing I had coded myself, but I always get my input into place by pasting the AoC input content into a local file in my VS Code project. This method has always worked fine for me but thanks to today’s puzzle I just realized that the IDE’s auto-format rules “helpfully” trim all whitespace from the end of every line, even in unknown file types. Which completely breaks this puzzle input. That was annoying.

    1 vote
  8. Comment on Day 6: Trash Compactor in ~comp.advent_of_code

    balooga
    Link Parent
    That’s basically what my first attempt did, and it worked for Part 1. The problem is that in Part 2, the number and positions of those extras spaces actually matter quite a bit. My solution that...

    That’s basically what my first attempt did, and it worked for Part 1. The problem is that in Part 2, the number and positions of those extras spaces actually matter quite a bit. My solution that accounts for that was to infer the width of each column by measuring the distance between its operator and the next one in the bottom row, then extract all the numbers from that column as strings (with space characters intact) using the right-sized window.

    2 votes
  9. Comment on The stories we're not telling - Liberal Democracy is in danger, we need the stories of those who rescued it in ~society

    balooga
    Link
    I love this so much. Over the past decade I’ve been hoping for elected leaders to take the same no-nonsense approach with messengers of illiberalism and cruelty. Hopes mostly met with...

    In 1939, the Second World War broke out. Spaak was Belgium’s foreign minister. He was committed to keeping Belgium neutral and out of the war, but his efforts made no difference. In May, 1940, the Germans again invaded Belgium on their way to France. On the morning of the invasion, the German ambassador went to Spaak to read a formal explanation for the invasion and urge Belgium not to resist. Spaak cut him off. “No, you are not going to read this to me. I know what it is. I am the one who is going to speak, and what I have to say is — get the hell out of here.”

    I love this so much. Over the past decade I’ve been hoping for elected leaders to take the same no-nonsense approach with messengers of illiberalism and cruelty. Hopes mostly met with disappointment. There’s plenty of bluster on the other side, so why’s it so hard for the good guys to stand against them? Who keeps putting microphones in front of nazis, and why haven’t the grown-ups stepped in to unplug them yet?

    13 votes
  10. Comment on Day 6: Trash Compactor in ~comp.advent_of_code

    balooga
    Link
    TypeScript Okay, got my actual solution figured out now. The breakthrough was realizing that the operators (in the last row) always appear in the same position within each column, even though the...

    TypeScript

    Okay, got my actual solution figured out now. The breakthrough was realizing that the operators (in the last row) always appear in the same position within each column, even though the numbers above them shift around. So I begin by walking that bottom row, using each operator as a delimiter which indicates the size (number of characters) in the previous column. Only caveat was that since there's no delimiter to mark the end of the last column, I needed a bit of special kludge to handle that case correctly.

    Once I have the information for a given column — still within that bottom-row walk loop — I can assemble an array of "numbers" (but I'm keeping them as strings to preserve digit placement) for the math problem represented by that column. I store the operator for that problem in my data structure too, so the final formatted data is an array of MathProblem objects that look like this:

    {
        "operation": "*",
        "numbers": ["123", " 45", "  6"]
    }
    

    Then I leave the logic for handling that formatted data up to the Parts 1 and 2 logic. Part 1 is easy, I just call numbers.join(operation) to merge them into a string that looks like this: "123* 45* 6" and then I pass that to eval(). It handles the number parsing and math and doesn't care about the irregular whitespace. Part 2 is a little trickier but just boils down to assembling different "number" strings by getting the length of the existing ones and iterating that many times, and concatenating all of the characters from each existing one that share the same index.

    It's harder to write this in plain English than it was to just code it, lol...

    Parts 1 and 2
    import { RunFunction } from '../../../types';
    
    type Operation = '+' | '*';
    interface MathProblem {
      operation: Operation;
      numbers: string[];
    }
    type InputData = MathProblem[];
    
    function formatInput(input: string): InputData {
      const makeProblem = (operation: Operation) => ({
        operation,
        numbers: [],
      });
    
      const inputArr = input.split('\n');
      inputArr.length && inputArr[inputArr.length - 1] === '' && inputArr.pop();
      const problems: InputData = [];
      const lastRow = inputArr[inputArr.length - 1];
      let currentProblem = makeProblem(lastRow[0] as Operation);
      let currentSize = 0;
      for (let charIndex = 1; charIndex < lastRow.length; charIndex++) {
        const isLastChar = charIndex === lastRow.length - 1;
        if (lastRow[charIndex] === ' ') {
          currentSize++;
          if (!isLastChar) {
            continue;
          }
        }
        if (isLastChar) {
          currentSize++;
        }
        for (let row = 0; row < inputArr.length - 1; row++) {
          // offset accounts for 1-space padding between cols + next operator
          // (neither is present when processing last number in row, so fudge that one)
          const offset = isLastChar ? 2 : 0;
          currentProblem.numbers.push(
            inputArr[row].substring(charIndex - currentSize - 1 + offset, charIndex - 1 + offset)
          );
        }
        problems.push(currentProblem);
        if (!isLastChar) {
          currentProblem = makeProblem(lastRow[charIndex] as Operation);
          currentSize = 0;
        }
      }
      return problems;
    }
    
    export const run: RunFunction<InputData> = () => {
      const totalAnswersHuman = (problems: InputData): number => {
        let total = 0;
        for (const { operation, numbers } of problems) {
          total += eval(numbers.join(operation));
        }
        return total;
      };
    
      const totalAnswersCephalopod = (problems: InputData): number => {
        let total = 0;
        for (const { operation, numbers } of problems) {
          const cephalopodNumbers: string[] = [];
          for (let charIndex = numbers[0].length - 1; charIndex >= 0; charIndex--) {
            cephalopodNumbers.push(
              numbers
                .map(number => number[charIndex])
                .join('')
                .trim()
            );
          }
          total += eval(cephalopodNumbers.join(operation));
        }
        return total;
      };
    
      return [formatInput, totalAnswersHuman, totalAnswersCephalopod];
    };
    

    Benchmarks: The median formatting time on my machine over 50 iterations was 97µs, Part 1 was 242µs, and Part 2 was 482µs.

    1 vote
  11. Comment on You’re probably using the wrong dictionary in ~books

    balooga
    Link Parent
    Truth! I've only read The Da Vinci Code and that was ages ago. I don't remember much about his voice. I was mainly reaching for the name of a popular writer with mass appeal but little claim to...

    Truth! I've only read The Da Vinci Code and that was ages ago. I don't remember much about his voice. I was mainly reaching for the name of a popular writer with mass appeal but little claim to technical mastery (and that's obviously a debatable point for any well-known published author).

    2 votes
  12. Comment on You’re probably using the wrong dictionary in ~books

    balooga
    Link Parent
    Honestly one of the best indicators of a good writer is whether that person is able to understand their audience and write for that audience. I can expand my vocabulary to include a lot of...

    Honestly one of the best indicators of a good writer is whether that person is able to understand their audience and write for that audience. I can expand my vocabulary to include a lot of uncommon but flowery terms, and hone the art of crafting those words into evocative and lyrical sentences — which is absolutely a real skill — but if I’m not discerning whether a situation actually calls for that kind of prose, I’m still a bad writer.

    There’s room in this world for both James Joyce and Dan Brown. And Dr. Seuss. And the countless invisible writers of instruction manuals of all kinds. Not everything has to be (or should be) a literary masterpiece.

    8 votes
  13. Comment on 1977 theatrical cut of Star Wars coming to theaters in 2027 in ~movies

    balooga
    Link Parent
    I mean you’re probably right but I used to love watching for clues like that to figure out how old effects were done. One really subtle but technically sophisticated effects shot that sticks out...

    I mean you’re probably right but I used to love watching for clues like that to figure out how old effects were done. One really subtle but technically sophisticated effects shot that sticks out in my memory even after all these years is this one from Back to the Future: Part II. It was like a revelation when teenage me realized that innocuous-looking lamppost was actually the secret to the whole thing. Once that clicked I suddenly realized just how much preparation and attention to detail goes into making these otherwise uninteresting shots look natural.

    Nowadays the answer is just (digital) “VFX,” it’s all VFX. There’s not a pixel in any modern movie that hasn’t been run through that pipeline. I miss seeing the edges of movie magic. It feels like digitally erasing them is like an erasure of history in some way, it’s the erasure of the hard work and incredible talent of a previous generation of craftspeople. Feels like a slap in the face, on some level.

    2 votes
  14. Comment on You’re probably using the wrong dictionary in ~books

    balooga
    Link
    I don’t know much about the inner world of dictionary editorial teams, but I enjoy the Merriam-Webster Word of the Day podcast. It’s been hosted for years by their editor-at-large, Peter...

    I don’t know much about the inner world of dictionary editorial teams, but I enjoy the Merriam-Webster Word of the Day podcast. It’s been hosted for years by their editor-at-large, Peter Sokolowski. Occasionally I’ll hear his voice pop up in other podcasts I listen to also, like 99% Invisible. His personality comes through a lot more in those other contexts, since WotD is scripted to be brief and informative.

    Anyway he’s the only modern lexicographer I could tell you the name of but I’ve always thought he’d be an interesting figure to have a word-nerd conversation with. I’d be really interested to hear his response to this article.

    2 votes
  15. Comment on 1977 theatrical cut of Star Wars coming to theaters in 2027 in ~movies

    balooga
    Link Parent
    If I was a Disney exec I’d probably figure that just paying for 4K77 is cheaper than reproducing all that hard work in-house, for an outcome that (as far as 99.9% of audiences would be able to...

    If I was a Disney exec I’d probably figure that just paying for 4K77 is cheaper than reproducing all that hard work in-house, for an outcome that (as far as 99.9% of audiences would be able to tell) is identical.

    If I was the creator of 4K77 I’d be happy to see my labor of love officially recognized and receive a nice payday for it as well. Though I’m sure there’s an egalitarian “it belongs to the people!” angle in this story too, so who knows how it would actually play out.

    8 votes
  16. Comment on Day 6: Trash Compactor in ~comp.advent_of_code

    balooga
    Link
    Ack, I solved Part 1 pretty quickly but Part 2 is throwing me for a loop. Need to reverse course on most of my assumptions that held Part 1 together, namely the way my input formatting collapses...

    Ack, I solved Part 1 pretty quickly but Part 2 is throwing me for a loop. Need to reverse course on most of my assumptions that held Part 1 together, namely the way my input formatting collapses adjacent spaces to make parsing easier. It's getting too late for me now, I'll need to sleep on it and refactor this thing in the morning.

    Probably a good opportunity to rethink my string -> number -> string -> number conversion strategy too, let's just say TypeScript is not happy with some of the stuff I've been doing in my current state of sleepy delirium lol...

    2 votes
  17. Comment on Day 5: Cafeteria in ~comp.advent_of_code

    balooga
    Link
    TypeScript Seems like the range sort/merge approach is probably the only sane way to tackle this one. Once I cleared that mental block it was smooth sailing, and Part 2 ended up being faster and...

    TypeScript

    Seems like the range sort/merge approach is probably the only sane way to tackle this one. Once I cleared that mental block it was smooth sailing, and Part 2 ended up being faster and simpler than Part 1 (a welcome change)!

    I did have some fun modifying the array of ranges in-place with Array.prototype.splice() while looping through it, carefully managing the current index to recompare merged ranges after each mutation. Some judicial use of continue and break in the Part 1 nested loops helps avoid unnecessary iterations.

    Parts 1 and 2
    import { RunFunction } from '../../../types';
    
    type Ranges = [number, number][];
    interface InputData {
      freshRanges: Ranges;
      ids: number[];
    }
    
    function formatInput(input: string): InputData {
      const [freshRangesRaw, idsRaw] = input.trim().split('\n\n');
      return {
        freshRanges: freshRangesRaw
          .split('\n')
          .map(freshRangeRaw => freshRangeRaw.split('-').map(boundString => Number(boundString)) as [number, number]),
        ids: idsRaw.split('\n').map(idString => Number(idString)),
      };
    }
    
    export const run: RunFunction<InputData> = () => {
      const mergeRanges = (freshRanges: Ranges): Ranges => {
        const sortedRanges = freshRanges.sort(([a], [b]) => a - b);
        for (let i = 0; i < sortedRanges.length - 1; i++) {
          // no overlap between ranges, do nothing
          if (sortedRanges[i + 1][0] > sortedRanges[i][1]) {
            continue;
          }
          // partial overlap, merge ranges (keep start of A and end of B)
          if (sortedRanges[i + 1][1] > sortedRanges[i][1]) {
            sortedRanges.splice(i, 2, [sortedRanges[i][0], sortedRanges[i + 1][1]]);
            i--;
            continue;
          }
          // range A fully overlaps B, so just delete B
          sortedRanges.splice(i + 1, 1);
          i--;
        }
        return sortedRanges;
      };
    
      const countAvailableFreshIds = ({ freshRanges, ids }: InputData): number => {
        const mergedRanges = mergeRanges(freshRanges);
        let total = 0;
        for (const id of ids) {
          for (const [start, end] of mergedRanges) {
            if (id >= start) {
              if (id <= end) {
                total++;
                break;
              }
              continue;
            }
            break;
          }
        }
        return total;
      };
    
      const countAllFreshIds = ({ freshRanges }: InputData): number => {
        const mergedRanges = mergeRanges(freshRanges);
        let total = 0;
        for (const [start, end] of mergedRanges) {
          total += end - start + 1;
        }
        return total;
      };
    
      return [formatInput, countAvailableFreshIds, countAllFreshIds];
    };
    

    On my machine the median input formatting time is 115µs, Part 1 is 225µs, and Part 2 is 12µs. This is my first solution since improving my benchmark methodology earlier today. Eagle eyes may notice slightly different scaffolding syntax in today's code, to support that.

    It made me really happy to see <1ms times for all three parts. These accurate benchmarks make me feel more like a real contender after all, even as a JS goof trying to keep up with you hardcore Rust gurus and your ilk. Turns out, JS can be pretty quick too!

    1 vote
  18. Comment on Day 4: Printing Department in ~comp.advent_of_code

    balooga
    Link Parent
    @zkxs Just finished up my benchmarking improvements! Haven't started today's puzzle yet though, lol... Some of the improvements I made: Increased precision to nanosecond resolution (which is...

    @zkxs Just finished up my benchmarking improvements! Haven't started today's puzzle yet though, lol...

    Some of the improvements I made:

    • Increased precision to nanosecond resolution (which is overkill and later gets rounded down to microseconds and milliseconds)
    • Stopped including input formatting in solution benchmarks (I've separated that into its own measurement now)
    • Removed filesystem / IO stuff from measurement, since it's additional overhead that doesn't have any bearing on my solutions
    • Added configurable iteration count, using 50 for now, to run each part repeatedly and return the min, max, and median durations

    And check out my fancy new tabular printout:

    $ ./bin/aoc -l
    Running solution for Advent of Code 2025, Day 04...
    
    Part 1:
    [REDACTED]
    
    Part 2:
    [REDACTED]
    
    ╔═════════════════════════════ BENCHMARKS (50 PASSES) ══════════════════════════════╗
    ║                                                                                   ║
    ║                    ----- Median -----   ------ Min -------   ------ Max -------   ║
    ║   Format Input:      0ms     (197µs)      0ms     (189µs)      0ms     (336µs)    ║
    ║   Part 1:            1ms     (917µs)      1ms     (899µs)      1ms    (1150µs)    ║
    ║   Part 2:           28ms   (27726µs)     27ms   (27103µs)     36ms   (36476µs)    ║
    ║                                                                                   ║
    ╚═══════════════════════════════════════════════════════════════════════════════════╝
    

    It's wild what a difference it makes — the benchmarks I posted here yesterday were 7ms for Part 1 and 56ms for Part 2. Without changing any of my solution code I've more than halved my numbers and have more confidence their accuracy. Feels good.

    2 votes
  19. Comment on Carmageddon: Rogue Shift | Announcement trailer in ~games

    balooga
    Link
    I never played the originals but a glance through the YouTube comments confirms that this is a very different concept from those games. My impression was that they were cut from the same cloth as...

    I never played the originals but a glance through the YouTube comments confirms that this is a very different concept from those games. My impression was that they were cut from the same cloth as other ‘90s edgelord titles like Postal, where you’re wreaking violent chaos against innocents in public spaces. Wasn’t running over as many pedestrians as possible a key scoring mechanic? Looks like they’ve been replaced with zombies, and the regular city streets with closed-course gladiatorial tracks?

    I dunno, as a non-fan I’m not exactly trying to defend the integrity of the brand. I was never into those outrage-bait games that lean into psychopathy as an aesthetic (I don’t have a moral problem with them either, I just find them distasteful and not-for-me). And I realize I’m likely just mischaracterizing games I have no experience with, which isn’t fair — Carmageddon’s got a pretty large following so I’m sure it’s well-made and fun. Just seems like this, judging from the trailer, is more akin to Twisted Metal and just using the Carmageddon name for brand recognition.

    3 votes
  20. Comment on Day 4: Printing Department in ~comp.advent_of_code

    balooga
    Link Parent
    Very good points here. Currently my AoC runner just grabs a start timestamp and an end timestamp and prints the difference. It only has millisecond precision and I do get varying results each time...

    Very good points here. Currently my AoC runner just grabs a start timestamp and an end timestamp and prints the difference. It only has millisecond precision and I do get varying results each time I run it.

    Not that this is particularly important but I can probably improve that in a few different ways. Could be a fun exercise. I’ll poke at it tomorrow.

    1 vote