Skip to content

Tutorial: Creating a TicTacToe Bot

Mike Leonard edited this page May 22, 2016 · 9 revisions

Introduction

As an introduction to Merknera this tutorial will guide you through creating a bot to play Tic-Tac-Toe in JavaScript using node.js. The aim of this tutorial is to give you an introduction into creating a bot, registering and having it play its first games - this should give you a good understanding of how JSON-RPC works in the context of Merknera and how to test your bot using ngrok and postman.

A simple an non-effective implementation will be implemented and it will be left as an exercise of the reader to improve upon this.

Prerequisites

You will need to have the following software download and installed before continuing with this tutorial:

Setting Up

  1. Create a new directory and navigate to it at the command line.
  2. Run npm init and following the on-screen instructions.
  3. Run npm install jayson --save to install and add the dependency to our package.json file created for us in step 2.

Server Implementation

When orchestrating a game Merknera acts as a client and makes calls to the bots participating in the game and the bots act as a server. We will start by implementing the 4 server methods required by Tic-Tac-Toe to play a game: Status.Ping, TicTacToe.NextMove, TicTacToe.Complete & TicTacToe.Error.

Status.Ping is the easiest so we will start with that.

Status.Ping

Status.Ping is called whenever the Merknera server starts to find out which bots are online and it also calls Status.Ping prior to any move being played, this enables Merknera to ensure the bot is still online and to wake up any bots that might be hosted on a free service such as Heroku that goes to sleep after periods of inactivity. Status.Ping is the same for all game types so its code can be reused. Full documentation can be found here

