Build a tamagotchi game with Realtime TFL Data — Tamago-Train!
Tags: javascript, gamedev, node, webdev
Published: 2020-07-30 09:25:52

The [ Station Manager game](https://trainagotchi.glitch.me/) with [Realtime Tube data](https://www.ably.io/hub/products/10)

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.

![](https://cdn-images-1.medium.com/max/1748/1*cScYOmEkxK59mXU5c3i_ow.gif)

#### Make it your own!

[All of the code for the game is on Glitch](https://glitch.com/~tamagotrain). 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](https://www.ably.io) has a [Hub of realtime data streams](https://www.ably.io/hub/products/10) for developers to try out and build apps with. I used the [London Tube Schedule stream](https://www.ably.io/hub/products/10), 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](https://data.gov.uk/dataset/ff93ffc1-6656-47d8-9155-85ea0b8f2251/national-public-transport-access-nodes-naptan) 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](https://dsx-sources.herokuapp.com/source/tfl)), 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](https://github.com/ably/ably-js). To do so, I need an [Ably API key](https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys) which comes with [a free account](https://www.ably.io/signup). To keep my API key safe, I also used [Token Authentication](https://www.ably.io/documentation/core-features/authentication#token-authentication) which makes a [Token Request](https://www.ably.io/documentation/realtime/authentication#token-request) on the server side which is handed to the [Ably realtime client](https://www.ably.io/documentation/realtime) to authenticate. There is a brilliant walkthrough of how to implement Token Authentication here:

{% glitch ably-client-side-api-calls %}

#### TFL Data

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

![An example of the data published on the TFL stream](https://cdn-images-1.medium.com/max/800/1*EPQK1DovrpjCs9ChrkhZfQ.png)

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…

![](https://cdn-images-1.medium.com/max/1400/1*LFQXN6273DwPsJv580MtFA.gif)

#### 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](https://expressjs.com/) 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](http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/).)

#### 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](https://pawelgrzybek.com/the-observer-pattern-in-javascript-explained/) 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.

![](https://cdn-images-1.medium.com/max/300/1*QPNt6CEbnlsPh7PO4Xf01Q.gif)

#### 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](<https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)>), 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 `tick`ed.

- 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:

{% gist 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.

![](https://cdn-images-1.medium.com/max/600/1*lSSh2DvBdtPQxJqlVvg26g.gif)

#### Starting the Game

The game uses [webpack](https://webpack.js.org/) 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:

{% gist 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.

![](https://cdn-images-1.medium.com/max/300/1*5SHwNuIiaQuwca3RO_-8lA.gif)

#### 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 ](https://twitter.com/ThisIsJoFrank)on twitter. I’d also love to see any remixes you make!

![](https://cdn-images-1.medium.com/max/900/1*7PohOL0gMH7pILqeEI6p5g.gif)