Andrey Listopadov

Fennel Game Jam 2022

Quite recently a Fennel game jam happened on and I’ve decided to participate. I’ve been part of the Fennel community for some years, and every once in a while a lot of people from this community participated in a lisp game jam, but I’ve never made a game before, so I’ve skipped these events. However, when the Fennel-specific game jam was announced I decided that I must finally put myself together and participate.

The rules of the jam were quite relaxed - you basically had to use fennel for game programming, and release the game under an OSI approved license. No theme, no engine restrictions, no size requirement - an ideal situation for a programmer like me, who doesn’t know how to make a game, at all. When I was young, and I mean, like almost 20 years ago, I only toyed with Macromedia Flash and ActionScript 2.0 at my school computer class, making interactive cartoons, which can hardly be considered games. It’s not like I’m a complete stranger to game programming though, we had a very brief introduction to game programming at the university, except we never actually programmed games during these courses. So, for example, I know what delta time is, how it should be used, or how to do vector math needed for basic movement, but this doesn’t count as experience. If you read my blog, you may remember my small projects like raycasting and raymarching, which feature movement, camera programming, basic collision detection, and a basic game-loop. So actually I felt prepared when I decided to enter the jam.

I’ve already tried LÖVE2D in the mentioned raymarching example, so I’ve decided to change things up a bit since I’ve wanted to explore another project - TIC-80. The change from LÖVE2D to TIC-80 actually made it easier for me, since I could do the sprite-work and sound effects inside tic’s inbuilt editors. So I’ve begun to work on it.

I’ll try to structure this post as a series of days based on the notes I made during the event. There probably will not be any code though, as I have no early versions, since I’ve opted out of using version control systems for this project, to keep the retro vibe of the fantasy computer.

Day 1

The hardest day in my opinion because it is the day when I have to start working on a project I haven’t had any thoughts about beforehand. I’ve sat down and thought about what kind of game I can make in the limited time of the event which was going from May 25 to Jun 1st. Given 5 days and almost no game development experience, I’ve decided to play it safe and make a Breakout-like game. Though a plain breakout clone would be a bit boring, so I’ve decided that I’ll put a small twist once you destroy the final block which would shake things a bit and hopefully be a welcome surprise.

Now, since the theme was chosen I at least knew what I needed to start - a ball, a platform, and a box to bounce the ball to prevent it from flying away to infinity. But I didn’t know how to do anything in tic, so I’ve opened up a manual. The manual is minimalist, to say the least, and doesn’t provide a lot of info beyond essential things. So I’ve picked up the circ rect and rectb functions and started working. This was my first result:

This has very basic hard-coded physics, meaning that I’ve literally defined a vector of all ball velocities and just cycled through it when the ball collided with any world object. But it already has some interesting stuff done, which I haven’t changed much afterward.

The first thing is that It has a proper world to screen coordinate transformation, which I’ve kinda never done before. It’s a simple thing, I just have a world X and Y offsets that made the game world, e.g. the box rest, not in the top left corner but 10 pixels to the right and down. All game physics was calculated with the top left corner in 0,0 and then translated before drawing.

The platform tracks its position and makes sure not to go outside the world boundaries. And the ball is checked for collisions with everything but the lower boundary. There, instead of a collision a basic game-over? flag is set to interrupt the game loop.

I was pretty satisfied for day one, especially since it wasn’t really a full day of me programming this, but only 3 hours after work. The jam started at 10:00 AM in my time zone, and my work day also starts at the same time, so I had to begin working on a game 9 hours after the start of the jam. I’m sure this is a common situation for most other participants, though perhaps they have had time before work too.

Day 2

I knew that I’ll not be able to move forward with the current physics system I’ve added to the game, so I spent most of the second day fighting with the collision system. This was the result I got:

This had really wonky surface normal calculations, and you can see that the ball isn’t particularly happy with the results, as it just decides to leave the game. I wasn’t happy with it either, but it was a move in the right direction.

