beanz Magazine

How to Create a Chat Bot

Paul Seal on codeshare.co.uk

This tutorial shows how to create a chat bot that plays hangman.

What is this post about?

This post will show you step by step how to create a Chat Bot using Node.js and the Microsoft Bot Framework. Don’t be put off if you’ve never used node.js before. It’s really easy to use. You don’t need to be an expert to write node.js programs. Anyone can follow along with this tutorial.

Hang on what is Node.js?

Node.js was created to allow you to write server side code using JavaScript. (Previously JavaScript would just run in your browser). It runs cross platform (meaning Windows, Mac and Linux), and it allows you to build websites and apps very easily. There are loads of tutorials on the internet which explain what Node is, but I found this one by Brad Traversy to be the best one for me. Node.js tutorial for absolute beginners.

What is the Microsoft Bot Framework?

The Microsoft Bot Framework is a set of libraries which were created to make building a bot really easy. It also allows you to write the code once and publish it in several places like Skype, Slack and Facebook Messenger. The bot framework is available in .NET and Node.js.?

Try out the finished Hangman Chat Bot now

Start by saying hello, or anything else, then type film or song to start the game.

Before we get started

You will need to have Node.js installed. To check if it is installed you first need to open a command window.

Some tips if you’re on Windows 10

You can open a command by doing one of the following:

  • Open a folder, click in the address bar and type cmd then press enter.
  • Or press the windows button on your keyboard and then type cmd and press enter.
  • Or open a folder and right click in the white space whilst holding shift, you can then click on open command window here

With the command window open, to check if node is installed, type in

node -v

If it’s installed it will tell you the version number,

What if I haven’t got node installed yet?

You can get it by going to nodejs.org click on the big green button on the left which says v6.10.2 LTS. The version number might be different if you are reading this a while after the date of this post, but main thing to note is LTS. This is the most stable build for you to use.

When you download the installer, you can leave the tick boxes and options as they are, just press next through it and finish. If you leave all of the defaults it will also install npm. I will explain what npm is later.

When it has finished repeat the check from above to see if node is installed.

Want to see me implement the code?

Let’s create our project

Create a folder, open command window and navigate to the folder.

With the command line navigated to the correct folder enter

npm init

This is a sort of install wizard, it will ask you some questions so you can create a project.json file.

Answer the questions, if it is in brackets you can press enter it will use the value in the brackets.

  • So enter a name, in lowercase and no spaces, press enter.
  • Press enter for the version number.
  • Enter a description, something like “my first chat bot”.
  • Entry point, this is the main file which runs your code. I like to name mine app.js and I will be referring to it as app.js in this tutorial, so type app.js and press enter
  • Ignore the test command, by pressing enter, same with git repository and keywords.
  • You can enter you name as the author.
  • Leave the license as ISC, press enter

It will show you what the project.json file will be like, press enter to say yes this is correct.

When this has finished you will see a file called project.json has been created in the folder. If you open it, it should look like this.

{
  "name": "chatbottutorial",
  "version": "1.0.0",
  "description": "My first chat bot",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Paul Seal",
  "license": "ISC"
}

Let’s install some important packages

In order for our bot to work, we need to install 2 packages.

The quickest way to do it is to enter the following commands one after the other in the command line:

npm install --save botbuilder

and then when that has finished

npm install --save restify

With those installed we can start writing some code.

Let’s write some code

Create a file in the folder called app.js

Now open the file or the folder in your favourite text editor.

I like to use Visual Studio Code. It’s a light weight text editor which is rich with features. It also runs on Windows Mac and Linux. If you want to use it as well, you can download it from https://code.visualstudio.com/

With the app.js file open, we need to start adding some of the code to get the bot working. I got this code from the documentation site for the Microsoft Bot Framework.

If you want to skip to the final code, use this link

var builder = require('botbuilder');
var restify = require('restify');

//=========================================================
// Bot Setup
//=========================================================

// Setup Restify Server
var server = restify.createServer();

server.listen(process.env.port || process.env.PORT || 3978, function () {
   console.log('%s listening to %s', server.name, server.url); 
}); 

// Create chat bot
var connector = new builder.ChatConnector({
    appId: process.env.MY_APP_ID,
    appPassword: process.env.MY_APP_PASSWORD
});

var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());

//=========================================================
// Bots Dialogs
//=========================================================

bot.dialog('/', function (session) {
    session.send("Hello World");
});

This code uses the 2 modules we installed, botbuilder and restify.

It sets up a restify server which is used for sending and receiving messages.

It is listening to port 3978 on your machine.

When it is live it will need to use the App Id and App Password, but when we are testing in development we don’t need one.

