Creating a Cinch plugin part 2: a word game bot for IRC
This is the second part of a tutorial for creating a cinch IRC bot game. If you haven't read the first part then you can read it here.
This part will go through the basics of creating a cinch plugin, and will give you a basic implementation of the word game.
Creating the folder structure
We'll use the same structure that's commonly used in gems. This makes it easier to package the cinch plugin as a gem at a later stage, if you want to do so. We want to have a top-level folder for our plugin, containing the structure lib/cinch/plugins
.
Then, create a file called word_game.rb
- this will contain our plugin. Add the following:
Now, let's create a test file that we can use to run the bot. In the top directory ("cinch-wordgame") create a file called test.rb
, which looks like this:
If you run this file then your bot will join the IRC channel #wordgame
on freenode. Join the channel and run !word start
to see the bot reply with:
Starting a new word game
Well, it's not particularly exciting yet, but at least you now have an easy way of testing your changes. You can kill the bot's ruby process with Ctrl-C
.
Multiple matches
We need a way of starting the game and guessing a word. That means that there are at least two different message matchers that we need to add for the same plugin. We already have !word start
, but we also want something like !guess [word]
to actually make a guess. Fortunately, that's easy to do:
You can add multiple regular expression matches, and specify which method is used for each match. Our guess match records the word that the user gives, and passes it through to the guess
method (regular expression group matches are passed as arguments).
This is a bit better, but it's nowhere near a game yet. To get to the point of it being a playable game, we need to meet the following criteria:
The bot needs a list of words
When the game starts, the bot should choose a random word
When a guess is made, the bot should check if a game has been started
If it has, the bot should say whether the guessed word comes before or after the bot's word (and also whether it is a real word)
If the guess is correct then the bot should announce the winner and end the game
Loading some words
For this, we need a dictionary. If you're using Ubuntu, you already have one - you'll find it in "/etc/dictionaries-common/words". If you're not, download one here. It contains approximately 10,000 words, and it will do nicely for our purposes.
It makes sense to keep this concept encapsulated, so let's create a Dictionary
class. We don't need it to be accessible outside of the WordGame class, so we can nest it for the time being:
The dictionary class is initialized with an array of words. To build it from a dictionary file, I've added a class method Dictionary.from_file(filename)
. This assumes that each line of the file is a new word. I noticed that the Ubuntu dictionary includes proper nouns and words with apostrophes, so we only add them to our array of words if they are lowercase and don't include an apostrophe.
The dictionary object gives us two methods, random_word
, which returns a random word from the list, and word_valid?
, which allows us to pass in a word to check whether it exists in our dictionary.
Now, we need to create a loaded dictionary class. We can put this in the plugin class' initialize
method:
Change the path to the location of your dictionary file. Eventually we'll make it a configuration option for the plugin, but it's fine for it to be hard-coded for the time being. Note that it's very important to call super, otherwise cinch won't be able to initialize the plugin properly.
Starting a game
When the game starts, we want to choose a random word, and save this word so that we can compare it against guesses. We also want an easy way of comparing a word against another. To do this, we can create a very simple wrapper class for a word (again, we'll nest it within the plugin class):
Let's go back to our start
method and create a Word
to mark the start of a game:
Making a guess
Now we can respond to guesses from the user. In our guess
method:
It looks like a lot is going on, but it's actually fairly simple - it's just that there are quite a few paths that a guess could take.
Check to see whether a game has been started. If not, we let the user know how to start one.
Check whether the guessed word is in our dictionary, and tell the user if it isn't.
Finally, compare it to the random word we chose. If it's the same, then tell the user that they've one, and clear out the word. Otherwise, tell them whether the word comes before or after in the dictionary.
It's worth splitting this out into other methods, as the nested if
statements scream "refactor me". However, you now have a working game - go and try it out! Here's the full code so far, just in case something went wrong in the following of this tutorial:
Cheating
When you're playing this for the first time, you probably want to check that it's working correctly. Therefore, you might like to add a peek
command temporarily, which prints out the word:
When you're happy that it's working, replace it with this cheat
command, which ends the game if someone uses it (plus piles on the guilt):
Go and play
Congratulations, you now have a working word game! In the next and final part of the tutorial, I'll go through how to add configuration options and a help command.