Building a game of Snake using an MQTT based controller

The complete source code for each step of this tutorial is available on Github.

The complete source code for each step of this tutorial is available on Github.

The complete source code for each step of this tutorial is available on Github.

Note: The Python version of this tutorial requires the installation of Curses library. However, this library will only work on Unix-based machines, not Windows.

Ably Realtime provides support for a number of protocols with its pub/sub system, one of which is MQTT. MQTT is an open ISO standard providing a lightweight messaging protocol for small sensors and mobile devices, optimized for high-latency or unreliable networks. In most cases we recommend use of the Ably client library SDKs where you can, due to their far richer feature sets such as presence, automatic encoding and decoding of data types, and much more. However, MQTT can be great for use with languages we don’t currently support, as well as when you have stringent bandwidth restrictions or wish to avoid vendor lock-in.

In this tutorial, you’ll be using the MQTT protocol with Ably to connect a controller made in Node.js with a game of snake to be played in a browser. You’ll be using our JavaScript client library SDK for the webpage. We’re using MQTT with Node.js in this tutorial for demo purposes, but if you were to actually to make something like this we’d suggest using our JavaScript client library SDK instead of MQTT.

In this tutorial, you’ll be using the MQTT protocol with Ably to connect a controller made in Go with a game of snake to be played in a browser. You’ll be using our JavaScript client library SDK in this tutorial for the webpage.

In this tutorial, you’ll be using the MQTT protocol with Ably to connect a controller made in Python with a game of snake to be played in a browser. You’ll be using our JavaScript client library SDK in this tutorial for the webpage.

You’ll be using a keyboard in this tutorial as input for the controller, but in actuality what device or language you use as the controller doesn’t matter so long as it supports MQTT.

Step 1 – Create your Ably app and API key

To follow this tutorial, you will need an Ably account. Sign up for a free account if you don’t already have one.

Access to the Ably global messaging platform requires an API key for authentication. API keys exist within the context of an Ably application and each application can have multiple API keys so that you can assign different capabilities and manage access to channels and queues.

You can either create a new application for this tutorial, or use an existing one.

To create a new application and generate an API key:

  1. Log in to your Ably account dashboard
  2. Click the “Create New App” button
  3. Give it a name and click “Create app”
  4. Copy your private API key and store it somewhere. You will need it for this tutorial.

To use an existing application and API key:

  1. Select an application from “Your apps” in the dashboard
  2. In the API keys tab, choose an API key to use for this tutorial. The default “Root” API key has full access to capabilities and channels.
  3. Copy the Root API key and store it somewhere. You will need it for this tutorial.

    Copy API Key screenshot

Step 2 – Set up your device

Now that you have an Ably account and have your API Key, you can work on setting up your controller. Firstly, you’ll need to create a folder and enter it. To do this type the following into your command line:

mkdir mqtt-snake
cd mqtt-snake

You’ll also need the MQTT.js and keypress NPM modules for the controller, so type the following into your command line to get them:

npm install mqtt --save
npm install keypress —-save

You’ll now see all the required files inside the node_modules folder that has been created within the mqtt-snake folder.

MQTT.js is required for you to use MQTT in JavaScript, and keypress allows you to easily detect keys being pressed on the keyboard.

See this step in Github

You’ll also need to make sure you have Go properly set up on your device, and have also added a GOPATH.

Once you have Go set up, you’ll need to install the Eclipse Paho MQTT Go client library, in addition to the termbox library to read inputs from the keyboard. Type the following into the command line:

go get github.com/eclipse/paho.mqtt.golang
go get -u github.com/nsf/termbox-go

You’ll now have access to these libraries when using Go.

You’ll also need the paho-mqtt library for the controller to use MQTT, so type the following into your command line to get it:

pip install paho-mqtt

Step 3 – Add your libraries

With your NPM modules ready, it’s time to create a file to contain your controller code. Create a file called controller.js inside your mqtt-snake folder. Inside this file, instantiate the MQTT.js and keypress libraries:

const mqtt = require('mqtt');
const keypress = require('keypress');

See this step in Github

With your libraries installed, it’s time to create a file to contain your controller code. Create a file called controller.go inside your mqtt-snake folder. Inside this file, reference the libraries we’ll need:

package main

import (
  'fmt'
  MQTT 'github.com/eclipse/paho.mqtt.golang'
  termbox 'github.com/nsf/termbox-go"
)

See this step in Github

With your libraries ready, it’s time to create a file to contain your controller code. Create a file called controller.py inside your mqtt-snake folder. Inside this file, instantiate the paho-mqtt and curses libraries:

import paho.mqtt.client as mqtt
import curses

See this step in Github

Step 4 – Connect to Ably through MQTT

With the libraries now available, it’s time to set up MQTT. When using MQTT with Ably, there are a few requirements with regards to your setup. You’ll need to connect to ‘mqtt.ably.io’ on port 8883, which requires the use of SSL/TLS in your connection. If you have a device which cannot support SSL, you’ll need to connect via port 1883 instead, but this will come with a number of restrictions.

You will need to set the “keep alive” time value to between 15 and 60 seconds, where 60 seconds will maximize the battery life, and 15 seconds will maximize responsiveness to network issues. For this tutorial you’ll be setting it to 15.

For authentication, you’ll need to either use an API Key, or a token. Our recommendation for untrusted devices it to make use of tokens, but for the simplicity of this tutorial you’ll be using the API key you obtained in step 1. For this, you’ll need to provide the first half of the API Key (before the colon) as the username, and the second half of the API Key (after the colon) as the password. For example, if your API key is A12B3C.4D:5E6F7G8H, your username will be A12B3C.4D and your password will be 5E6F7G8H.

Add the following code below your current code in controller.js, replacing ‘USERNAME_PART_OF_API_KEY’ and ‘PASSWORD_PART_OF_API_KEY’ with the appropriate parts of your own API Key:

var options = { keepAlive: 15,
  username: 'USERNAME_PART_OF_API_KEY',
  password: 'PASSWORD_PART_OF_API_KEY',
  port: 8883
};

var client = mqtt.connect('mqtts:mqtt.ably.io', options);

client.on('connect', function () {
  console.log('connected!');
});

client.on('error', function(err){
  console.error(err);
  client.end();
});

With the above code, you set options as described above, and then attempt to connect to Ably’s MQTT endpoint. You specify mqtts as part of the host address to specify that the connection attempt should be made with MQTT using SSL/TLS.

Now, try out the code by typing node controller.js in the command line and you should see in the console that the client successfully connects through MQTT.


Connected through MQTT!

If you have any issues with this, please check out the Ably MQTT usage notes.

See this step in Github

Add the following code just after your imports in controller.go, replacing ‘USERNAME_PART_OF_API_KEY’ and ‘PASSWORD_PART_OF_API_KEY’ with the appropriate parts of your own API Key:

func main() {
  options := MQTT.NewClientOptions();
  options.AddBroker('ssl://mqtt.ably.io:8883')
  options.SetKeepAlive(15)
  options.SetUsername('USERNAME_PART_OF_API_KEY')
  options.SetPassword('PASSWORD_PART_OF_API_KEY')

  client := MQTT.NewClient(options)
  if token := client.Connect(); token.Wait() && token.Error() != nil {
    panic(token.Error())
  } else {
    fmt.Println('Connected!')
  }
}

With the above code, you set options as described above, and then attempt to connect to Ably’s MQTT endpoint. You specify ssl as part of the broker address to specify that the connection attempt should be made with MQTT using SSL/TLS.

See this step in Github

Add the following code just after your imports in controller.py, replacing ‘USERNAME_PART_OF_API_KEY’ and ‘PASSWORD_PART_OF_API_KEY’ with the appropriate parts of your own API Key:

def on_connect(client, userdata, flags, rc):
  print('Connected')

def on_disconnect(client, userdata, rc):
  print('Disconnected')
  client.loop_stop()

