An open mind is like a fortress with its gates unbarred and unguarded.
Naturally, it begins with the theme of the 2023 edition of the compo: 13th Century. When it dropped, I was completely lost. Medieval history is like Warhammer 40,000 to me, where in the grim darkness of ages past, there is mostly war. Doesn't give off warm fuzzies.
So I began coding a simple board game thing: a square grid with pieces on it. Such an enjoyable experience, that. Writing clean code for small, easy to understand systems.
Smooth brain energy
Herein lies my first mistake, thinking that a square-grid-based game will be easy to make. Full of unwarranted enthusiasm, I added clusters of pieces that should move together as a single large object, and whipped up some sort of a collision solver: a piece pushes another piece, so we recursively solve for that piece moving. And its entire cluster moving.
So when there are two interlocked clusters, they push one another into a stack overflow. This terrible algorithm haunted me for the entirety of the compo, and I'm still unsure how to implement it properly, edge cases and all. In a feat of peak gamedev, I settled on making levels that don't break the solver, and that was it.
Of ducks and men
At this stage the game spontaneously ended up being about a duck going on a crusade, under the working title of Super Holy Chalice and bearing the following description:
Embark on a crusade
Reclaim the Holy Chalice
In the 13th century
To be honest I wasn't feeling it, so at work I discussed the matter with a brilliant engineer on my team, who suggested a different take: the siege of Baghdad. We're a scholar at the House of Wisdom, about to be sacked by the culturally diverse Mongol migrants. We need to prevent this from happening using magic. Puzzle solving is the compulsory magic ritual.
Super Siege of Baghdad
In the heart of ancient Baghdad, a shining beacon of knowledge, the House of Wisdom, stands as a testament to the golden age of enlightenment. As the Mongol forces close in on the city in 1258, threatening to obliterate centuries of accumulated wisdom, one scholar embarks on a mission — wielding not a sword, but the power of sacred geometry.
Set in the 13th century, Super Siege of Baghdad immerses you in a world where geometry is more than an empirical science; it's a mystical force with the potential to change the course of history. Can you harness this power and save the House of Wisdom from its impending doom?
I don't hate this fluff piece, but to me it feels way forced. This is not something I would've considered if not for the compo theme.
When talking about this issue with a friend, the third and final take on the 13th Century theme was born: a game about building a castle. Castles are vaguely medieval. The level is a quarry, where you arrange the stone blocks. Suddenly it all made sense.
Super Castle Game
The fortress of the mind will rise
Now I needed to render a castle. Isometric projection in hand, I made this model by starting with a cube and poking holes in it for the gate, windows and battlements on top. This is only done once while the game is loading, then the model is stored as three one-dimensional arrays for x, y and z coordinates of each block.
Optimizing the rendering of this thing was a fun little challenge. I'm working with the regular 2D canvas, where each separate draw call is expensive, and acceptable performance is achieved by making a single large path and then filling it all at once. But at the same time I rely on rendering the model back to front, one block at a time, else it looks all messed up.
To stress test the rendering (so that the effect of any optimizations is easier to see), I ran a solid cube 12×12×12, or about 1700 blocks. Three sides per block are visible, amounting to roughly 20,000 calls to
.lineTo() and 5000 calls to
You can imagine it was REALLY slow in this setting. We're talking barely 30 FPS on a half-decent Intel MacBook, and budget smartphones would be seeing fractional frame rates. So I tried two things to make it faster:
.fill()calls so that a run of blocks can be filled with just three calls per run, down from three calls per individual block. This only works if there's no depth order fighting, so I made it render vertical columns of blocks together: first the top side of each block (this is important!), and then right and left sides. And it's super effective! Like I anticipated, calls that add a subpath are cheap, it's
.stroke()that are costly.
Then, in the mood for some more performance optimization, I made a hash function for blocks to check if they're occupying the same exact spot on screen. The function is rather straightforward:
(x - y) * 999 + (x + y - z - z). With this, culling the blocks that are fully covered by others closer to the camera became very easy. I also decided to bake the culling into the model itself, since there's no camera rotation anyway. This reduced the number of blocks by about 20% when applied to my actual castle model, so it's a small win.
After these changes, the test bench ran smoothly and capped at the screen refresh rate on any computer I tried.
This entire performance story is, of course, about procrastination: instead of doing the hard thing, such as making levels that are fun to play, I worked on parts that are fun to fiddle with. So now I had a fast isometric renderer for the title screen, but no level design for the bulk of the game.
Here I made a decision which, for a change, I didn't regret immediately: to use Aseprite for a level editor. If you're not familiar, Aseprite is an open source pixel art editor, and it's as gorgeous as it is convenient to work in.
Having made a Python script to convert PNG files to a BigInt EEBE format, I now had a toolchain to iterate on the levels very efficiently. And iterate on the levels I did: turns out it's so hard to come up with a clever puzzle! Who would've thunk.
Some of my earlier designs were laughably broken. One of the stages I thought was impressively challenging could be solved by pressing, quite literally, right, up, right. That's it, that's the entire solution. I have failed to see this (elegant, but very much unintended) answer for the longest time, and only discovered it thanks to a teammate, whom I coerced into early playtesting.
This time I wanted to make a real appearance in the Decentralized category of the compo, so I decided I'll make a level editor. Levels are relatively compact, so I put them in the URL of the game. This way anyone can make a level and share it via any means: on a social network, git repository, by email, or a graffiti of a QR code on a neighbor's house. (Please send me a picture of that!)
There's a Community Levels page with a collection of stages made by you. Send pull requests, or just drop me a link to the stage you've made, and I'll add it.
The game is also available on IPFS: ipfs://bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb..
In case you're interested in low-level stuff, in no particular order:
Super Castle Game uses natlib, a highly composable library for small games. I wrote both, so it's not a sound endorsement.
Speaking of build, this year I wrote my build scripts in both bash and PowerShell. The PowerShell one is very slow for no apparent reason, but it made switching between a MacBook and a Windows PC so easy that I'm not even mad. Compared to Windows Subsystem for Linux, which is low key abominable, PowerShell hits different.
Having no artistic ability whatsoever, I chose the Sweetie 16 palette by GrafxKid as my main driver. Best choice ever made in the history of choices.
The pixel font is from the TIC-80 fantasy computer.
There's also gamepad support, and turns out it's really easy to implement! Of course I only read the left analog stick and the B button, but it's still impressive how straightforward the Gamepad API is, especially compared to others. (I am truly convinced that the Web Audio API was designed by a committee of sentient guacamoles from hell.)
Anyway, have fun with the source code, it's a tangled mess! I'll try to clean up some of the more egregious parts. It's under the GPLv3 license, so feel free (as in freedom) to do whatever.
Thank you for reading! Or scrolling to the end! If the js13k voting is still happening, please play Super Castle Game and vote. Then play other games, too, there are some truly phenomenal ones in there.
Follow me on GitHub for more spaghetti code.