In addition to improved physics, I’ve added destructible bricks with a random amount of health represented by a color. The color was an accident, as I used brick health in place of the brick color when bricks were drawn on the screen, but I actually liked it. The health was distributed randomly, but I’ve been focused on physics, so I’ve left it as is.

There were two main problems. First, I’ve re-used some linear algebra code from my raymarching project, mainly the calculation of the distance to the box and the calculation of the normal. However, the calculation of a normal was done for a 3D world and wasn’t as meaningful for 2D space. I’m not sure whether I’ll be able to explain the general method, but the idea is that I’ve found an intersection point, and picked two more points one below the surface and to the left and another above the surface and to the right. Then I used some vector math to find a normal based on distance estimation from these points to the intersection point. This works quite well for 3D, given that all reflections in the raymarching demo were correct, but in 2D it resulted in weird unexpected reflections (not in the video).

Unfortunately, I have this kind of problem that once I’ve fixated on some solution I can’t see another one until I step away from the problem for a bit. So I’ve kept the current physics and ended the day 2 session.

Day 3

This day was mostly spent polishing and rewriting the collision system. I’ve almost fully fixed bugs with unexpected angles, but the ball still managed to find its way inside the platform. I couldn’t spend too much time on it, so I’ve decided to leave it as is, and maybe fix it after everything else is done.

In addition to that, I’ve made it such that the block health depends on the row it was spawned at. So the lower blocks had 1 health, the next row above it had 2 health, and so on. This can be seen with this nice color transition from purple to orange.

I’ve also done some basic point systems that added 100 points when you destroy a brick, and 10 points upon collision. There are now lives, so you can lose the ball several times before the “game over”. Satisfied with that I’ve ended the third day, realizing that theirs not that much time left, but also not too far til the game will be complete.

For the next two days, I was planning to add graphics and a small twist that will hopefully make the game a bit more interesting.

Day 4

Up until this day I’ve drawn everything with basic rectangles and this wasn’t going to cut for the jam in my opinion. So for the fourth day, I’ve decided to focus on some graphics and sounds.

The inbuilt sprite editor is quite good, actually. I’ve only tried the LibreSprite, an open-source fork of aseprite sprite editor, and it was a bit sluggish, perhaps because it was using a software cursor, which felt unresponsive. But with TIC’s sprite editor it wasn’t a problem and I quickly drew basic sprites for bricks, the platform, and the ball:

Figure 1: Sprites

Figure 1: Sprites

Then I decided to add power-ups.

There are three classic power-ups that affect the size of the ball or the platform - the big ball, the long platform, and the short platform. All these powers are timer-based, with a small exception for the big ball - if you lose the ball while it’s big, the power-up is also lost. In addition to that, there are two speed-related power-ups, one that increases the ball’s speed, and one that decreases it.

Speaking of speed, I’ve also made it so that the speed of the ball gradually grows during the game up to twice the initial speed. And there’s a mentioned power-up to increase and decrease the value by a quarter.

Here’s a demonstration of various power-ups:

As can be seen, the ball still can act weirdly, almost spinning around the platform, but I’ve already spent most of my time on graphics and sounds, so I’ve decided to left it for the last day.

Day 5

The last day of the jam! I’ve added more graphics, as I wasn’t able to figure out how to measure things in seconds, as the values of the time function were kinda weird. So I’ve opted out of colored bars that gradually decrease in length. I’ve also decided to add some effects, like screen shake when the ball is big. Here’s how it looks:

At this point, I’ve started working on the twist and polishing things out. For the twist I’ve decided to do a boss battle at the end of the game, essentially turning the Breakout game into Pong when the last brick is destroyed:

The boss has a fixed amount of health, and each hit with the ball removes some amount of it. If the big ball power-up is active the ball deals twice the damage. I’ve actually made it that this damage multiplier also affects bricks, so bricks are destroyed a bit faster now too. And if you score the boss, it’ll lose 5 times hp of a single hit, although it’s almost impossible, unless you picked a lot of speed power-ups.

