logo

 

     
 
Home
Site Map
Search
 
:: Bitwise Courses ::
 
Bitwise Dusty Archives
 
 
 

rss

 
 

ruby in steel

learn aikido in north devon

Learn Aikido in North Devon

 


Section :: Ruby

- Format For Printing...

Adventures In Ruby, Part 2

Further explorations in Ruby programming...
Sunday 7 October 2007.
 

Time to get on with the next phase of my adventure game written in Ruby. This series comes with a warning: I’m going to be writing code the way I do in real life - which is not necessarily the way that programming writers generally like to give the impression that they write code. From time to time, I may try things out, then change my mind and go back and rewrite my code. In fact, this series is a bit like an adventure game itself: full of puzzle-solving and exploration through the world of Ruby. If you are looking for a tutorial that moves in smooth and painless progressions with no dead-ends or U-turns, this series is not for you. If, on the other hand, you want to learn some Ruby and have some fun along the way, jump on board...

This month we’ll work out ways of taking and dropping the treasures located in the various ‘rooms’ of the game.

Download The Source Code

Zip - 5.3 kb

The code that goes with this series can be downloaded in the zip file, rubyadventure2.zip. This contains the original version of the project in the subdirectory \Adventure1 and this month’s rewritten version in the subdirectory, \Adventure2. You can load the Ruby files (.rb) into any text editor. I also supply a project file, Adventure.sln, which can be loaded into Ruby In Steel Developer. The screenshots in this article all show Ruby In Steel.
For a more structured guide to Ruby programming, try out my free e-Book, ‘The Little Book Of Ruby’. You can download a copy from the SapphireSteel Software site.

See also: Part One and Part Three of this series.


Getting Ready

Before going any further, I want to do some ‘housekeeping’, to tidy up my existing code. In my original project (see part one of this series), all the code was in a single file. I’ve decided to move the class definitions into a separate file, called adventureclasses.rb with the remainder of the code in a file called adv2.rb.When you want to use one Ruby file in another you generally ‘require’ that file as I have done at the top of adv2.rb (you can omit the “.rb” extension when requiring a file):

require "adventureclasses"

Previously, the game data - the rooms and the treasures - was added inside the initialize method of the Implementer class. The Implementer is the ‘god’ of the game world - the thing that looks down from above, moving pieces around like chessmen over a board. It’s all very well putting all the data initialization into the Implementer if I only ever plan to write one game. But my ‘game system’ will be far more adaptable if I keep the classes (which might apply to any game) separate from the data (which applies to this specific game). In this new version, therefore, I’ve put all the data-initialization into a new method, createAdventure, in the ‘main’ unit, adv2.rb. At a later stage of development, this data might be saved to disk and loaded when a new game starts.

Finally, I’ve added @player as an ‘instance variable’ of the Implementer class. An instance variable is one which belongs to a specific object. And I’ve added a moveTo() method which takes a symbol such as :n or :s to specify a direction and then moves the player into the next room located in that direction (if there is one). It does this by calling the moveActorTo() method which is capable of moving any object in the game (though it expects and ‘Actor’, which is my name for any object such as a monster, a character or even the game-player, which has an active ‘part to play’). Ruby symbols and the moveActorTo() method were described in part one of this series. I’ve also made one small change (which was suggested in one of the comments to my previous article), by using the send method to evaluate a symbol as code rather than method.call:

exit = @game.map.rooms[anActor.position].send(aDirection)

The end result is the same (i.e. if the argument aDirection has the value :n, the method named n will be executed) but the code is a bit neater. Now, in order to create a game I just create an array of Room objects and an Implementer object, @imp which is initialized with the rooms:

@imp = Implementer.new( someRooms )

The Implementer then creates a Game object which contains a Map object (that is, the array of Rooms). Currently the Game and the Map classes have no special behaviour and I could have omitted them altogether without any real loss of functionality. However, I’d like to leave myself the option of adding Map and Game-specific behaviour later on (for example, I may want to be able to save and load different game objects).

Modifying The Class Hierarchy - Things Within Things

I am now ready to make a fairly dramatic change to the class hierarchy itself. Up to now all my rooms have been empty. And, in an adventure game, empty rooms are boring. After all, the whole point of an adventure is to explore, find and collect things. That means that each Room object must be able to contain one or more other objects. And the player must be able to collect objects from rooms. In other words, objects created from classes such as Room and Actor (the player is an Actor object) must be able to ‘hold’ zero or more Things of arbitrary types.

I’ve added a class, called ThingHolder. This descends from Thing (the ultimate type of all my game classes); it has an instance variable @things (an array of Thing objects) plus a few methods for manipulating that array. For example, this method adds a Thing to the end of @things array:

def addThing( aThing )
  @things << aThing
end

And this one concatenates several Things (the array, someThings) to @things:

def addThings( someThings )
  @things.concat(someThings)
end

And this one deletes a Thing from one array, someThings and adds it to the array, @things:

def takeThingFrom( aThing, someThings )  
     addThing( someThings.delete( aThing ) )
end

Incidentally, the above code could be written as two operations, like this:

addThing( aThing )
someThings.delete( aThing )

However, as the delete method returns the object itself (if found), I’ve compressed these two lines into one:

addThing( someThings.delete( aThing ) )

Nil Desperandum

There’s a potential problem in the above code, however. If the object isn’t found, delete returns nil. As a consequence, if we don’t take some special action, we could easily and up adding lots of nils into the @things array. Since nil objects are not the sorts of things that adventure game players tend to want to collect, I’ll have to do something to avoid this problem before it arises.

It is normal, in an adventure game, to check if a specific object is actually in the current location before allowing the player to take it. I haven’t added any user interaction to the game yet but I started preparing for the possibility. I’d like the user to be able to tell the game to (for example) “Take the wombat” and I’d like the game to respond either “OK, you now have the wombat” or, alternatively, “There is no wombat here” - and, naturally, I only want the wombat to be added to the player’s inventory if there is a wombat to add!

If this were a conventional database application instead of a game, I might insist that each item have a unique value so that I could check if item #12345 is in stock and only add it to the shopping basket if it is. Games are messier than that. A game could, in principle, contain many treasures with the same or similar names - a silver ring, a golden ring, a magic ring, and so on. If the user says “Take ring” and any one of those rings is in the current location, it is reasonable to allow the ring to be taken. If two or more rings are present, the game might respond “Which ring do you want to take - the silver ring, the golden ring or the magic ring?” It may be a while before I get around to adding that level of interaction but I have to start preparing for it now. At the very least, I have to be able to see if an object in any given list (the @things array inside a ThingHolder object) can be identified by its name. Only if it can, is that object deemed to be ‘here’ and available to be transferred from one array to another (i.e. either ‘taken’ from a room or ‘dropped’ into a room). So I need a method, which I’ll call obInThings, that iterates through the @things array, trying to match a specific name given by a Thing object’s name attribute; here the name attribute is a pair of ‘getter’ and ‘setter’ methods which are used to access an object’s @name instance variable (refer to part one of this series for more on attributes).

Blocks and Iterators

I have previously written adventure game systems in C# and Delphi (Pascal) - and the influence of those languages can probably be seen in my initial implementation of obInThings. My first version of this method iterated through the @things array using a variable, i that was incremented by 1 at each turn through the loop. If a match was made, a local variable ob was assigned the matching object from the array and that object was returned at the end of the method. The while loop itself executed as long as i was less than the length of the @things array and ob was nil (that is, it hadn’t het been assigned an object). Here is that method...

def obInThings( aThingName )
     i = 0
     ob = nil
     while( i < @things.length )  and ( ob == nil ) do
        if ( @things[i].name == aThingName ) then
           ob  = @things[i]
        else
           i += 1
        end
     end
     return ob
end

There’s nothing really wrong with this implementation. It does the job. But when I look at it, I can’t help feeling I ‘mentally’ wrote it in Pascal with a very slight ‘Ruby makeover’. Surely there must be a more Ruby-like way of accomplishing this. Well, here is one possibility...

def obInThings( aName )
     ob = nil
     @things.each{ |t|
        if( t.name == aName )        
           ob = t
           break;
        end
     }
     return ob
end

