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 servery 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.
I hope you enjoyed this post, please feel free to comment below.