Thursday 4 March 2010

Adventures in programming...

Back in the day I used to love text-based adventure games (the one that stands out is Leather Goddesses of Phobos). I have made several attempts to write my own, in various languages from BASIC, to C, to Java. The use of an object-orientated language like Java was a big help, but I was recently wondering how Ruby might work (whether it will even come to anything is dubious).

Ruby has a number of apparent advantages, and a consideration of how the data file might be illustrates that.
def setup
locations = [
Location.new(:id => :start, :name => 'The Start', :south => :hall,
:north => :lobby),
Location.new(:id => :hall, :name => 'The Great Hall', :north => :lobby),
Location.new(:id => :lobby, :name => 'The Swanky Lobby', :south => :hall),
]
items = [
Item.new(:id => :sword, :name => "Widowmaker Sword", :loc => :hall),
Item.new(:id => :robes, :name => "Magical Robes", :loc => :lobby,
:wearable => true),
Item.new(:id => :health, :name => "Healing Potion", :loc => :hall,
:consume => "He gained 5 hit points.<% @char[:health] += 5 %>"),
]
characters = [
Character.new(:id => :boris, :name => 'Boris', :location => 'Home',
:inv_limit => 2, :health => 5),
]
commands = [
{:verb => :take_from, :alias => [/^take ([\w ]+) from (\w+)$/,
/^take ([\w ]+) out of (\w+)$/,
/^remove ([\w ]+) from (\w+)$/],
:item_not_here => "It's not there."
},
{:verb => :get, :alias => [/^get ([\w ]+)$/,
/^take ([\w ]+)$/,
/^pick up ([\w ]+)$/],
:okay => "+char+ picked up +item+.",
:item_not_here => "It's not there.",
:inv_limit => "You're holding enough already.",
:cannot_get_static => "You can't get that!"
},
{:verb => :examine, :alias => [/^examine ([\w ]+)$/,
/^look at ([\w ]+)$/,
/^describe ([\w ]+)$/],
:item_not_present => "It's not here.",
},
{:verb => :simple_com, :alias => [/^look$/],
:script => "<%= @location.describe %>",
},
]
World.new locations, items, characters, commands
end

Data files are code

In all my previous attempts, my data files have been simple text files (perhaps in XML). This time I took the example of Rails; migrations, the routes files and rake files are all Ruby code, so there is far more you can put in there when you configure. Previously I have had to write code just to load my data; not so this time.

It should be easy to extend the system, even in run time. Create a new data file, perhaps with code to modify existing locations, and run it as a script.

Hashes

Ruby just asks for you to use hashes for pretty much everything (again, Rails does this a lot). They are so easy to create, to use and to pass around. Sure, Java has hashes too, but it never encouraged me to use them (so really this is only an apparent advantage).

At its simplest, you just make everything, whether the player, an item or the location, a hash, and give it the values appropriate. Want to add a new property? Just add a new value to the relevant hashes and away you go.

Want to save the data? In one line you can save an array of hashes to YAML, and load it back in in another line.

Regular Expressions

For understanding what the user is saying, regular expressions are so useful. An input like "put the cat in the bag" can readily be matched against /^put ([\s ])+ in ([\s ])$/, and straight away you can pull out what went in where (regular expressions have been in Java since 1.4; too late for when I was trying to use it).

Dynamic Method Calls

Once the command has been recognised, the appropriate method can be invoked simply by calling send, with the :verb value from the hash. Could be done using reflection in Java, but not as easy. Earlier languages... no way.

Scripts

With Ruby a scripting language, it is easy to just put scripts into the hashes. In the healing potion above, an ERB script is used to restore health.

No comments: