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:
- Turn left if current cell is infected, turn right if it isn't.
- Flip the cell (clean –> infected, infected –> clean)
- 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. 😑
Generative dick art in 32,400 divs. Enjoy pic.twitter.com/ZXywfcbry7
— Swizec Teller (@Swizec) December 22, 2017
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 row
s. 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.
- React is SO MUCH FASTER after you turn off streaming
- If you render 250,000 DOM nodes, you're gonna have a bad time
- Pegging your algorithm at
requestAnimationFrame
makes it slow - Safari is a lot faster than Chrome at re-rendering a few ten thousand DOM nodes with React. I don't know why
It's surprising how much faster than Chrome Safari is at re-rendering a 180x180 div array in React on every requestAnimationFrame
— Swizec Teller (@Swizec) December 22, 2017
I triedFirefox too but it was laughably slow. Yes the new super fast Firefox.
Maybe I should switch to Safari 🤔 pic.twitter.com/tqyCA3y5ML
- When your code runs slow, development is slow, and getting the wrong result after 2+ hours of tinkering kinda sucks
array.splice(5, undefined, 1,2,3,4,5)
doesn't even bat an eye, turns thatundefined
into0
and proceeds like nothing happened
This "works" #javascript pic.twitter.com/p0RSmrLore
— Swizec Teller (@Swizec) December 22, 2017
Thanks to stream watchers for pointing out that I can't spell row.length
. You da real MVPs.
Continue reading about Advent of Code Day 22 – Sporifica Virus
Semantically similar articles hand-picked by GPT-4
- Screw web performance, just wait a little 😈
- Advent of Code Day 20 – Particle Swarm
- A Dancing Rainbow Snake – An Example of Minimal React and D3v4 transitions
- Animating with React, Redux, and d3
- Conway’s game of life in Google Docs
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
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 ❤️