Boss’ health is represented with another bar that changes color depending on the health range, going from green at full to red near the end. To win the game you must defeat the boss, which is actually a bit tricky and I can’t do it consistently myself either. Speaking of power-ups, there’s a random chance to spawn a power-up in a random location during the boss battle. The chance of spawning is very low though.

I’ve uploaded the game to itch as a web build, and noticed a weird problem. All power-ups were wearing off too fast! I couldn’t understand what the problem is, because to my knowledge I did a proper way of handling power-up times - I obtained the current game-time with the call to the time function, added a fixed amount to it, and checked on each frame if the value returned by time surpassed it. To simplify, upon activation of a timer based power-up, I did this end = time() + powerup_duration, and checked for end < time() to disable the power-up. Given that it is based on time and not on some ticking mechanism, I’ve thought that it should work equally well regardless of frames per second. Even more weird was that the ball speed was the same as on the desktop. So in short - power-ups ended a lot faster, yet the rest of the speed was the same.

I’ve decided to do it “properly” and calculated the delta time. Then I multiplied ball movement by newly calculated dt and reworked power-ups to work based on ticks, meaning they weren’t tied to time directly. Checked that all works well on my desktop I’ve made a fresh web build, uploaded it to itch, and it was way too fast! The ball speed now was superfast, power-ups ended almost immediately. After a ton of debugging, and trying to make any sense out of the phrase that the main TIC function is called 60 times per second, when it clearly wasn’t, as the game speed was different between the web and Linux builds. Then I made a Windows build, and it worked with the same speed as the web build, and I thought that maybe it was a bug in the Linux build. I’ve checked what value of dt was calculated on each platform, and the Linux build had a 10-fold difference from other platforms:

Figure 2: Delta time differences between platforms

Figure 2: Delta time differences between platforms

The Linux build had a delta time value of approximately 0.001 while the web and Windows build had 0.02. I’ve tried an older version of TIC, and it had no such issue but didn’t support the Fennel version I was using for the game. So I had to distribute the game only as a web build until the issue is fixed. I’ve tweaked the speed by adding a game_speed parameter that I could tweak around inside the web build to make the speed feel right. After that, I pushed a final web build with these tweaks and submitted the game.

Jam results

Because of my time zone, I was the first who submitted a game, but I couldn’t do any more work because the last hours of the jam were at nighttime here. Out of 21 participants, only seven games were submitted, which actually a pretty great result, in my opinion, it’s a solid third! Funnily enough, we have about 60 people in our #fennel IRC channel on Libera.Chat, so 21 people is a third too. The results can be viewed here, and I suggest you try all games, as there are pretty cool entries!

I managed to score third place, which actually surprised me a bit, given how simple the concept of my game was. But I’m glad, and I think it’s a solid result for the first game.

In retrospect, I think I could do some things better, especially in the user interface department. Adding a proper menu, maybe an intro, making the interface look more like an arcade cabinet, which I’ve originally attempted, but my pixel-art skills are not that great yet. But I still like the result, I feel that for a first game it turned out pretty great. And the process was quite fun, which is the most important thing for me.

I hope I’ll participate in more lisp-themed jams, maybe in a lisp game jam or the next Fennel jam. I’ve always wanted to make games, and I had some ideas about the game I want to make in the Metroidvania genre. Game jams are perfect for experiments, and you can learn a lot by doing things you never did before. I’m not going to make a whole game during the jam as I want it to be a more complete thing, but the jam can help me to flesh out some main mechanics, be it the movement, or world-building practice. So I’m looking forward to the upcoming events!

This was a lot of fun, and I hope more people will join next time! Thanks to everyone who participated, Matt Roelle for organizing the Jam, and everyone involved in Fennel and TIC-80 development.