client = mqtt.Client()
client.username_pw_set('USERNAME_PART_OF_API_KEY', 'PASSWORD_PART_OF_API_KEY')
client.tls_set()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.loop_start()
client.connect('mqtt.ably.io', port=8883, keepalive=15)

With the above code, you set up the client as described above, and then attempt to connect to Ably’s MQTT endpoint with connect. We also need to specify loop_start to create a background thread for communication with MQTT to occur. We stop this thread in the on_disconnect function with loop_stop.

Now, try out the code by typing python ./controller.py in the command line and you should see in the console that the client successfully connects through MQTT.

See this step in Github

Step 5 – Detect key presses

Now it’s time for you to detect key presses on the keyboard. For this tutorial, you’ll need to know when the arrow keys or space key are pressed, and then communicate which key was pressed through MQTT. You’ll be detecting the key presses with the keypress NPM module. Add the below code after instancing the library in the controller.js file:

keypress(process.stdin);

process.stdin.setRawMode(true);

process.stdin.on('keypress', function (ch, key) {
  if (key) {
    console.log('Key clicked: ' +  key.name);
    if (key.name == 'escape') {
      process.stdin.pause();
    }
  }
});

There are three main things occurring in this code. Firstly, keypress(process.stdin), which will simply cause our process.stdin to emit keypress events.

Secondly, process.stdin.setRawMode(true), which ensures your keyboard input is available character-by-character with no modifiers. For example, when typing ctrl-c, process.stdin will output the following as a keypress event:

{ name: 'c',
  ctrl: true,
  meta: false,
  shift: false,
  sequence: '\u0003'
}

Finally, process.stdin.on() is simply a listener for the keypress event. You use it in this case to print to the console whenever a key is pressed. You can try this out for yourself by running the following line of code in the terminal:

node controller.js

If all is well, you should see a message reflecting the key you pressed, whenever you click a key. To quit this, simply click the esc key.

See this step in Github

Now it’s time for you to detect key presses on the keyboard. For this tutorial, you’ll need to know when the arrow keys or space key is pressed, and then communicate which key was pressed through MQTT. You’ll be detecting the key presses with the termbox library. Add the following code inside the main function you made, just below the MQTT code:

err := termbox.Init()
if err != nil {
  panic(err)
}
defer termbox.Close()
fmt.Println('Press the ESC button to quit')

for {
  switch termbox.PollEvent().Key {
    case termbox.KeyEsc:
      client.Disconnect(0)
      return
    case termbox.KeySpace:
      fmt.Println('Space')
    case termbox.KeyArrowUp:
      fmt.Println('Up')
    case termbox.KeyArrowDown:
      fmt.Println('Down')
    case termbox.KeyArrowLeft:
      fmt.Println('Left')
    case termbox.KeyArrowRight:
      fmt.Println('Right')
  }
}

All that happens here is we initialise termbox, and then in the for loop listen for key events, printing out which keys were pressed.

You can now try out this code by running the following line of code in the terminal:

go run controller.go

If all is well, you should see a message reflecting the key you pressed, whenever you click a key. To quit this, simply click the esc key.

See this step in Github

Now it’s time for you to detect key presses on the keyboard. For this tutorial, you’ll need to know when the arrow keys, space key or ‘c’ key is pressed, and then communicate which key was pressed through MQTT. You’ll be detecting the key presses with the curses library. Add the following code inside the main function you made, just below the MQTT code:

def main(win):
  key=''
  win.clear()
  while 1:
    try:
      key = win.getkey()
      win.clear()
      if key == 'c':
        client.disconnect()
        break
      elif key == 'KEY_LEFT':
        win.addstr('Left clicked!')
        win.addstr(str(counter))
      elif key == 'KEY_RIGHT':
        win.addstr('Right clicked!')
      elif key == 'KEY_UP':
        win.addstr('Up clicked!')
      elif key == 'KEY_DOWN':
        win.addstr('Down clicked!')
      elif key == ' ':
        win.addstr('Space clicked!')
    except Exception as e:
      pass