This uses the each method of an array to pass each element into the block (i.e. the code between curly brackets) one by one. As each element arrives in the block it is assigned to the block variable, t, declared between upright bars. A block is sometimes described as a nameless method. If you think of it in that way, block variables are like arguments to a named method. When each object from the array is passed into the block, the code checks if the object’s name attribute matches aName. If so, the variable ob is assigned the object, we break out of the block and return the matched object.

For another, slightly simpler, example of a block that iterates over an array of objects and makes a match based on a string comparison, refer to the findob.rb program in the source code zip archive.

There are a couple of things to note in my new implementation of obInThings.

- 1) There are two ways of delimiting blocks in Ruby - curly brackets or a pair of do and end keywords. Some programmers like to use curly brackets if all the code fits onto one line and do and end otherwise. I prefer to use the same delimiters in all cases which is why I’ve stuck to brackets.

- 2) In Ruby, a local variable outside a block which happens to have the same name as a block argument can ‘take on’ the value of the block argument. So I could omit the ob variable completely and rewrite the method like this (where the local variable t will be assigned the last value given to the block variable, t)...

def obInThings( aName )
     t = nil
     @things.each{ |t|
        if( t.name == aName )                    
           break;
        end
     }
     return t
  end

I haven’t done this in my code for the simple reason that I wouldn’t sleep at night with code like that in my program. Matz, the author of Ruby, has expressed his own concerns about the perils of block-scoping and changes are expected in a forthcoming release of Ruby. At any rate, an extra ob variable costs me nothing in programming effort and adds to the clarity of the code.

- 3) I must admit a personal prejudice against the use of breaks wherever they can be avoided. It is my general habit to test values and exit from blocks or loops only when those values are met (as in my original Pascal-like version of this method). However, break is a quick and effective solution to exiting this block - preventing the iteration from continuing once a match has been made - so, on this occasion, I am prepared to grin and bear it... Those of you with no such aversions to break may put down my concerns to a strong personal preference for the eating of quiche.

Setting The Game In Motion

Now, if you turn to the ‘main’ unit of this program, adv2.rb, you can see how I have made use of the rewritten classes. First, I’ve created an array of Room objects...

someRooms = [ Room.new("Treasure Room", "a fabulous golden chamber", r1obs, -1,  2, -1,  1),
     Room.new("Dragon's Lair", "a huge and glittering lair", [],-1, -1,  0, -1),
     Room.new("Troll Cave", "a dank and gloomy cave",[],        0, -1, -1,  3),
     Room.new("Crystal Dome", "a vast dome of glass", [],      -1, -1,  2, -1)  
]

When new is used, it created a new Room class, then calls its initialize method, passing to it the specified parameters. Here, these parameters initialize, in order, the room’s name, description, list of things (@things) and its four exits (N, S, W and E). In most cases, the list of things is empty, []. However, I’ve added some objects (r1obs to the first room:

r1obs = [Treasure.new("sword", "a lovely golden sword", [], 50 ),
         Treasure.new("ring", "a ring of great power", [], 100 ),
         Treasure.new("wombat", "small, furry, gently snoring creature", [], 3 )]

Note that, being descendents of the ThingHolder class, these Treasure objects can, themselves, contain things. After all, you might want to have a Treasure Chest object or a sack or a reticule or some other sort of container. So far, however, the objects in my game are not the sorts of things that contain other objects so their arrays are all empty: [].

Here I’ve collapsed the classes to show just their names. In Ruby when a class name is followed by a < and another class name, this indicates that the class to the left of the < is a direct descendent to the class on the right. Otherwise, a class descends directly from the Object class.

The class hierarchy of my game now starts with Thing from which descends ThingHolder. The descendents of ThingHolder are: Treasure, Room, Actor and Map (which contains an array of Room objects). There are two other classes which are not part of this hierarchy: Game and Implementer.

Once the game has been created, the rest of my code simply ‘mimics’ player interaction. Later on, I’ll add the ability for a human being to enter commands. For testing purposes, however, it’s sufficient to simulate interaction by calling methods to move the player around the map and to test that specific objects are in a specific locations:

puts( @imp.moveTo( :e ) )
puts( @imp.moveTo( :w ) )
puts( @imp.moveTo( :n ) )
puts( @imp.moveTo( :s ) )
puts( @imp.moveTo( :e ) )
puts( @imp.moveTo( :s ) )

x = @imp.game.map.rooms[0].obInThings("ring")

I can also display the entire contents of a Room (here the room at index 0) or of the player’s inventory:

p @imp.game.map.rooms[0].things
p @imp.player.things

I can try taking an object (x) from the room in which it was found and adding it to the player’s inventory:

@imp.player.takeThingFrom( x, @imp.game.map.rooms[0].things )

Finally, I can check the contents of the room from which the object (here a “ring”) was taken; and I also check the player’s inventory to ensure that the object really has been removed from the former and added to the latter:

p @imp.game.map.rooms[0].things
p @imp.player.things

Here I am using the Ruby In Steel debugger to ‘drill down’ into Room and Actor objects. This lets me verify which objects are found in the @things array. Here, for example, I can see that the “ring” object has been removed from the first room and added to the player’s @things inventory.

Incidentally, if you have been paying close attention, you might have noticed one curiosity in the above code. Normally, any class that descends from ThingHolder has an attribute, things, which gives access to its internal array of objects (@things). So, since the Map class is a descendent of ThingHolder, in order to access the Room objects which it contains, it might seem that I should write:

@imp.game.map.things

The trouble is, that when I want to access an array of Room objects, I’d like to call it rooms, not things. I could, of course, have achieved this by writing the Map class from scratch (as a direct descendent of Object), detaching it from the ThingHolder hierarchy. But that seems an extreme measure just to have a different name for an attribute accessor. In fact, if you look at my definition of the Map class you will see how I have changed the name of the things accessor:

First I’ve ‘aliased’ the accessor name. In Ruby, you can make one name a ‘synonym’ of another name using the keyword alias (in the body of a class definition), followed by two symbols - the new name then the old name. Here I make rooms an alias for things:

alias :rooms :things

If that is all I’d done, I would be able to use both the accessor method names, things and rooms interchangeably with Map objects, like this:

@imp.game.map.rooms
@imp.game.map.things

But I’d prefer to have only rooms available for use with Map objects. To accomplish this, I can undefine the things accessor for this specific class (the things accessor will still be available in all other classes descending from ThingHolder):

class Map < ThingHolder
  alias :rooms :things
  undef_method( :things )  
end

So now, this works fine:

@imp.game.map.rooms

But this produces an error of the type ‘NoMethodError’:

p @imp.game.map.things

A word of warning. While aliasing and undefining methods can, in some circumstances, be very useful, they can, in other circumstances, build into your code some very difficult-to-track-down bugs. Remember that, while Ruby may superficially look like a simple and safe language, it is, in fact, extremely complex and powerful. Use with caution!

OK, so now we have a basic structure for the game - complete with a Rooms, Treasures and a Player who can move around, taking and dropping things. What this the game lacks is interactivity. That’s something I’ll be adding in my next article...


Huw Collingbourne began programming in the early ‘80s. His first big programming project was an adventure game called The Golden Wombat Of Destiny (download here) which was written in Turbo Pascal. He is now Technology Director of SapphireSteel Software, developers of the Ruby In Steel Ruby On Rails IDE for Visual Studio.

Ruby In Steel

AddThis Social Bookmark Button

Forum

  • Adventures In Ruby, Part 2
    10 October 2007, by James OKelly

    Very cool. What license are you releasing this under? Will we be able to use at as our 404s? :)

    • Adventures In Ruby, Part 2
      10 October 2007, by Huw Collingbourne

      You mean what license is the game system under? I’d say one that goes something like this: take it, use it, do what you want with it - just don’t blame me! ;-)

      best wishes

      Huw

  • Adventures In Ruby, Part 2
    8 October 2007, by Mike Woodhouse

    re obInThings()

    I’m not sure when it appeared (1.8?) but there’s a handy little method on Array that lets you reduce to this:

    def obInThings( aName )

    @things.find |t| t.name == aName

    end

    ... or use Array#select to get an array of all matches.

    Oh, I just checked - it’s actually on Enumerable. So you can do it with Hash too.

    Which is nice.

    • Adventures In Ruby, Part 2
      9 October 2007, by Huw Collingbourne

      Thanks for that. Enumerable::find (or detect) slipped under my radar. I’ll try that version out in my next revision.

      all the best

      Huw


Home