Share

Creating a Cinch plugin part 3: a word game bot for IRC

This is the third and final part of a tutorial for creating a cinch IRC bot game. If you haven't read the first two parts then you can start it here.

This part will add configuration options and a help command to the plugin.

Taking stock

At this stage, you should have a working word game. It allows someone to start a game, make guesses, win and cheat to end the game. The code should look pretty much like this.

It could do with some tidying up: for instance, if you start the game twice, it won't check to see whether there's an existing game in progress. But this is relatively simple stuff, so we'll move on to some of the things that are more specific to working with Cinch.

Adding a help command

First of all, let's add a help command, which we'll provide with !word help. Users will be able to call this command to work out how to play the game.

Add this block of code somewhere in your WordGame class:

  class WordGame
    #...

    match(/word help/, method: :help)
    def help(m)
      m.reply <<-HELP
Play a simple but addictive game, using words in the dictionary.

The bot will pick a random word, and you have to work out which word by making guesses. For every guess you make, the bot will tell you whether the word comes before or after (by alphabetic sorting).

Commands:
!word start   - Start a new game, with the bot picking a word
!guess <word> - Guess a word
!word cheat   - If you simply can't carry on, use this to find out the word (and end the game)

Have fun!
      HELP
    end
  end

Note that you will need to add a single space on empty lines, as cinch will only print lines if they aren't blank.

Adding configuration options

There are a couple of things that we could allow users to configure. The most obvious one is allowing them to specify the dictionary used, or to allow them to provide a custom word list.

First, let's allow them to specify the path to the dictionary file. You set plugin configuration options along with all the other configuration (e.g. channel name, bot name, etc). You will probably have been using a test file for this, so add it there. Mine looks like this:

require 'cinch'
require_relative "lib/cinch/plugins/word_game"

bot = Cinch::Bot.new do
  configure do |c|
    c.server = "irc.freenode.net"
    c.channels = ["#wordgametest"]
    c.nick = "wordgame"

    c.plugins.plugins = [
      Cinch::Plugins::WordGame
    ]
    c.plugins.options = {
      Cinch::Plugins::WordGame => {
        :dictionary_file => "/path/to/dictionary"
      }
    }
  end
end

bot.start

The plugin options are in a hash, which is indexed with the plugin class. Each plugin can have a hash of options, and options are accessed in the plugin with the config hash. For example, in our WordGame initializer:

  class WordGame
    def initialize(*)
      super
      dictionary_file = config[:dictionary_file] || "/etc/dictionaries-common/words"
      @dict = Dictionary.from_file dictionary_file
    end

    #...
  end

It's that simple. Now users can specify the file which contains the list of words.

However, this probably isn't enough. What if the user doesn't have a dictionary file with their particular OS? To be as inclusive as possible, we should provide the option to pass an array of words, so the user can define exactly which words are used. This way, if they want to get their words from a different source, they can do that. Let's add a dictionary option:

    c.plugins.options = {
      Cinch::Plugins::WordGame => {
        :words => %w(apple banana grape orange pear strawberry)
      }
    }

The reality is that they will use more words that this, otherwise it will be a very short game! In the plugin, we can now check to see whether the user has provided a list of words, and fall back to the dictionary file otherwise:

  class WordGame
    def initialize(*)
      super
      if config[:words]
        @dict = Dictionary.new config[:words]
      else
        dictionary_file = config[:dictionary_file] || "/etc/dictionaries-common/words"
        @dict = Dictionary.from_file dictionary_file
      end
    end

    #...
  end

The config hash is accessible to any of the instance methods that you use within your plugin, not just the initializer.

The end

That's it! Thanks for reading, and I hope you learnt something about cinch. I'll be releasing the word game as a gem fairly soon, so you can just drop it in to your cinch bot.

← Previous post: Strange behaviour of Enumerable#each_with_object and array concatenation Next post: Understanding ssh-agent and ssh-add →
comments powered by Disqus