Advent of Code 2021

Your instincts tell you that in order to save Christmas, you’ll need to get all fifty stars by December 25th.

This year I’m tackling Advent of Code again, after taking last year off. Two years ago, I used Clojure; this time I’ll tilt pedestrian and use TypeScript.

The idea is to do it right here, in my blog, built on Next.js and MDX. What this means is that my solutions will run in your browser, directly in the individual blog posts, rather than running on the server in advance.

Here are the days I’ve finished so far:

How it works

Some Advent of Code problems take seconds-to-minutes to run to completion. Two misbehaviors we need to avoid: (1) running the solution in Next.js server-side rendering, (2) holding up client-side rendering of the post content.

We can solve both of these with a React effect. Effects don’t run on the server, instead they wait until after the initial render on the client. For example, here’s a React component that starts with an empty result, then runs the function in an effect on the client browser:

import {useEffect, useState} from 'react'

const Component = () => {
  const [result, setResult] = useState('')

  useEffect(() => {
    const result = longRunningFunction()
    setResult(result)
  }, [])

  return <p>Result: {result}</p>
}

However there is a third misbehavior we would like to avoid: taking the browser hostage while the function runs. If we want the client free to rerender while the function runs, without waiting for the end, then we need to move computation off the main thread. For this we can use a web worker.

So we start with a React hook that will load our worker and simultaneously send it a message to get started:

const useWorker = (message: any, cb: any) => {
  const [worker] = useState(() => {
    if (typeof Worker !== 'undefined') {
      const worker = new Worker(new URL('./advent.worker.ts', import.meta.url))
      worker.addEventListener('message', (event: any) => cb(event.data))
      worker.postMessage(message)
      return worker
    }
  })

  // Terminate on unmount
  useEffect(() => () => worker.terminate(), [worker])

  return worker
}

The worker listens for messages and runs the appropriate day’s function with the given input:

self.addEventListener('message', (event: any) => {
  const props = event.data as AdventProps
  const fn = days[`d${props.day}${props.part}`] || days[`d${props.day}`]
  const result = fn(props)
  self.postMessage({result})
})