curses.wrapper(main)

All that happens here is you wrap the main function in a curses wrapper, and then in the main function loop, listening for key events, printing out which keys were pressed.

You can now try out this code by running the following line of code in the terminal:

python ./controller.py

If all is well, you should see a message reflecting the key you pressed, whenever you click a key. To quit this, simply click the ‘c’ key.

See this step in Github

Step 6 – Send key presses through MQTT

Now that you have established a connection through MQTT to Ably and detected the key presses, you can send some data through the connection. You need to firstly create a function to easily publish messages for the key presses you’re interested in. Add the following function to the bottom of controller.js:

function publishMessage(channel, message) {
  client.publish(channel, message, { qos: 0 }, function(err) {
    if(err) {
      console.log(err);
    }
  });
}

This function simply takes the message you wish to publish, and publishes it through MQTT using client.publish into the channel specified. The setting ‘qos’ (Quality of Service) represents whether messages are guaranteed to be delivered ‘at most once’ (0), ‘at least once’ (1), or ‘exactly once’ (2). Here you’ll use ‘at most once’, as in the case of a disconnect you wouldn’t want outdated information to be delivered to the game.

Now that you have a method to publish through MQTT, you need to use it to send our key presses. Replace the contents of the function associated with process.stdin.on('keypress') with the following:

if (key) {
  if (key.name == 'escape') {
    process.stdin.pause();
    client.end();
  } else if(key.name == 'left') {
    publishMessage('input', 'left');
  } else if(key.name == 'right') {
    publishMessage('input', 'right');
  } else if(key.name == 'up') {
    publishMessage('input', 'up');
  } else if(key.name == 'down') {
    publishMessage('input', 'down');
  } else if(key.name == 'space') {
    publishMessage('input', 'startstop');
  }
}

This will use the publishMessage function you created to send the key presses you’re interested in. If a user clicks the esc key, the stream will be paused and the client’s connection will be closed so that the program can stop. Otherwise, you’re sending the key pressed to the channel input.

See this step in Github

Now that you have established a connection through MQTT to Ably and can detect key presses, you can send some data through the connection. Replace the contents of your for loop with the following:

switch termbox.PollEvent().Key {
  case termbox.KeyEsc:
    client.Disconnect(0)
    return
  case termbox.KeySpace:
    client.Publish('input', 0, false, 'startstop')
  case termbox.KeyArrowUp:
    client.Publish('input', 0, false, 'up')
  case termbox.KeyArrowDown:
    client.Publish('input', 0, false, 'down')
  case termbox.KeyArrowLeft:
    client.Publish('input', 0, false, 'left')
  case termbox.KeyArrowRight:
    client.Publish('input', 0, false, 'right')
}

What client.Publish(topic string, qos byte, retained bool, payload interface{}) does is publish a message through the previously established MQTT connection. In the case of termbox.KeySpace, we’ll be publishing onto the channel input the message startstop with ‘qos’ (Quality of Service) 0, and we have also specified that we do not wish to retain the message by stating false. QoS represents whether messages are guaranteed to be delivered ‘at most once’ (0), ‘at least once’ (1), or ‘exactly once’ (2). Here you’ll use ‘at most once’, as in the case of a disconnect you wouldn’t want outdated information to be delivered to the game.

See this step in Github

Now that you have established a connection through MQTT to Ably and can detect key presses, you can send some data through the connection. Replace the contents of your while loop with the following:

try:
  key = win.getkey()
  win.clear()
  if key == 'c':
    client.disconnect()
    break
  elif key == 'KEY_LEFT':
    client.publish('input', 'left', qos=0)
  elif key == 'KEY_RIGHT':
    client.publish('input', 'right', qos=0)
  elif key == 'KEY_UP':
    client.publish('input', 'up', qos=0)
  elif key == 'KEY_DOWN':
    client.publish('input', 'down', qos=0)
  elif key == ' ':
    client.publish('input', 'startstop', qos=0)
except Exception as e:
  pass

