Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Advent of Code Day 22 – Sporifica Virus

    For Day 22, we built a virus and found that Safari is much faster than Chrome.

    The virus carrier works in bursts; in each burst, it wakes up, does some work, and goes back to sleep. The following steps are all executed in order one time each burst:

    1. Turn left if current cell is infected, turn right if it isn't.
    2. Flip the cell (clean –> infected, infected –> clean)
    3. Move forward

    Diagnostics have also provided a map of the node infection status (your puzzle input). The virus carrier begins in the middle of the map facing up.

    Naturally, I decided to solve this puzzle in React. Because it sounds like it might draw something interesting. And who doesn't want to see a visualization of the spread of a deadly virus?

    It draws a dick. 😑

    And takes 5 minutes to run. And gets the wrong result.

    Edit: I mixed up left and right. When you fix left and right, it no longer draws a dick, and it still gets the wrong answer 🤷‍♀️. /Edit

    There must be an off-by-one error somewhere in the code that I can't find. The animation looks correct.

    The image evolves, the virus carrier seems to follow the rules, and there are no obvious digressions from the plan. The recursive pattern it falls into around step 7,000 looks suspicious but not out of the question 🤔

    An attempt was made (and then fixed)

    You can see the code on GitHub.

    It's built from 3 React components: GridRow, which renders a single row of the grid, Grid, which renders the grid, and Day22, which drives the whole thing.

    GridRow

    If you guessed "Oh, that should be simple", you were right.

    const GridRow = ({ row, x, y, ry }) =>
        row.map((cell, i) => (
            <div style={{ background: y == ry && x == i ? "red" : cell == "#" "black" "white", width: "5px", height: display: "inline-block" }} key={i}>
                 
            </div>
        ));
    

    Loop through a row, which is an array, and render a <div> for each cell. Cells are 5x5 pixels and their background can be red, black or white.

    Red if the virus carrier is currently on that cell, black if the cell is infected, white if it's not.

    Grid

    Once more a simple one, walk through an array of rows, render GridRow components.

    const Grid = ({ grid, x, y }) =>
      grid.map((row, i) => (
        <div key={i} style={{ display: "flex", justifycontent: "center" }}>
          <gridrow row={row} x={x} y={y} ry={i}></gridrow>
        </div>
      ));
    

    Day 22 – the actual solution

    <Day22> is where all the logic happens. It sets up our grid as data, drives the virus carrier, and renders <Grid> and some meta data.

    const Width = 180,
      offset = 23;
    
    class Day22 extends Component {
      state = {
        grid: new Array(Width).fill(".").map((_, i) => {
          let row =
            i > Math.floor(Width / 2 - input.length / 2 - offset)
              ? input[i - Math.floor(Width / 2 - input.length / 2 - offset)]
              : null;
    
          let a = new Array(Width).fill(".");
          if (row) {
            a.splice(Width / 2 - row.length / 2 + offset, row.length, ...row);
          }
          return a;
        }),
        x: Math.floor(Width / 2) + offset,
        y: Math.floor(Width / 2) - offset,
        vx: 0,
        vy: -1,
        bursts: 0,
        infected: 0,
      };
    
      componentDidMount() {
        this.burstActivity();
      }
    
      burstActivity() {
        // snip
      }
    
      turnLeft(vx, vy) {
        // snip
      }
    
      turnRight(vx, vy) {
        // snip
      }
    
      render() {
        const { grid, width, bursts, x, y, infected } = this.state;
    
        return (
          <div>
            <h3 style={{ display: "flex", justifycontent: "center" }}>
              bursts: {bursts}, pos: ({x}, {y}), infected: {infected}
            </h3>
            <div style={{ border: "1px solid red" }}>
              <grid grid={grid} x={x} y={y}></grid>
            </div>
          </div>
        );
      }
    }
    

    That state calculation looks hairy. We're taking an array of Width, filling it with healthy cells, ., then walking through it to build rows. If the current index is past a certain point, we replace the middle part of the row with a row from our input array. We do that with a splice.

    The result is a 180x180 grid of cells. Some of them are infected, but most aren't.

    The render method just renders all of this.

    The virus carrier logic

    The virus carrier logic comes in 3 functions. burstActivity, turnLeft and turnRight.

       burstActivity() {
            const { x, y, bursts, grid } = this.state;
            let { vx, vy, infected } = this.state;
    
            if (grid[y][x] === "#") {
                grid[y][x] = ".";
    
                [vx, vy] = this.turnRight(vx, vy);
            } else {
                grid[y][x] = "#";
                infected += 1;
    
                [vx, vy] = this.turnLeft(vx, vy);
            }
    
            this.setState({
                x: x + vx,
                y: y + vy,
                vx,
                vy,
                grid,
                infected,
                bursts: bursts + 1
            });
            if (bursts + 1 < 10000) {
                requestAnimationFrame(() => this.burstActivity());
            }
        }
    
        turnLeft(vx, vy) {
            let _vx = -1 * vy;
            vy = vx;
            return [_vx, vy];
        }
    
        turnRight(vx, vy) {
            let _vx = vy;
            vy = -1 * vx;
            return [_vx, vy];
        }
    

    burstActivity is our main logic driver. It looks at the current virus carrier position, turns left if it's infected, or turns right if it isn't. In both cases, it also flips the cell.

    If the cell was flipped to infected, we increment our solution counter of how many cells we've infected.

    Then it updates state and continues the loop if there have been fewer than 10,000 bursts of activity.

    turnLeft and turnRight turn the direction our virus carrier is going. Left or right using a little bit of vector maths. Thanks to a helpful stream watcher who showed me this better way. I had a huge sequence of ifs at first 🙈

    Lessons learned

    I learned a couple of lessons with this build.

    1. React is SO MUCH FASTER after you turn off streaming
    2. If you render 250,000 DOM nodes, you're gonna have a bad time
    3. Pegging your algorithm at requestAnimationFrame makes it slow
    4. Safari is a lot faster than Chrome at re-rendering a few ten thousand DOM nodes with React. I don't know why
    1. When your code runs slow, development is slow, and getting the wrong result after 2+ hours of tinkering kinda sucks
    2. array.splice(5, undefined, 1,2,3,4,5) doesn't even bat an eye, turns that undefined into 0 and proceeds like nothing happened

    Thanks to stream watchers for pointing out that I can't spell row.length. You da real MVPs.

    Published on December 22nd, 2017 in Front End, Technical

    Did you enjoy this article?

    Continue reading about Advent of Code Day 22 – Sporifica Virus

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️