Some folks spent this November growing mustaches. Others were writing novels. I was writing a game to enter in Github's annual game jam. The fruit of my labour is Mander, a puzzle game about hacking democracy (desktop only—sorry mobile users!). It's written in Elm, and while the game is still very unpolished, working on it taught me some useful things about writing non-trivial programs in Elm.
Elm is already much touted for its ergonomic type system, freedom from runtime crashes, and solid performance, so I'm going to focus specifically on the pros and cons of using Elm to make a browser-based game.
Elm's type system does not eliminate all run-time bugs. Even when my code compiled I encountered display glitches and inconsistent runtime state. For all its utility, Elm's type system doesn't allow you to naturally express types like "A connected set of orthogonally adjacent coordinates" or "A number between 1 and the size of this list", and it can't guarantee that what you wrote is what you meant. Sorry, you still need to do some kind of testing to make sure your program isn't just technically correct.
My testing strategy was to build interactive prototypes of each game system and play with them before I moved on. This worked beautifully. Once I was satisfied that a system worked, the type system gave me confidence that I was using it correctly for the rest of the project. I also quickly saw ways to improve the UI or simplify the rules, so the prototypes also helped me iterate quickly.
By far the most useful feedback I got came from user testing (i.e. making my ever-patient partner play my almost-finished game). Besides being highly recommended for software, user testing a game is particularly important. It's all too easy to assume folks will know how to play intuitively. Watching another person play Mander, it was easy to see that the rules were not obvious. Because I had an early prototype and a dependable type system, I was able to make adjustments, confident that I wasn't breaking anything.
Writing code in Elm's walled garden of functional purity and static
types is blissful; sometimes dealing with the web platform is not. In
particular, I used the
elm-lang/svg library to draw the
map, and the too-familiar bugbears of building for the web bit me.
While building prototypes, I naively built my views with only mouse events, expecting that I could easily add support for touch events later. Big mistake! SVG touch events are hard, and the combination of this difficulty and the Elm SVG library's limited API proved too much for me. The game "shipped" without touch support.
So if touch support is important make sure you figure it out early. There may come a glorious time when we can all use pointer events and have a single API; I watch for it, trembling with anticipation. For now you'll have to handle each input type separately.
The Elm architecture is a pattern for building interactive applications. It has the DNA of MVC and command-query separation, and should feel familiar for fans of the Flux architecture. Briefly, you define a Model to hold your application state, and a View function which renders it to a displayable object (such as HTML). The displayed object creates Messages when the user interacts with it. Messages are sent to the Update function, which can change the model. It all gets put together in a Main module that defines the application's entry-point.
While working on Mander, I was pretty sure I had discovered some cases where this scheme was too simple. I had utility modules, a Data module for my Model to depend on, and my Update module was split into "more manageable chunks". I was a special, clever, innovative snowflake!
And of course I was over-complicating things. My special modules boiled down to this:
Apart from the Elm architecture components, I ended up with a View composed of sub-views and one utility module to configure how I draw SVGs.
So whether you're learning Elm or exporting its lessons to your environment of choice, before you embellish your architecture too much, consider keeping it simple. Build functions out of smaller functions. That utility module for calculating things about your Model? Just put it in with the Model. Having an app with a familiar layout and some chunky modules might be better than scattering your code across dozens of tiny ones.