What client.publish(topic, payload, qos, retain) does is publish a message through the previously established MQTT connection. In the case of key == "KEY_LEFT, we’ll be publishing onto the channel input the message left with ‘qos’ (Quality of Service) 0. QoS represents whether messages are guaranteed to be delivered ‘at most once’ (0), ‘at least once’ (1), or ‘exactly once’ (2). Here you’ll use ‘at most once’, as in the case of a disconnect you wouldn’t want outdated information to be delivered to the game.

See this step in Github

Step 7 – Receiving inputs from Ably

Now that you’ve set up your controller, it’s time to create something to run the game of Snake, and receive the data from Ably that you sent from the controller. You’ll be using the JavaScript client library SDK for this part. This is possible due to the fact Ably is Protocol Agnostic, allowing protocols to be used interchangeably.

Firstly create a file called snake.html, and place the following code inside, replacing REPLACE_WITH_YOUR_API_KEY with your API Key:

<!DOCTYPE HTML>
<html>
<head>
  <meta charset='UTF-8'>
  <title>Snake with Ably</title>
  <script src='http://cdn.ably.com/lib/ably.min-1.js'></script>
</head>
<body>
  <h1 id='heading'>Ably Realtime Snake</h1>
  <script type='text/javascript'>
    var ably = new Ably.Realtime('REPLACE_WITH_YOUR_API_KEY');
    var decoder = new TextDecoder();
    var channel = ably.channels.get('input');
    channel.subscribe(function(message) {
      var command = decoder.decode(message.data);
      if(command == 'left') {
        console.log('Left!')
      } else if(command == 'up') {
        console.log('Up!')
      } else if(command == 'right') {
        console.log('Right!')
      } else if(command == 'down') {
        console.log('Down!')
      } else if(command == 'startstop') {
        console.log('Pause or Play!')
      }
    });
  </script>
</body>
</html>

This will simply subscribe to the input channel you’re publishing to from the controller, and print it into the console. The TextDecoder is required due to MQTT being a binary protocol with no encoding. This means the payload you receive is just raw bits that need to be interpreted. In this case we’re interpreting it as text with the TextDecoder.

Open up snake.html in a browser, open up the browser’s console, and run your controller by typing node controller.js into your command line. Now when you press the arrow keys from your controller, you should see messages being received by the browser in the console.


Sending messages through Ably with MQTT

See this step in Github

Open up snake.html in a browser, open up the browser’s console, and run your controller by typing go run controller.go into your command line. Now when you press the arrow keys from your controller, you should see messages being received by the browser in the console.


Sending messages through Ably with MQTT

See this step in Github

Open up snake.html in a browser, open up the browser’s console, and run your controller by typing python ./controller.py into your command line. Now when you press the arrow keys from your controller, you should see messages being received by the browser in the console.


Sending messages through Ably with MQTT

See this step in Github

Step 8 – Play Snake!

With the setup complete, you can now add in the code for the actual snake game. Replace the contents of snake.html with our Snake HTML codeour Snake HTML codeour Snake HTML code code. Replace REPLACE_WITH_YOUR_API_KEY with your API Key. When you reload the snake.html page you should see your basic Snake game, based off of an external source code. Run the controller as before, and you can now play! Use the arrow keys to move, press the space key to pause/play, and try to grow the Snake as large as you can.


Playing Snake!

The complete source code for each step of this tutorial is available on Github.

The complete source code for each step of this tutorial is available on Github.

The complete source code for each step of this tutorial is available on Github.

We recommend that you clone the repo locally:

git clone https://github.com/ably/tutorials.git

Checkout to the tutorial branch:

git checkout mqtt-snake-js
git checkout mqtt-snake-go
git checkout mqtt-snake-python

And then run the demo locally by adding your Ably API key to snake.js and your controller.

Next steps

1. Learn more about our support of other protocols
2. Learn more about Ably features by going through our other Ably tutorials
3. Gain a good technical overview of how the Ably realtime platform works
4. Get in touch if you need help

Created something cool based on this tutorial? Tweet it to us!