For the last few months, I've been working with a group of my coworkers to build a small game based on the old Hasbro title Hungry Hungry Hippos. We built the game for a company-wide artist showcase, spurred on by our art director's desire to do something different than what the other artists at the company would be doing. After a few months of semi-dedicated work, we've released version 1.0 of the game! You can play it now by simply visiting HungryHipp.us on your phone, though to actually see what's going on you'll need to also pull up HungryHipp.us/host on a computer.
In developing HungryHipp.us, I took the opportunity to try out some new technologies and project management strategies, and in doing so I learned a number of interesting things. In this post-mortem, I'll go over what I've learned, and discuss what new things I'll be bringing with me for future projects.
So what's the game?
The game itself is pretty simple. As a player, pull up HungryHipp.us in your phone's web browser and tap as fast as you can to gain points. Periodically your phone will buzz and a red dot will appear; Don't be the last player to tap it or you'll be knocked out. First player to tap the red dot gets bonus points. To see all the players and how many points they have, pull up HungryHipp.us/host on a computer (or any other device with a large screen).
The game is meant to be played in a festival setting, where the host screen is left on display, and people can join in as they wander past. Anybody can join in on their phone, and there are no time limits or rounds: You play as long as you want, and can leave at any time.
The biggest thing that I wanted to try while working on this project is an iterative approach to building the game. In all previous game development projects, my team has wildly over-scoped our development efforts. After several failed projects, I've spend a lot of time thinking about how to better manage future efforts. At some point I came to the conclusion that an iterative process would help solve this problem, and so I used this project as opportunity to try it out.
To explain the iterative approach, it's helpful to understand the non-iterative approach that I've used in the past. Generally, when starting a new game, the development process starts with brainstorming, where the team sits down and figures out what exactly we're going to make. This is generally brief, done in a single session, and once complete, the team breaks up by discipline to start building the various parts of the game (art, programming, sound, etc.). At this point, the game is built in a piecemeal fashion: Within each discipline, the game is built one part at a time until all parts of the game have been added. Once all the pieces are in place, the initial version of the game is done. From there, the team gets feedback from players, decides on what changes they want to make in order to improve the game, and then works towards those goals in the same piecemeal fashion. Hopefully we're done with that work when the deadline arrives. If not, the game is likely to be incomplete, where it's difficult to play or may not even run at all.
The iterative approach is almost exactly the same, but condenses the "determine an end goal, then work towards that end goal" process into a very short time span: For HungryHipp.us, we worked in one week sprints. The goal of each sprint shouldn't be to deliver a single part of the final game, rather the goal should be to have a cohesive, working game at the end of each sprint. This addresses 2 fundamental issues that I've seen in past projects:
- Scope. By forcing the team to consider only doing as much work as they can get done in a week, you encourage them to break work into small, manageable chunks.
- Delivering a finished product. Focusing on having a "finished" game at the end of each sprint means that you'll always have something to show for your efforts, even if issues come up that slow down development or stop development early.
Clearly I had high expectations for this methodology, but how did it pan out? Well, pretty okay. The biggest impact that this had was that, for maybe the first time ever, the project had a reasonable scope. Focusing on how much work we can do in a single sprint, rather than on how much work we think we could do with all the time available, made it much easier for us to be realistic about how much work we could do. What's especially interesting is that we did overestimate the first couple of sprints, but we quickly dialed back the amount of work for subsequent ones as we got a feel for how much work we were actually getting done.
In terms of delivering a higher quality game at the end, I'm not sure I can draw as strong a correlation. The final game still has balance issues, is short on variety (needs more content and more mechanics to play with), and is awfully buggy. While I had hoped for better results, it's clear that the methodology alone isn't enough to ensure that we deliver a quality product. That said, I think that's more on us as the development team than the methodology. What I noticed was that, by working one sprint at a time, we were regularly given the opportunity to adjust our trajectory and change priorities. The amount of content we could make was mostly limited by how much time we had, and the most any methodology can do is help us best allocate what little time there was.
The last thing to point out is that we were delivering new versions of the game every week. At the very beginning of the project I set up a system that would push the latest changes live to the website each time I finished some small amount of work. This kind of automation isn't common in small projects, but it was super helpful to have. Making sure each week that we not only had a version of the game that was ready to be deployed, but that we actually had it live for other people to see, meant that we were able to get regular feedback throughout the development process. In turn, that feedback helped us set priorities for subsequent sprints, as we were easily able to identify which changes would make for the biggest improvements to the game.
Squash-merging in Git
For this project I tried squash-merging all PRs in GitHub. The end result is a clean commit log, including links the the PR for each commit. The only problem I ran into was that squash-merging breaks my ability to branch off a branch. The normal development process for me goes like this:
- Branch off of master.
- Do all work for current issue on the new topic branch.
- Once work is done, create a PR back to master.
- Merge PR once CI tests pass and someone has reviewed my changes.
While waiting for the PR to be approved, I'll sometimes create a new branch off of the existing topic branch so that I can prepare work for the next PR. Git's branching and merging functionality means that I can easily merge this branch back to master later without conflict.
With squash-merging, this stops working. When you squash-merge onto master, you lose the commit history for the merged branch. The second branch I made still has all the commit history, though, so when I go to merge that branch down to master, it results in merge conflicts because Git thinks that the same changes were made twice. The savvy Git user will recognize that the solution to this issue is Rebasing. This would allow me to "replay" the commits from the new branch over the new, squashed commit on master. I haven't tried rebasing to get around this problem yet, but I'll continue to experiment with it in future projects.
Building and deploying a web app
This project was my first attempt at building a web app, in particular it was my first time building an HTTP server and deploying it publicly. Since I prefer to do software development in Rust where possible, I opted to use the Rocket framework to build the backend code. My experience here was pretty great. Rocket proved to be well designed and easy to use, and it made it really easy for me to build a solid backend despite my lack of server development experience. I only ran into a couple of issues with Rocket, both of which were easy to work around:
- Lack of async support. By default, Rocket spawns multiple worker threads to handle incoming requests, but I ran into an issue where the server freezes if all threads are blocked. This issues wouldn't occur if Rocket were async, but it was easy to work around by using more threads.
- Lack of websocket support. Websocket support doesn't exist because of the lack of async support, but Rocket is a good team player and doesn't mind if you spin up a separate websocket server on another thread. It's unfortunate that I need to open up another port for the websocket server as a result, but it didn't take too much to get things working.
The front end code is fairly simple, all static JS, HTML, and CSS files served directly by Rocket. I didn't use npm or webpack because I wanted a "one click" setup for development and I was already using Cargo to build the server. The goal was to keep local development as simple as:
- Clone the repo.
More challenging than building a web app was deploying it to AWS. Fortunately, I had the help of my team's DevOps engineer, and his experience with AWS proved helpful. I was able to setup build/deployment automation with Travis and AWS CodeDeploy to automatically build all changes on
master and push them live on a single EC2 instance. I'm sure the solution I setup isn't quite production ready, but for a small project, having automated deployments made the development process a lot smoother.
- Using enums for state machines is real nice, but has ergonomic issues. I opted to use a method where I would destructure the current state in a
matchstatement and then return the new state, using Rust's ownership semantics to ensure that there was only ever one state at a time and that the state is always valid. While this worked really well to avoid bugs, even a fairly simple state machine can turn into a huge mess of code. I think there's room to improve, though, it'll just take some iteration.
- Quick iteration and being able to tweak numbers to get things to "feel" right is super important. Browser development with static assets makes this easy, as the iteration process can be as simple as editing your CSS file and refreshing the page. Depending on what you're doing, you may have to take deliberate steps to ensure that this kind of iteration is quick and easy to do. This is definitely worth it.9
- Slow jams (i.e. game jams that last several weeks or several month) are fun, and I want to do more of them. Maybe month long jams would be a good way to collaborate with other game devs.