The Elm Game Jam #4 on the theme of animals and nature just ended this week. My submission was an ecosystem simulator where players attempt to balance a population of foxes and rabbits by fiddling with population parameters such as metabolism rates or energy costs for reproduction.
Here are some lessons I learned and concepts that were re-affirmed by building here:
Get to playable ASAP
This is something I’ve talked about before, but didn’t do a good job of putting into practice on this project. I sank too much time into the rules for interactions between foxes and rabbits and ended up a week before the deadline with a “game” didn’t end and had no meaningful player decisions.
For future jams, I would aggressively push for the following milestones before investing in other things:
- Show something visual on screen
- Have a way for users to interact with the game
- Have game end conditions
Refactoring
Elm is so fun to refactor! Part of the fun of a game jam side project is exploring data modeling and changes that have to happen as the game evolves. However, it can easily prevent you from shipping. I feel like I did a really good job on this project at balancing refactoring and feature work.
One really valuable thing I did was keep refactoring and feature work in separate commits. These typically took one of two forms: either cleaning up tech debt after a feature, or “making the change easy” before a feature. Both can be seen on the screenshot below.
Invest in your tools
Some small investments here really payed off. I documented the processes I used to build and release the app as part of the README. In combination with some handy bin/build
and bin/server
scripts, this made it easy to jump back into the project after a week or two not touching it.
Part-way through the project, I learned about butler, itch.io’s CLI tool. This made it much easier to release a new version to itch than fiddling around on the web UI. I wish I’d started using butler
earlier on the project.
I automated deploys to Netlify on pushes to GitHub which is really nice. I wish I’d taken the time to do something similar with itch, or at least written up a bin/release
script.
Yak shaving
Part-way through the project, I noticed that foxes were moving to bizarre locations. I quickly realized that this was due to a bug in the way a grid library I was using calculated neighbors. I dug into the source and quickly got lost on a yak-shaving adventure. Eventually, I was able fix the issue and copied over a patched version of the library into my project.
Figuring out this bug was a significant time-sink and also killed my motivation to work on the game for a few weeks. I was hoping that using a grid library would allow me to focus on game logic but between this bug and having to layer on extra behavior to fit my use-case, I’m not sure I saved a lot of time here.
This may have been a case of pulling in a dependency that was the wrong tool for the job since the project was initially created with somewhat different use-cases in mind. On the plus side, I can probably extract several nice patches that can be submitted to the upstream project.
Draw your problems
I often find that expressing problems in a visual medium is a handy debugging tool, particularly for spatial problems. This technique saved my bacon when debugging the incorrect neighbors issue mentioned above.
A 3x3 square contains all the possible configurations of corner, edge, and center pieces so I drew out a series of 3x3 squares showing, for a given cell, what cells I considered neighbors and which cells the library told me were neighbors. With this diagram, I was able to see that the library worked fine on center pieces but broke down when dealing with (literal!) edge and corner cases.
As a follow-up, I turned this diagram into a series of tests to help guide my exploration. Eventually, I was able to track the behavior down to a subtle combination of two different bugs
CSS Grid
Game jams like this are a nice chance to try out a new library or technology. On this project, I used CSS grid for the first time. It reminds me a bit of the way elm-ui works. I only did enough to get a basic skeleton layout but would be interested in digging deeper
Custom types led to a nicer API
There are a lot of numbers going around in the game and eventually I decided to tag all of the energy values with a custom Energy
type. You can’t do math directly on custom types and implementing equivalent energyAdd
functions was a bit ugly. After some thought, I realized actual things I wanted to do were not math but domain operations. I went from code that looked like:
if fox.food > foxBirthCost + foxCostOfLiving then
to
if canSupportCosts [ foxBirthCost, foxCostOfLiving ] fox then
Turns out I can have my cake and eat it too!
Dynamic record setters
I have a bunch of different form inputs that set energy values on a record. Since these are all really similar and it was getting annoying to have a message for each one, I created some helper functions to allow me to have a single message and to dynamically set keys in the record. I didn’t want to use a Dict because the keys are fixed.
This pattern isn’t particularly pretty but can occasionally be convenient, particularly if dealing with a combinatorial explosion of messages. In general though I don’t think it’s worth it. In this particular case, it might have been better to keep the verbose approach with one message per field
type alias RabbitConfig =
{ costOfLiving : Energy
, grassNutrition : Energy
, birthCost : Energy
}
type RabbitField
= RabbitCostOfLiving
| GrassNutrition
| RabbitBirthCost
setRabbitConfigField : RabbitField -> Energy -> RabbitConfig -> RabbitConfig
setRabbitConfigField field energy config =
case field of
RabbitCostOfLiving -> { config | costOfLiving = energy }
GrassNutrition -> { config | grassNutrition = energy }
RabbitBirthCost -> { config | birthCost = energy }