Roomba Dash is an online multiplayer game based on a boardgame in which you solve puzzles in as few steps as possible while being quicker than your friends.
Check it out here: Roomba Dash
Gitlab
Origins #
Based on the Boardgame “Rasende Roboter”, the idea to make Roomba Dash arose from an evening of boardgames. While the original ideas thrown around were more centered around developing algorithms or neural nets to solve the puzzle, my interests switched to using this as an excuse to learn some more Go and develop a modularized server structure. I also wanted to try to make the design highly scalable in theory, as you would expect from a multiplayer game with lots of players. Motivated by an upcoming Hackathon, I started making some plans for how to make my first multiplayer game.
Server Structure #
Basically microservices. I came up with the following components:
A Login Server which would handle all authentication including user account creation as well as making sure, that the other components’ internal api endpoints were only accessible by other services and not users.
So far it’s capable of registering users and authenticating them, of course. It can create ‘anonymous’ accounts for quick play functionality, as forcing people to create accounts is the easiest way to convince them it’s not worth trying out your game. Users are provided a JWT which they can then use to access other services.
For the other microservices it provides endpoints to validate user-provided tokens and to fetch user objects for more information.
Users can then contact the Lobby Server which handles all organisational stuff related to creating or joining games as well as browsing public lobbies (games).
For the service this means it needs to know about all running games as well as being able to create new games. We’ll get back to this, because we first need to talk about..
..Game Servers which contain all the knowledge about how to run a game. As a game server we expect to be instructed to host a game, after which we can process connecting players and start an actual game.
In my structure I treat the game servers like a pool of resources, with the added annoyance/constraint that while they don’t have to handle persistent state, we also can’t treat them like a pool of nameless workers because we need to make sure players connect to the right server. To add to this we also can’t just scale them up and down without making sure we aren’t shutting down any running games.
This means the lobby server and users need to be able to contact each individual game server. If you know how Deployments and Services behave in Kubernetes, there are a few immediate problems that come to mind: Services pointed at Deployments (with >1 replicas) use load balancing strategies to distribute traffic, while StatefulSets get individual Service endpoints (game-0.game.rd.svc.cluster.local
, etc) and how do I stop Kubernetes from killing my non-empty game servers?
I ended up using a Deployment for my game servers with the following system: Since I use Traefik for my ingress, I am able to use its sticky session and forward auth features to preconfigure incoming requests to be directed to the correct Pod. Because Traefik uses the Pod IP as identifier, it’s possible to just announce every game server IP to the lobby server on startup, which it can then use to set the sticky session cookie for incoming game joins as well as contact the game server itself to set up games. While I had my doubts about how well this would work, I had surprisingly little issues with this.
When updating the Deployment, Kubernetes lets you set terminationGracePeriodSeconds
to delay Pods being immediately killed after they have been marked for termination. This combined with properly handling the SIGTERM that is sent to the Pod when marked, enables the game server to finish any running games (within the time limit) and exit gracefully.
Client #
The most accessible way to make a client turned out to be making a Website, as this would enable new People to try out the game without having to download anything. It was also beneficial due to the fact that both me and my friend Josef (who did the initial work on the client during a Hackathon) already had some knowledge with Vue.js / Nuxt.
After some initial page designs were made there was quite a bit of effort to do in terms of managing the login flow, gamestate and have a properly working game board.
Conclusion #
Personally I’m quite happy with the outcome of this project. It took waaay longer to have something functional than I thought though. After I had a basic plan I started with developing the login server half a week before the Hackathon so we could focus more on the game during the event. We had about 2 days (Friday till Sunday) to work on things and at the end we had the lobby server and a client which could login, create a game and display a static map. It took me about 2 weeks more work to make things actually playable.
Currently the game is in a functional state though there are lot of small tweaks I want to make. Firstly I want to make the quickplay option in the login more obvious, since new people get confused by the login screen. Then the default settings for a new game are a bit too quick and people als struggle understanding how the game is supposed to be played, so the rules need some work.
I also have been made aware that the design holds up badly for colourblind people and am thinking of different ways to fix that. Either some clever patterns or maybe only lighting up the important tiles and fading/hiding the rest enough to contrast it better.
Feel free to try it out and let me know what you think!