
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 &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