Mark Vasilkov
Mark Vasilkov

Mark Vasilkov

The Neatness (js13kGames–2022)

Mark Vasilkov's photo
Mark Vasilkov
·Sep 14, 2022·

5 min read

This is the story of The Neatness.

It all started when I began playing The Looker, a brilliant game by Bradley Lovell. Line-based puzzles in The Looker are mostly satire, the player being able to bypass many of those by scribbling madly. I was instantly convinced, however, that it was the perfect game mechanic.

The title of the game, The Neatness, is absolutely a nod to Jonathan Blow's work, which I'm a huge fan of. So this was the easiest decision ever.

Level design was done entirely on paper, which naturally worked great for this type of game mechanic. That's not to say it was an especially easy task, I found myself iterating on puzzles for the entire duration of the compo.

Level design be like

And here's how the actual development went:

Algorithms

The most basic feature of The Neatness is drawing lines on screen. Of course, the first thing that comes to mind is the Bresenham's line algorithm. However, Bresenham's algo makes diagonal connections; if we're going to check the connectivity on a square grid, it makes more sense to only account for neighbors in four main directions (left, up, right, down).

Having that in mind, I implemented the digital differential analyzer (DDA) algorithm for drawing lines, and it worked flawlessly, first try. And I totally didn't have to debug it for like two hours because of a subtle bug.

The second feature I needed was the ability to tell if one control point was connected to the other, to signal level completion. And nope, I didn't go with the A* algo for that. I wanted the code to be as efficient as possible at this point, since I had no idea how computationally intensive the other parts of the game would end up being.

Instead of A*, I decided to flood fill any new lines that are drawn with the first nonzero neighboring thing they meet (either a control point, or another line). Then the code checks if we've met a different neighboring thing, meaning the two are now connected.

Despite using the flood fill algorithm, this is very cheap to compute: the player shouldn't be able to cover any significant length between pointer events, and we never update the same spot twice.

Thanks to Lode Vandevenne's excellent Computer Graphics Tutorial, I was even able to implement this stuff reasonably fast.

Colors

Being incapable of producing a color scheme that doesn't look horribly wrong, I left this task to professionals and went with BLK NEO and TwilioQuest 76 palettes.

However, the main set of colors didn't work on a bright background. To remedy this situation, I used the entirety of my severely limited knowledge about colors to do the following:

  1. Convert the main colors from sRGB to linear RGB
  2. Take that into the Oklab color space
  3. Tone down the L channel ever so slightly
  4. Retrace the steps all the way back to sRGB

Color scheme

I'm immensely grateful to Björn Ottosson, the man, the legend, who bestowed the Oklab color space upon us. It's magical.

Pixels

Another decision made very early into the competition was to use non-square pixels (kind of like SNES) for the painting layer. Ideally I'd like to simulate a proper CRT screen with shaders, but that feat of engineering surpasses my WebGL abilities rather dramatically.

What I've learned about the SNES video output is as follows:

  • Internal SNES aspect ratio is 8:7, at the resolution of 256×224
  • Contemporary CRT TV aspect ratio was 4:3
  • So the pixel aspect ratio (PAR) should be 7:6 (4:3 ÷ 8:7)
  • However PAR of an NTSC TV is 8:7 because reasons

So in the end I went with the PAR of 8:7, which gives a nice subtle anti-aliasing effect.

Sprites

The Neatness uses both 1-bit and 2-bit sprites, encoded as plain bitmaps and represented using Array<Number> in JS. I don't think that implementing any form of compression (such as RLE) for these bitmaps is productive, because the js13kGames' compulsory submission format — ZIP — will outperform RLE anyway.

2-bit sprites (which amounts to transparency + 3 colors) are used for the skulls, gravestones, the princess, and her crown (the crown being a separate sprite because I wanted it to have a distinct color). The levels and the buttons on the right side of the screen are 1-bit sprites.

The princess

In order to iterate on the level design somewhat efficiently, I wrote a Python encoder that takes a bunch of picture files and outputs bitmaps as JS arrays. Using Aseprite as a level editor is wonderfully convenient.

One caveat to pay attention to when working with bitmaps in JS: bitwise operations truncate their operands to 32 bits! Meaning that you can't use the entirety of JavaScript's 53-bit integers if you do shift right, either >> or >>> — you have to divide by a power of 2 instead.

natlib

natlib is a highly composable library for small games, cannibalized from my previous js13kGames entries. The part that I'm especially fond of is the fixed-step game loop, I use it at every opportunity (which is not often).

The Neatness uses the following bits and pieces from natlib:

  • 2D vector math
  • Viewport auto-scaling
  • Mouse and touch event handling
  • PRNG (the PRNG of choice being Mulberry32, designed by Tommy Ettinger)
  • The fixed-step game loop mentioned earlier

Package size

One of the prominent challenges of the js13kGames compo is the package size, which is required to be no larger than 13,312 bytes.

A 13 KB ZIP file is a good 70–90 KB of JS. Turns out I physically cannot write that much code in a month. By the end of the compo I still had some 2 KB to spare, before any optimizations (and I maintained a list of possible code size optimizations just in case.)

By the way, The Neatness source code is released under the GNU General Public License version 3.

Kudos

I'd like to thank my friends @iiSatana and Andrey Shuster for beta testing my stuff.

Super special thanks to Andrzej Mazur for organizing this amazing event.

And thank you for reading this wall of text.