NIBBLES.BAS

Start

My parents had bought a personal computer for my brother and I even though none of us knew how to use one; they figured it was a better alternative to TV for kids to play with. I remember asking my dad what C:\> was and he told me not to worry about it and write stories. So I would click clack away for a few lines until the command line buffer filled—unmistakable by the shrill beeps—and asked why nothing was coming out anymore. "Just press Enter." "What does 'Bad command or file name' mean?"

My parents dropped my brother and I off at the library most Sundays as public daycare. I liked going into the huge marble building downtown and looking around the children's section in the basement. My brother liked Garfield; I was more Calvin and Hobbes. I learned about my heritage through picture books about the mischievous monkey king. I love libraries.

3-2-1 Contact was one of my favorite children's magazines. It made you curious about the world by showing things even if you might not understand them. They had a coding column every issue that had a couple dozen lines of BASIC. I never knew what the programs did; I only knew about "Bad command or file name."

One day I found a special issue. Like all the others it had a coding column with a couple dozen lines of BASIC. But there was also an aside written in a starburst, to the effect of: Did you know most PCs with DOS come with a BASIC interpreter? Try running C:\> qbasic.exe!

QBasic was my very first programming language. It included some sample programs, presumably to show you how to use it. I just liked playing the games. GORILLAS.BAS had the best graphics and sound effects; also what a killer premise, gorillas throwing exploding bananas across building tops. NIBBLES.BAS was the runner up, but honestly I had way more fun playing it. It has support for two players, so my brother would play with me. Usually we played cooperatively but we could also be mischievous monkeys.

Level select

Near the end of my second batch at the Recurse Center, my friend Frank brought in a vintage arcade cabinet. As soon as he powered it on and I heard the CRT coil whine, years of memories with QBasic, Turbo buttons, and childhood computers flooded in.

A few days later, Frank installed the spinners (inspired by Hover Burger at the singular Wonderville). I had also just watched Tron: Ares (advice: don't) and was reliving memories of GLtron. Somehow with this combination of vintage gaming, digital, and analog controls, I knew what I had to do. I had to break Nibbles out of the grid.

Main game

I knew going in that I wanted the snakes to be able to go 360 degrees with the spinner, so I couldn't use a standard text grid. But I also wanted to maintain the illusion that my game was just a nostalgic and straightforward port of the classic Nibbles; so I had to fastidiously ensure that every draw rendered on character boundaries even while using floating point and polar coordinates under the hood.

Just getting rid of antialiasing so we could have perfectly crisp pixels was a challenge. I ended up writing to the image bitmap and loading that into the canvas, avoiding any canvas rendering operations altogether. That means all the line drawing and text rendering are implemented from scratch. My favorite VS Code mini map of all time is the source file that encodes the bitmap font; I wrote A and Claude generated the rest.

Collision detection was particularly tricky. We know that we can't just detect overlap on the character grid, since activating the spinners breaks out of the grid. Instead it is implemented by reading from the image bitmap; if we try to draw the front of the snake and encounter a pixel that isn't blue (the color of our background), then we've hit something. But.

Dear god, the buts. Head on collisions were a pain in the ass, since we want to attribute the death to both snakes, not whichever snake happened to be drawn second. The solution was a hack involving drawing in two passes and using the alpha (transparency) channel as a mark bit. I'm glossing over it because honestly it still gives me heeby jeebies. But it got me used to the idea of hiding data in the alpha channel for the second issue.

Final boss

When the spinner activates the snake breaks out of the grid. That obviously means it can move at any angle, but it less obviously means it is drawn one pixel length at a time rather than one character block at a time. That means each frame our snake moves forward, one pixel length of its new front will be drawn over the background, and the remaining pixels will overlap with the previous front of the snake. According to our collision detection algorithm, that's a self collision; activating the spinner instantly kills the snake :(

So using the alpha channel hack again, we steal a few bits this time to store the age of newly drawn pixels. Any pixel drawn within the last 6 frames will not count as a self collision. After each frame, we mark these pixels one older until they are 6 frames old and fully collidable (and old enough to vote).

Continue

I want to briefly reflect on Nibbles, because of the unexpected feelings I've gotten in watching other people play. I mean the pride is nice I guess, but what I didn't realize is the joy that comes from creating a reaction in others. I've made a lot of useful things, but to make something that is fairly useless, except that it creates some interest, mirth, mingling, conversation—well, you realize it actually is pretty useful. What a happy feeling.

For helping me have this realization, thanks Ben and Taniya for the conviviality of collaborative play, Iris for dogged domination, Frank the cabinet father and Joseph for calling out the details, and Claire, Max, Nolen, and Toby for sharing their own play that has unassumingly influenced me.

I highly recommend Frank's write up about building the RCade community as well as Stephen's incredible adventures building a display adapter from scratch. If you're in NYC, Wonderville is an amazing experience!

Thanks for playing!