It is set up to receive messages at the address /api/messages

Let’s test the code

In the command window, navigate to the folder you are working on and type

node app.js

Then press enter.

This will start the application, ready to test.

If you get an error saying ‘Cannot find module botbuilder’ or ‘restify’ you may have forgotten to install the modules. Install them as per the instructions above. Then run the above command again.

Whilst developing our bot, if we want to test it, we need to use an emulator. You can download the Bot Framework Emulator from Microsoft.

Once it has downloaded. Double click on the installer. When it has finished installing it will open automatically.

In the address bar at the top, choose the first address; http://localhost:3978/api/messages and click on Connect.

Now you should be able to type something in the message bar at the bottom and press enter.

You should see a response from the bot saying ‘Hello World’

Making the Hangman Game

First we need to with something for the user to guess. We can start with the words ‘Hello World’. Then we need to replace the letters with ‘?’ characters and display it to the user.

The code at the top of the app.js file doesn’t change at all now, so instead of showing all the code every time, We are just going to replace the code from under the Bots Dialogs comments

bot.dialog('/', function (session) {
    var wordsToGuess = "Hello World"
    var hiddenWords = wordsToGuess.replace(/[A-Z]/ig,'?')
    session.send(hiddenWords);
});

Now in the command window press Ctrl + C. This will cancel the previous running app.

Then type in

node app.js

or just

node app

and press enter

In the bot emulator press the refresh icon at the top next to the address bar.

Say something to the bot and press enter.

You should see a reply like this

????? ?????

That’s the basic idea.

User interaction

We now need to start caring about what the user writes, so to start with we are going to set up some code to welcome the user and to check when they want to play a new game.

var intents = new builder.IntentDialog();
bot.dialog('/', intents);

intents.matches(/^new/i, [
    function (session) {
        session.userData.word = "Hello World";
        session.userData.masked = session.userData.word.replace(/[A-Z]/ig,'?')
        session.send('Guess the hidden words');
        session.send(session.userData.masked);
    }
]);

intents.onDefault([
    function (session) {
        session.beginDialog('/welcome');
    }
]);

bot.dialog('/welcome', [
    function (session) {
        session.send("Hi I'm Hangman Bot. Type 'new' to start a new game.");
        session.endDialog();
    }
]);

This code is slightly different, it introduces intents. Intents are where you work out what the user wants. So in this code, it checks if what the user types starts with ‘new’,

