Making a Simple Game in Ruby with Metaprogramming

Previously in the series we begin exploring the basics of metaprogramming by creating a quine, a small program that replicates itself. Today we’re going to continue our exploration into metaprogramming by creating the command line RPG “Dwemthy’s Array”.

The Creature Class

This game consists of two different classes and we’re going to start off with creating the creature class that’s used to create both the player character and the monsters they’ll fight.

First start by creating a class called creature. Then inside that class create a metaclass using the .self keyword. The self method allows you to manipulate the current object, in this case the creature class, and the metaclass keyword allows you to define classes and methods within another class.

class Creature

  # Get a metaclass for this class
  def self.metaclass; class << self; self; end; end

Next define a method called traits which returns itself when empty. You also need to set up an accessor for each variable so that way they can be used by the program.

# Advanced metaprogramming code for nice, clean traits
  def self.traits( *arr )
    return @traits if arr.empty?

    # 1. Set up accessors for each variable
    attr_accessor( *arr )

After this you need to create a new class method for every trait that currently exists within the creature class.

# 2. Add a new class method to for each trait.
    arr.each do |a|
      metaclass.instance_eval do
        define_method( a ) do |val|
          @traits ||= {}
          @traits[a] = val
        end
      end
    end

Once all of the traits have been added as class methods they need to be initialized for each creature currently in the game.

class_eval do
      define_method( :initialize ) do
        self.class.traits.each do |k,v|
          instance_variable_set("@#{k}", v)
        end
      end
    end

  end

Every creature has the following four traits: life, strength, charisma and weapon. You should define these and make them all read only.

 traits :life, :strength, :charisma, :weapon

Now we need to define the methods which will allow turns to pass by and creatures to take and receive hits during a fight. First define the method which will apply the damage. The hit method should have a random chance of healing the creature based on its charisma and then reduce its life by the damage taken. If it’s life goes below zero the creature is dead and can no longer continue fighting.

def hit( damage )
    p_up = rand( charisma )
    if p_up % 9 == 7
      @life += p_up / 4
      puts "[#{ self.class } magick powers up #{ p_up }!]"
    end
    @life -= damage
    puts "[#{ self.class } has died.]" if @life &amp;lt;= 0
  end

Lastly you need to create the fight method which will be where the two creatures fight each other. Each creature should do a random amount of damage from 0-strength+weapon until one of them is dead.

# This method takes one turn in a fight.
  def fight( enemy, weapon )
    if life < = 0
      puts "[#{ self.class } is too dead to fight!]"
      return
    end

    # Attack the opponent
    your_hit = rand( strength + weapon )
    puts "[You hit with #{ your_hit } points of damage!]"
    enemy.hit( your_hit )

    # Retaliation
    p enemy
    if enemy.life > 0
      enemy_hit = rand( enemy.strength + enemy.weapon )
      puts "[Your enemy hit with #{ enemy_hit } points of damage!]"
      self.hit( enemy_hit )
    end
  end

end

Now that the creature class is finished you can move on to creating Dwemthy’s Array.

The Dwemthy’s Array Class

This class will be an array and so should inherit from the array class.

class DwemthysArray < Array

The class needs to be able to ready the first monster in it to fight and then when it dies delete that monster from the array and shift the next one in line to the front.

 alias _inspect inspect
  def inspect; "#<#{ self.class }#{ _inspect }>"; end
  def method_missing( meth, *args )
    answer = first.send( meth, *args )
    if first.life <= 0
      shift

If the last monster in the array has been killed a small victory message should appear to the player and then the game should end. Otherwise whenever a monster is killed a message should appear telling the player the name of their new foe.

 if empty?
        puts "[Whoa.  You decimated Dwemthy's Array!]"
      else
        puts "[Get ready. #{ first.class } has emerged.]"
      end
    end
    answer || 0
  end
end

Congratulations you’re ready to start actually playing the game now!

Playing the Game

To play the game you need to create yourself and the creatures that you’ll be fighting. Simply create a new class for yourself and then initialize it like so:

class Rabbit < Creature
    life 10
  strength 2
  charisma 44
  weapon 4
 end
r = Rabbit.new

To create monsters it’s the exact same process except that they need to be initialized within the Dwemthy’s array class.

class IndustrialRaverMonkey < Creature
  life 46
  strength 35
  charisma 91
  weapon 2
end

class DwarvenAngel < Creature
  life 540
  strength 6
  charisma 144
  weapon 50
end

class AssistantViceTentacleAndOmbudsman < Creature
  life 320
  strength 6
  charisma 144
  weapon 50
end

class TeethDeer < Creature
  life 655
  strength 192
  charisma 19
  weapon 109
end

class IntrepidDecomposedCyclist < Creature
  life 901
  strength 560
  charisma 422
  weapon 105
end

class Dragon < Creature
  life 1340     # tough scales
  strength 451  # bristling veins
  charisma 1020 # toothy smile
  weapon 939    # fire breath
end

dwarr = DwemthysArray[IndustrialRaverMonkey.new,
                      DwarvenAngel.new,
                      AssistantViceTentacleAndOmbudsman.new,
                      TeethDeer.new,
                      IntrepidDecomposedCyclist.new,
                      Dragon.new]

To actually play the game you will need to either copy all of the source code above into the irb or put it into a file called dwemthy.rb and load request dwemthy in the irb.

Further Suggestions

The game in its base form is fairly simplistic but you can easily insert additional complexity into it. You can add more traits to the base class which add brand new elements to the game, insert more random events beyond the occasional chance to regain life and even expand the game into a full on text based adventure game which simply uses the array for its combat sections.

Good Luck!

Download the above source code here.

If you have any questions or comments email them to me at nick@crumbsofcode.com