Building My First Game With Realtime Data — Tamago-Train!

Jo Franchetti
The Startup
Published in
8 min readJul 24, 2020

The Station Manager game with Realtime Tube data

I’ve been learning a lot recently about using realtime data streams and how and why one might use them in an app. In order to better understand the differences between realtime streaming data and REST APIs (which I’ve had more experience with), I decided to build a game, the mechanic of which uses realtime train arrival data. As trains arrive into the station in real life, effects are triggered in the game which the user has to manage.

Make it your own!

All of the code for the game is on Glitch. This means that you can see the code, ‘remix’ it and make it your own. There is a thorough readme file in the repo and I’ll also go over some of the methods used in this blog post.

Getting Arrivals Data

Ably has a Hub of realtime data streams for developers to try out and build apps with. I used the London Tube Schedule stream, which provides a stream of various data from TFL; including arrivals at a given station. For the tamagotchi game, I wanted to find out the arrivals at a busy station, so that I would have lots of trains arriving in close succession. I chose King’s Cross station for this reason. The data stream uses the station NAPTAN code rather than it’s name to get the correct data, so I had to look up the correct code for King’s Cross (you can look up station codes here), which is 940GZZLUKSX.
The stream I’ll therefore be using is:

[product:ably-tfl/tube]tube:940GZZLUKSX:arrivals

Every time the data from TFL is updated, this channel will publish a message with the new arrival times of trains into Kings Cross. The joy of the data being a realtime stream means that I don’t have to poll for the data, as I would with a REST API, instead, I establish a connection once and data is published to the channel as and when updates happen.

Connecting to a Data Stream

In order to connect to the data stream I used the Ably Javascript SDK. To do so, I need an Ably API key which comes with a free account. To keep my API key safe, I also used Token Authentication which makes a Token Request on the server side which is handed to the Ably realtime client to authenticate. There is a brilliant walkthrough of how to implement Token Authentication here:

TFL Data

The data published by the stream looks a little like this ↓

An example of the data published on the TFL stream

It is a large array of train objects each with a lot of information. For the sake of this game, the only information I’m really interested in is the TimeToStation value. I can use these numbers to calculate when to cause a train to arrive into the station in the game.

I could have created all kinds of interesting extensions to the game, with multiple platforms and colour coded trains for their lines, even maybe an arrivals board with train destinations, but let’s not get too carried away…

Game mechanics

Congratulations! You’re the newest TamagoTrain Station Controller! Now you’ve got to keep your station in fine fettle.
Don’t let your station get too hot, don’t let your platform fill up with passengers, litter or mice!

  • Trains raise the temperature of your station, as do passengers
  • If it gets too hot, passengers will start to faint!
  • Unconscious passengers can’t leave the platform
  • Passengers sometimes drop litter
  • Too much litter attracts mice!
  • Trash and mice all take up space on the platform making it difficult for your passengers to exit
  • If your platform gets too full, too hot or too cold your station will have to shut and your game will end

How to play

  • Clean the platform to clear away litter
  • Vent cold air through the station to keep everyone cool (but don’t get carried away!)
  • Passengers departing through the exit will cool the platforms down a little
  • Departing trains also cool the platform slightly
  • You can charm mice with songs! They’ll find their way off the platform if musically enticed
  • Music will also wake up fainted passengers

Game Code

The game is an expressJS app. It is split into two parts — the simulation/game loop, which runs in ‘ticks’ and the ui/render loop which runs at 30 frames per second. This separation prevents us from tying the game logic to the frame rate, which will reduce the chance of the frame rate dropping if the game logic gets complicated. (If you’re interested, this is a great intro to game loops.)

Game.js

The Game.js file is the main control loop for the game - in it, we define a JavaScript class called Game. When games are created, we create a new instance of this class to hold the game state. This is also where we set up the tick() function, which is called once per second. This ‘tick’ steps the simulation forward by iterating the game loop. It ‘ticks’ the game entities (the platform, passengers and trains), applies any problems (adding litter and mice) and applies any buffs (cleaning, venting or music).

The only input the user can supply is applying a Buff — either clean, vent or music, which are triggered by the buttons in the UI. When pressed, these buttons add a Buff object to an array in the Game instance, that we use as a queue of actions. Buffs can only be added to the queue a maximum of 3 times, after that clicking the buttons in the UI will just return until the Buff has been taken off the queue.