intents.matches(/^new/i, [

and if not it falls back to the default.

intents.onDefault([

There is also a welcome dialog set up at the bottom. If what the user enters doesn’t start with ‘new’, then it will go to the default intent. In the default intent it sends them to the welcome dialog.

Also notice we are storing the words by using session.userData, we can store whatever values we want like this:

session.userData.word = “Hello World”;

Try it out.

Remember to stop the code using Ctrl + C and then type node app and press enter.

Refresh the emulator. Type anything in the message, it should say

Hi I’m Hangman Bot. Type ‘new’ to start a new game.

Then add the message ‘new’

It will reply with

Guess the hidden words

????? ?????

Letting the user guess the words

Now let’s add some more dialogs and logic to the code. We are going to allow the user to guess what the words are. If they are right, they are show the win dialog, if the are wrong, they lose a life and get to guess again. If they have no lives left it shows the gameover dialog.

var intents = new builder.IntentDialog();
bot.dialog('/', intents);

intents.matches(/^new/i, [
    function (session) {
        session.userData.lives = 5;
        session.userData.word = "Hello World";
        session.userData.masked = session.userData.word.replace(/[A-Z]/ig,'?')
        session.send('Guess the hidden words');
        session.beginDialog('/guess');
    }
]);

intents.onDefault([
    function (session) {
        session.beginDialog('/welcome');
    }
]);

bot.dialog('/welcome', [
    function (session) {
        session.send("Hi I'm Hangman Bot. Type 'new' to start a new game.");
        session.endDialog();
    }
]);

bot.dialog('/guess', [
    function (session) {
        session.send('You have ' + session.userData.lives + ' ' + (session.userData.lives == 1 ? 'life' : 'lives') + ' left');
        builder.Prompts.text(session, session.userData.masked);
    },
    function (session, results) {
        var nextDialog = '';
        if(results.response.toUpperCase() === session.userData.word.toUpperCase())
        {
            session.userData.masked = session.userData.word;
            nextDialog = '/win';
        }
        else
        {
            session.userData.lives--;
            nextDialog = session.userData.lives === 0 ? '/gameover' : '/guess';
        }
        session.beginDialog(nextDialog);        
    }
]);

bot.dialog('/win', [
    function (session) {
        session.send(session.userData.masked);
        session.send('Well done, you win!');
        session.endDialog();
    }
]);

bot.dialog('/gameover', [
    function (session) {
        session.send('Game Over! You lose!');
        session.send(session.userData.word);
        session.endDialog();
    }
]);

Have a look at the code for the guess dialog above, it introduces the waterfall concept. It asks the user for input in the first function, then it runs another function to act on the user’s response.

As it stands the user can only guess what the words are in full, not what the letters are. So if you type ‘Hello World’ you will win, but anything else will make you lose a life.

Run the code again to test it out. Remember to stop the code using Ctrl + C and then type node app and press enter.

Let’s let them guess letters too

We will move the logic out of the gues dialog, into a function called get next dialog, this works out if it was an accurate guess of letter or words and directs them to either guess, win or game over, as well as taking off lives when wrong.

We’ll also add another method called RevealLettersInMaskedWord() for revealing the correctly guessed letters from the masked word. This is a simple method, which loops through the characters in the word to guess, checks if the letter the user entered matches, and if it does it updates the character at the same position in the masked word. So that character will now show as the real letter rather than a question mark.

var intents = new builder.IntentDialog();
bot.dialog('/', intents);

intents.matches(/^new/i, [
    function (session) {
        session.userData.lives = 5;
        session.userData.word = "Hello World";
        session.userData.masked = session.userData.word.replace(/[A-Z]/ig,'?')
        session.send('Guess the hidden words');
        session.beginDialog('/guess');
    }
]);

intents.onDefault([
    function (session) {
        session.beginDialog('/welcome');
    }
]);

bot.dialog('/guess', [
    function (session) {
        session.send('You have ' + session.userData.lives + ' ' + (session.userData.lives == 1 ? 'life' : 'lives') + ' left');
        builder.Prompts.text(session, session.userData.masked);
    },
    function (session, results) {
        var nextDialog = GetNextDialog(session, results);
        session.beginDialog(nextDialog);        
    }
]);

bot.dialog('/win', [
    function (session) {
        session.send(session.userData.masked);
        session.send('Well done, you win!');
        session.endDialog();
    }
]);

bot.dialog('/welcome', [
    function (session) {
        session.send("Hi I'm Hangman Bot. Type 'new' to start a new game.");
        session.endDialog();
    }
]);

bot.dialog('/gameover', [
    function (session) {
        session.send('Game Over! You lose!');
        session.send(session.userData.word);
        session.endDialog();
    }
]);

function GetNextDialog(session, results){
    var nextDialog = '';
    if(results.response.length > 1)
    {
        if(results.response.toUpperCase() === session.userData.word.toUpperCase())
        {
            session.userData.masked = session.userData.word;
            nextDialog = '/win';
        }
        else
        {
            session.userData.lives--;
            nextDialog = session.userData.lives === 0 ? '/gameover' : '/guess';
        }
    }
    else
    {
        session.userData.letter = results.response;
        session.userData.masked = RevealLettersInMaskedWord(session, results);

        if(session.userData.masked == session.userData.word)
        {
            nextDialog = '/win';
        }
        else
        {
            nextDialog = session.userData.lives === 0 ? '/gameover' : '/guess';
        }
    }
    return nextDialog;
}

function RevealLettersInMaskedWord(session, results)
{
    var wordLength = session.userData.word.length;
    var maskedWord = "";
    var found = false;

    for(var i = 0; i < wordLength; i++)
    {
        var letter = session.userData.word[i];
        if(letter.toUpperCase() === results.response.toUpperCase())
        {
            maskedWord += letter;
            found = true;
        }
        else
        {
            maskedWord += session.userData.masked[i];
        }
    }

    if(found === false)
    {
        session.userData.lives--;
    }

    return maskedWord;
}

If you test out the code in the emulator, you will see it is almost finished now. The final thing to do is to add some words for you to guess.

The final working code, with films and songs to guess.

This code includes all of the setup code at the top too, so you can replace all of the code in the app.js file.

You will see I’ve added an array of films and an array of songs. The ‘new’ intent has gone now, and has been replace with 2 new ones, ‘film’ and ‘song’. If you enter ‘film’ or ‘song’ it picks one at random from the relevant array and start the game.

var builder = require('botbuilder');
var restify = require('restify');

//=========================================================
// Bot Setup
//=========================================================

// Setup Restify Server
var server = restify.createServer();

server.listen(process.env.port || process.env.PORT || 3978, function () {
   console.log('%s listening to %s', server.name, server.url); 
}); 

// Create chat bot
var connector = new builder.ChatConnector({
    appId: process.env.MY_APP_ID,
    appPassword: process.env.MY_APP_PASSWORD
});

var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());

//=========================================================
// Bots Dialogs
//=========================================================

var films = ["Star Wars The Force Awakens", "The Lion King", "The Truman Show", "Saving Private Ryan", "Rocky"];

var songs = ["I will always love you", "Cannonball", "How much is that doggie in the window", "It wasnt me", "Rockabye"];

var intents = new builder.IntentDialog();
bot.dialog('/', intents);

intents.matches(/^film/i, [
    function (session) {
        session.userData.lives = 5;
        session.userData.word = films[Math.floor(Math.random() * films.length)];
        session.userData.masked = session.userData.word.replace(/[A-Z]/ig,'?')
        session.send('Guess the film title');
        session.beginDialog('/guess');
    }
]);

intents.matches(/^song/i, [
    function (session) {
        session.userData.lives = 5;
        session.userData.word = songs[Math.floor(Math.random() * songs.length)];
        session.userData.masked = session.userData.word.replace(/[A-Z]/ig,'?')
        session.send('Guess the song title');
        session.beginDialog('/guess');
    }
]);

intents.onDefault([
    function (session) {
        session.beginDialog('/welcome');
    }
]);

bot.dialog('/guess', [
    function (session) {
        session.send('You have ' + session.userData.lives + ' ' + (session.userData.lives == 1 ? 'life' : 'lives') + ' left');
        builder.Prompts.text(session, session.userData.masked);
    },
    function (session, results) {
        var nextDialog = GetNextDialog(session, results);
        session.beginDialog(nextDialog);        
    }
]);

bot.dialog('/win', [
    function (session) {
        session.send(session.userData.masked);
        session.send('Well done, you win!');
        session.endDialog();
    }
]);

bot.dialog('/welcome', [
    function (session) {
        session.send("Hi I'm Hangman Bot. Type 'film' or 'song' to start a new game.");
        session.endDialog();
    }
]);

bot.dialog('/gameover', [
    function (session) {
        session.send('Game Over! You lose!');
        session.send(session.userData.word);
        session.endDialog();
    }
]);

function GetNextDialog(session, results){
    var nextDialog = '';
    if(results.response.length > 1)
    {
        if(results.response.toUpperCase() === session.userData.word.toUpperCase())
        {
            session.userData.masked = session.userData.word;
            nextDialog = '/win';
        }
        else
        {
            session.userData.lives--;
            nextDialog = session.userData.lives === 0 ? '/gameover' : '/guess';
        }
    }
    else
    {
        session.userData.letter = results.response;
        session.userData.masked = RevealLettersInMaskedWord(session, results);
        
        if(session.userData.masked == session.userData.word)
        {
            nextDialog = '/win';
        }
        else
        {
            nextDialog = session.userData.lives === 0 ? '/gameover' : '/guess';
        }
    }
    return nextDialog;
}

function RevealLettersInMaskedWord(session, results)
{
    var wordLength = session.userData.word.length;
    var maskedWord = "";
    var found = false;
    for(var i = 0; i < wordLength; i++)
    {
        var letter = session.userData.word[i];
        if(letter.toUpperCase() === results.response.toUpperCase())
        {
            maskedWord += letter;
            found = true;
        }
        else
        {
            maskedWord += session.userData.masked[i];
        }
    }
    if(found === false)
    {
        session.userData.lives--;
    }
    return maskedWord;
}

What next?

I will write another post soon to show you how to publish your bot.

If you want to see the code for my live hangman bot, it is on GitHub here

If you want to add my Hangman Bot to Skype you can do it using this link.

Why not try changing the code to have different levels of difficulty, maybe add in the ability to get clues etc.

http://www.codeshare.co.uk/blog/how-to-create-a-hangman-chat-bot-game-in-nodejs-using-microsoft-bot-framework/

Learn More





Also In The August 2017 Issue

A substitution cipher is an easy way to begin learning about how to use and make secret codes.

Scratch is a fun block-based programming language that's easy to learn once you understand the basics.

The micro:bit is a not too expensive board that lets you easily build projects to learn about computing.

The humble sewing machine can be a great first step to fun maker projects. Here's how to get started!

There's lots you can do make your online experiences enjoyable AND safe.

Minecraft is a fun game to play and a way to learn about games and programming. But first you have to learn the basics.

Have you ever put books in alphabetical order? What do you think the best method of alphabetizing would be?

Some ideas how to engage young women in computing and STEAM based on recent research.

These three dimensional objects are 3D printed and cast images when light shines through them.

How do computers predict what text you want to write next? Here's how to create predictive stories.

Links from the bottom of all the August 2017 articles, collected in one place for you to print, share, or bookmark.

Interesting stories about computer science, software programming, and technology for February 2017.