We need to be able to accept a method of Status.Ping with no parameters and return with a result of "ping": "OK".

  1. Create a file called `index.js
  2. Add the following contents.
// Import the jayson module into a variable names "jayson"
var jayson = require('jayson');

// Create a jayson server.
var server = jayson.server({
  // Register the Status.Ping method
  "Status.Ping": function(callback) {
    // Write a log message so we can see that this RPC method was called.
    console.log("Status.Ping");
    // Invoke the callback that will respond to the client.
    callback(
      null,
      { ping: "OK" } // Return an object with ping: "OK" that will be used in the response.
    );
  },
}, {
   // don't collect params in a single argument
   // More information can be found here: https://github.com/tedeh/jayson#named-parameters
  collect: false
});

// Start the server and listen on port 3000.
server.http().listen(3000);

This code registers the Status.Ping method and returns ping: "OK" when called.

We can now test this method using Postman.

  1. Launch your server by typing node index.js at the command line.
  2. Launch Postman an create a new request.
  3. Click on the "GET" drop down and change it to "POST".
  4. In the request URL enter http://localhost:3000.
  5. Click on the "Headers" tab and add a header with a "key" of Content-Type and a "value" of application/json.
  6. Click on the "Body" tab and select the "raw" radio button option.
  7. Paste the following into the body in Postman.
{
  "jsonrpc": "2.0",
  "method": "Status.Ping",
  "id": 1
}
  1. Click "Send" and you should get a result like the following. You can also check the command line and you should see a message written of Status.Ping.
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "ping": "OK"
  }
}

If you followed the above steps and got the above result then everything worked and you can move on to the next RPC call.

TicTacToe.NextMove

This method is responsible for taking in the current game state and returning your bots next move. Full documentation can be found here.

  1. Add the following code to jayson.server after "Status.Ping"
var server = jayson.server({
  ...
  "TicTacToe.NextMove": function(gameid, mark, gamestate, callback) {
    // Write a log to the console so that we can see that this RPC method was
    // called and what the parameters.
    console.log("TicTacToe.NextMove: gameid: " + gameid + ", mark: " + mark + ", gamestate: " + gamestate);
    // Invoke the callback that will respond to the client.
    callback(
      null,
      { position: 0 } // For now hard code position one to play in.
    );
  }
  ...
}

When adding this, don't forget to add a comma after the closing } of "Status.Ping".

This code registers the TicTacToe.NextMove method. This method accepts the current game ID, the mark (either "X" or "O") that you are playing and the current game state as an array of each position on the board. See the GameBoard documentation for more information.

We can now test this method using Postman - this is mostly the same as when testing Status.Ping.

  1. Launch your server by typing node index.js at the command line - kill it first if it is already running.
  2. Launch Postman an create a new request.
  3. Click on the "GET" drop down and change it to "POST".
  4. In the request URL enter http://localhost:3000.
  5. Click on the "Headers" tab and add a header with a "key" of Content-Type and a "value" of application/json.
  6. Click on the "Body" tab and select the "raw" radio button option.
  7. Paste the following into the body in Postman.
{
  "jsonrpc": "2.0",
  "method": "TicTacToe.NextMove",
  "params": {
    "gameid": 78,
    "mark": "X",
    "gamestate": ["O", "X", "O", "", "O", "", "", "X", ""]
  },
  "id": 1
}
  1. Click "Send" and you should get a result like the following. You can also check the command line and you should see a message written of TicTacToe.NextMove: gameid: 78, mark: X, gamestate: O,X,O,,O,,,X,.
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "position": 0
  }
}

Enhancing the game play logic.

Currently the bot is hard-coded to always respond in position 0. This will cause an error with Merknera if player has already played this move.

This tutorial will now enhance this logic to a point where it does not error but it will achieve this with a crude method and you should aim to enhance this once you have completed the tutorial. The method implemented in this tutorial will simply find the first available space and play that space.

  1. Change your TicTacToe.NextMove function to the following:
  "TicTacToe.NextMove": function(gameid, mark, gamestate, callback) {
    // Write a log to the console so that we can see that this RPC method was
    // called and what the parameters.
    console.log("TicTacToe.NextMove: gameid: " + gameid + ", mark: " + mark + ", gamestate: " + gamestate);

    // Create a variable to store the position the bot will play.
    var position;
    // Loop through each position in the game state.
    for (var i = 0; i < gamestate.length; i++) {
      // If the position in the game state is an empty string then it has not
      // yet been played so take this as out position.
      if (gamestate[i] === "") {
        // Set the position to the empty space we've found and break out of the loop.
        position = i;
        break;
      }
    }
    // Invoke the callback that will respond to the client.
    callback(
      null,
      { position: position } // Return the position we just calculated.
    );
  }

Run the same test as we did before in Postman for TicTacToe.NextMove. This time, however, the position should be the first available space which is 3. Ensure to restart your server with node index.js if it is still running to load your latest modifications. The response should be as follows.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "position": 3
  }
}

TicTacToe.Complete

This method is called when a player has won the game or a draw has been found. You will be passed the game ID of the completed game, whether or not you were the winner, the final gamestate and the mark you were played (either "X" or "O"). This is really just a notification so the response does not matter too much. We will just log a message to the command line when a game is completed. Full documentation can be found here.

  1. Add the following code to jayson.server after "TicTacToe.NextMove".
  "TicTacToe.Complete": function(gameid, mark, winner, gamestate, callback) {
    // Write a log to the console so that we can see that this RPC method was
    // called and what the parameters.
    console.log("TicTacToe.Complete: gameid: " + gameid + ", mark: " + mark + ", winner: " + winner + ", gamestate: " + gamestate);

    // Invoke the callback that will respond to the client.
    callback(
      null,
      { status: "OK" } // Return status: "OK" to indicate we received the message.
    );
  }

When adding this, don't forget to add a comma after the closing } of "TicTacToe.NextMove".

You can now test this in the same way as Status.Ping and TicTacToe.NextMove with a request body as below. Ensure to restart your server with node index.js if it is still running to load your latest modifications.

{
   "jsonrpc": "2.0",
   "method": "TicTacToe.Complete",
   "params": {
       "gameid": 21,
       "winner": true,
       "mark": "X",
       "gamestate": ["O", "X", "O", "", "O", "X", "O", "X", ""]
   },
   "id": 1
}

And expect a response of the following and a message at the command line of TicTacToe.Complete: gameid: 21, mark: X, winner: true, gamestate: O,X,O,,O,X,O,X,.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "status": "OK"
  }
}

TicTacToe.Error

When your bot causes an error (for example, playing a move that has already been played) Merknera will mark you bot as error and games will cease to be played until you bot is re-registered (we'll get that that soon). Merknera will also call TicTacToe.Error with an error message so that you can diagnose any issues. For this bot we will just log the error to the command line and it will work very similar to TicTacToe.Complete. Full documentation can be found here.

  1. Add the following code to jayson.server after "TicTacToe.Complete".
  "TicTacToe.Error": function(gameid, message, errorcode, gamestate, callback) {
    // Write a log to the console so that we can see that this RPC method was
    // called and what the parameters.
    console.log("TicTacToe.Error: gameid: " + gameid + ", errorcode: " + errorcode + ", message: " + message);

    // Invoke the callback that will respond to the client.
    callback(
      null,
      { status: "OK" } // Return status: "OK" to indicate we received the message.
    );
  }  

When adding this, don't forget to add a comma after the closing } of "TicTacToe.Complete".

You can now test this in the same way as TicTacToe.Complete with a request body as below. Ensure to restart your server with node index.js if it is still running to load your latest modifications.

{
   "jsonrpc": "2.0",
   "method": "TicTacToe.Error",
   "params": {
       "gameid": 21,
       "message": "You played an invalid move, 'X' was already played here.",
       "errorcode": 1548
   },
   "id": 1
}

And expect a response of the following and a message at the command line of TicTacToe.Error: gameid: 21, errorcode: 1548, message: You played an invalid move, 'X' was already played here..

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "status": "OK"
  }
}

Server Implementation Complete

That's it! The implementation of the server is now complete no we want to register our bot with Merknera and play some games.

Client Implementation

Your bot acts as a client and Merknera as the server when registering your bot to play games. You do this by making a JSON-RPC call to the RegistrationService.Register method on Merknera.

Once we have done this we cant then go on to registering with Merknera and playing our first games.

RegistrationService.Register

Bot registration should be called every time your bot starts up, this lets Merknera know that you're online and also allows Merknera to update any information relating to your bot such as its URL and its version. More information and full ARPC documentation for the Registration Service can be found here.

The jayson library we are using to handle JSON-RPC also make client requests simple.

  1. Add the following code after the var jayson = require('jayson'); line at the top of the file and before var server = jayson.server({
// Create the HTTPS client object to Merknera.
var client = jayson.client.https({
  hostname: 'api.merknera.com',
  path: '/rpc',
  method: 'POST'
});

// Configure the registrations parameters.
var registrationParams = {
  "token": "TODO", // API token as generated from www.merknera.com
  "botname": "TODO", // The name you want to give to your bot. This must be unique.
  "botversion": "0.0.1",
  "game": "TICTACTOE",
  "rpcendpoint": "TODO", // The URL that your bot is running on. We'll use ngrok for the tutorial.
  "programminglanguage": "JavaScript",
  "description": "My first TicTacToe bot created from the tutorial."
};

// Make the client request to the registration service RPC endpoint and log
// the response message.
client.request('RegistrationService.Register', registrationParams, function(err, response) {
  if(err) throw err;
  console.log(response.result.message);
});
  1. Now go to a new command line window and type ngrok http 3000. This will run ngrok and create a secure tunnel into your PC on port 3000. ngrok will display various URLs are the command line. Use the http forwarding one - it will look like this: http://e25620cb.ngrok.io
  2. In index.js edit the registrationParams object to complete the TODOs. Fill out therpcendpoint with the forarding URL from ngrok, give your bot a name and get a token from merknera by following the Obtaning a Token guide.
  3. Run your bot with node index.js you bot should register and begin playing games!

Next Steps

You have now successfully created your first bot! However, there are still some improvement you can make that will be left as an exercise to the reader.

  • You can modify the logic used to play the game in the TicTacToe.NextMove RPC handler to make your bot more competitive.
  • Currently this bot only works if your PC is online and ngrok is running. You might want to look at hosting your bot on a free service such as Heroku.
Clone this wiki locally