eieio.games

by nolen royalty

One Million Checkboxes

One million checkboxes that anyone can check

Jun 25, 2024

Hi. It's Nolen (eieio) from the future, a day and half after I posted this.

While it was live, One Million Checkboxes was played by over half a million people. Over 650 million boxes were checked. The site was featured in the New York Times and has a Wikipedia page. Wild stuff.

This post discusses the original implementation. You can read about how I scaled up the site here, and you can read a story of some incredible emmergent gameplay from the site here.

The original blog post continues below.


I made a website. It’s called One Million Checkboxes. It has one million checkboxes on it. Checking a box checks that box for everyone (and makes some numbers go up).

You can find it at onemillioncheckboxes.com.

checking some boxes

Why

I don’t really know. The idea came up in a conversation with my friend Neal last Friday and I felt compelled to make it.

How

There are a few fun tricks I used here.

  • To efficiently store state I use a bit array. Checking box 0 just flips the first bit in that array.
  • I store my state in redis since redis can easily flip individual bits of a value.
  • I broadcast individual “toggle” updates via websockets and push out a full state snapshot every 30 seconds or so to make sure clients stay synched.
  • I use react-window to avoid rendering checkboxes that aren’t in view.

Is there anything else you’d like to tell us

Not much! This one was fun and fast. I did run into one bug that was baffling - I’ll tell you about it really quick.

Endianness

When we toggle a checkbox, the server does something like this:

def set_bit(index, value):
    state['bitset'][index] = value

Pretty simple! We convert that state to a big array of bytes and ship it to the client 1. The client looks at the state to figure out which toggles are set. My original implementation looked like this:

1

I originally RLE encoded this but later decided that just shipping 125kb was fine since it saves my server some work.

class BitSet {
  constructor(size) {
    this.size = size;
    this.bits = new Uint32Array(Math.ceil(size / 32));
  }

  get(index) {
    const arrayIndex = Math.floor(index / 32);
    const bitIndex = index % 32;
    return (this.bits[arrayIndex] & (1 << bitIndex)) !== 0;
  }

  set(index) {
    const arrayIndex = Math.floor(index / 32);
    const bitIndex = index % 32;
    const mask = 1 << bitIndex;
    this.bits[arrayIndex] |= mask;
  }
}

Does this work?

No! Look at what we get from each of these implementations when we set the first bit:

def set_bit(index, value):
    state['bitset'][index] = value
state = { "bitset": [0 for _ in range(32)] }
set_bit(0, 1)
print("".join(str(x) for x in state["bitset"]))
# '10000000000000000000000000000000'
int("".join(str(x) for x in state["bitset"]), 2)
# 2147483648
> bitset = new BitSet(32)
> bitset.set(0)
> bitset
BitSet { size: 32, bits: Uint32Array(1) [ 1 ] }

Our python implementation treats bit 0 as the leftmost bit of the leftmost byte. In javascript we’re grabbing the rightmost bit of the leftmost byte!

This isn’t quite an endianness problem - really we’re reversing the order of bits in a byte, instead of reversing the order of bytes in a word. But it certainly feels like an endianness bug.

Fixing this meant deciding whether I wanted to model my data as “one million bits” or “125,000 bytes” - and of course realizing that those were two different things. The bug appeared mid-refactor - my original update code hid the error in my data model until you refreshed the page - and so it took me ages to suspect the bit twiddling. In retrospect this was a mistake. Always suspect the bit twiddling.

Wrapping Up

This site was fun to make. It got me thinking about a new space of collaborative experiences that I want to explore.

It was also really nice to make and ship something in two days! I’ve got a few ongoing projects that I’ve been working on for too long and this was a welcome break (maybe I should just ship those projects too).

Anyway. I hope you enjoy the site, and I’ll be back soon with some larger webcam-based projects :)

Thanks for reading!

Keep up with me on my socials 👆

Or sub to my newsletter here! 👇