The Game instance is responsible for three core things

  • Handling train arrival/departure messages, and routing them to the platform
  • Creating instances of Buffs
  • Checking for game over

All the rest of the game logic happens in the tick() functions found on the Entities, Problems and Buffs.

GameUI.js

The GameUi.js file is where the rendering of the game happens. It uses an observer pattern to keep track of the game state.

30 times a second the GameUI.draw() function is called and passed a snapshot of the game state. GameUI instance keeps track of the last state it was called with so that it can avoid re-drawing things that have not changed.

The GameUi class has a collection called _renderingFunctions — a list of functions it calls in order, each being passed the current game state. If any rendering function returns a value of -1, we use this as a signal to stop drawing to the screen and to display the Game Over screen. The rendering code places absolutely positioned divs on the page which are styled in the css. The divs contain animated gifs of the entities, buffs and problems. The appearance of the divs is changed by adding css classes and data-attributes dependant on the problems or buffs that have been applied in the game state.

Entities, Buffs and Problems

By default, when an instance of Game is created, a Platform entity is created. This platform has some basic state (an age measured in ticks, a width, a height) along with the three core stats the game is ranked on - hygiene, temperature and capacity. The game is won or lost based on the state of these variables, which the game evaluates each tick. As the game ticks, the Game instance will process any objects in its queue first in first out, creating an instance of the requested Buff and applying it to the Platform.

When the Platform ticks, the following things happen -

  • Any unprocessed messages are read, FIFO.
  • If a message for a train arrival or departure is found a train is created on the platform or removed from it.
  • All tickables are ticked.
  • Any completed contents or buffs are removed — an item is deemed complete if a property completed is present, and set to true on the object.

The tickables that the platform stores are:

  • Any present train
  • All of the contents of the platform
  • All of the buffs applied to the platform

On each tick, the item that is being ticked gets handed the current instance of the platform, and based on the logic in that item’s class, it can mutate the properties of the platform. For instance - every tick, a Mouse could reduce the hygiene property of the platform.

The rest of the entities, Buffs and Problems are all JavaScript classes that can mutate the state of the Platform instance in their tick method.

  • Both Entities and Problems have x and y coordinates that are used to move them around the user interface.
  • Problems all inherit from a Base Class called Problem which creates these properties by default for them.

A problem looks like this:

https://gist.github.com/thisisjofrank/81dc5c0c542feaed47e922e0fb218a0a

Entities and problems hold state which will cause effects during the lifetime of a game. For example:

  • Travellers walk towards the exit by moving 10 pixels closer to the exit each tick
  • Travellers have a chance of dropping litter
  • Litter has a chance of adding mice to the platform
  • Trains add an extra Traveller to the platform every tick

All of this logic exists in the tick function of each kind of entity or problem.

Starting the Game

The game uses webpack to bundle the client side JavaScript. The script.js file is the webpack entry point, webpack bundles together all of the JavaScript files before they are sent to the browser. This is great because it means we only need to reference script.js to start the game.

The script.js file is referenced in the index.html file and it takes care of starting new games. It contains a startGame() function which does all the work:

https://gist.github.com/thisisjofrank/e026bf204d497852cdd3857bc1d7fd94

This function:

  1. Creates a game instance
  2. Creates an instance of the GameUI class, passing it a reference to the new game instance
  3. Calls game.start() passing a configuration object of two actions - one to execute on start, one on end.
    - the onGameStart action listens for events on the dataSource
    - the onGameEnd action disconnects the dataSource to stop the game from using up messages that we don't need.
  4. The ui.startRendering() function is called which will set up the render loop
  5. Finally the game is returned so that the UI buttons will work in the browser.

Game Over

Game failure states are managed in the Game.js file, in the function isGameOver(). This contains a collection of objects with functions for different failure conditions. At the start of each tick, each of those functions are run and if any of them return true then the game is over.

Have fun!

I hope you’ve enjoyed playing the game and will enjoy making your own versions, or even adding some extensions to mine. If you’ve any questions about the game or about realtime data streaming you can drop me a message in the comments or get me @thisisjofrank on twitter. I’d also love to see any remixes you make!

--

--