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 3

User Interaction - Making a Hash of it!
Thursday 1 November 2007.
 

In the first two parts of this series, I concentrated on constructing an appropriate class hierarchy for an adventure game. So far, there has been no user interaction at all. When I wanted to test the game, by moving around the map or taking a and dropping objects, I did so by simulating user interaction (as in Adventure/adv2.rb in the source code archive). The time has now come to move beyond this and add some means by which a real live player can interact with the game.


Download The Source Code
Zip - 8.6 kb

Zip archive of the code that goes with this series.

The Zip archive contains both the current and previous versions of the project. 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 project will appear in the Solution Explorer (see above). With other IDEs or editors, load the Ruby code files one at a time. The screenshots in this article all show Ruby In Steel.


See also: Part One and Part Two of this series


For the time being, I am going to assume that interaction will take the time-honoured traditional form of entering commands at a prompt. Later on, I’ll provide an alterative user ‘visual’ interface (for .NET programmers). My intention to provide two alternative interfaces - command line and form-based - necessarily means that I must be sure to keep the code that deals with user interaction quite separate from the ‘program logic’. Later on, I shall also add saving and loading features. The currently fashionable way of describing the separate of these three parts of an application is MVC (Model, View, Controller) where the Model describes the data-storage and management; the View is the user interface and the Controller is the ‘program logic’.

As with most programming acronyms (DRY for ‘Do not Repeat Yourself’ being another currently fashionable example), MVC describes a general principle to which, in broad terms, many of us adhered long before we’d encountered the acronym (in the old days we just called it ‘modularity’). My adventure game will only follow the MVC paradigm insofar as it seems useful and, accordingly, may not satisfy the full rigours of the MVC methodology. It will, however, attempt to divide the code up into logical chunks to ensure a reasonable degree of modularity.

First, Let’s Clean Up That Code...

As always, I’m going to begin by tidying up the previous version of this project. I’ve decided to adopt the suggestion made in a comment to part two of the series and rewrite the obInThings method (in adventureclasses.rb) using the find method of Ruby’s Enumerable module. The obInThings method iterates over the items in an array and will (eventually) be used to check that a specific object is in a specific location before allowing the player to ‘take’ or ‘drop’ it. My previous implementation worked ok but has to ‘break’ out of the loop when a specific object is found:

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

I’ve never liked breaking out of loops. The new version avoids this:

def obInThings( aName )            
     @things.find{
        |t|
        t.name == aName
        }
end

Modules and Mixins

Newcomers to Ruby might now be wondering how I am able to use the find method of Enumerable when @things is an Array object. Normally, objects can only access the methods of the class which defines them (here Array) or of its ancestor classes. The Array class only has one ancestor - Object, which is the ‘base class’ from which all Ruby classes descend. Neither Array nor Object defines a find method - so how is it that an array object is able to use it?

This is explained by the fact that Enumerable is not a class but a ‘module’. In Ruby, modules are like classes apart from the fact that you cannot inherit from a module. What you can do, however, is ‘include’ a module into a class. This is sometimes called ‘mixing in’ modules and - for shorthand - modules which are mixed in are called ‘mixins’. When a module is mixed into a class, the methods in the module become available to objects defined by the class. It turns out that the Enumerable module is mixed into the Array class. The Enumerable module defines the method find so Array objects can use the find method.

It is often said that mixed-in modules are Ruby’s alternative to multiple inheritance (though it could also be argued that they are simply a way to ensure ‘modularity - that is, they gibe you an object oriented way of creating reusable code units).

As Ruby only permits a single line of inheritance, each class only has a single parent - so you could define MyArray as a descendent of Array which itself descends from Object. While no class can have more than one parent it can mix in multiple modules. Moreover, a single module can be mixed in to different classes. The Enumerable module, for example, is mixed into the String and Range classes as well as the Array class. As a result, String, Array and range objects (which are not a part of the same ‘family tree’) can all use the same methods to find and collect items (see the program: enumerable.rb):

# Range
(1..5).find{ |i| i == 3 }           #=> 3
(1..5).collect{ |i| i == 3 }        #=> [false, false, true, false, false]

# Array
[1,2,3,4,5].find{ |i| i == 3 }      #=> 3
[1,2,3,4,5].collect{ |i| i == 3 }   #=> [false, false, true, false, false]

#String
"123".find{ |s| s == "123" }        #=> "123"
"123".collect{ |s| s == "123" }     #=>[true]

For an example of defining your own mixed in modules, take a look at my sample program, mixins.rb. This defines the Friendly module which has two methods, sayHello and sayGoodbye:

module Friendly
  def sayHello
     puts( "hello from #{@name}" )
  end
 
  def sayGoodbye
     puts( "goodbye" )
  end
end

This module is then ‘mixed in’ (using the include method) to two unrelated classes, Class1 and Class2:

class Class1
  include Friendly        
  # class1’s methods here...  
end

class Class2
  include Friendly
  # class2’s methods here...
end

Now, when an object is created from Class1, it can call all the methods defined in Class1 plus all the methods defined in the Friendly module but it cannot call methods defined in Class2. Similarly, when an object is created from Class2, it can call all the methods defined in Class2 plus all the methods defined in the Friendly module but it cannot call methods defined in Class1:

ob1 = Class1.new
ob2 = Class2.new

ob1.method1                         #<= a method of Class1
ob1.sayHello                        #<= methods in the Friendly module
ob1.sayGoodbye

ob2.method2                         #<= a method of Class2
ob2.sayHello                        #<= methods in the Friendly module
ob2.sayGoodbye

I have also started to divide up the code into more logical units (in separate files). The fundamental classes remain, as previously, in adventureclasses.rb while the ‘game setup’ routine, which initializes the data has been moved into its own file, advinit.rb. There is not much in this file at present but it is likely to grow as the game gets bigger and when I add saving and loading routines. Finally, the ‘entry’ point of the game has been moved into advmain.rb. In principle, this should comprise the user interface part (the ‘view’) though currently it also contains some data and methods. These are convenient to have here at this stage in the program development but they will be moved elsewhere when I start working more seriously on the user interface.

Two Files, One Class

OK, so that’s the housekeeping out of the way. Now let’s see what’s new. If you look in advmain.rb you’ll see I’ve created an Adventure class and, from this, an adventure object, @adv. This object is, the ‘entry’ point to the game. In Ruby, when an object is created by calling its new constructor, a method named initialize (if defined) is automatically called. This is the method as defined by the Adventure class:

def initialize
  @imp = createAdventure
end

Here createAdventure is the method which creates all the game objects in advinit.rb. The game and all its objects are ‘owned’ by the implementer (an object created from the Implementer class) and this object is returned by the createAdventure method.

Notice, by the way, that, even though initialize and createAdventure are in separate code files, they are both within the same class: Adventure. Ruby lets you created ‘partial’ classes by using the same class name in different places, like this:

class Adventure
  # 1st set of  methods
end

class Adventure
  # 2nd set of methods
end

In the cases above, the two definitions of the Adventure class are ‘stuck together’ by Ruby, so the above code is equivalent to the following:

class Adventure
  # 1st set of  methods
  # 2nd set of methods
end

Having created the game, I next call its rungame method:

@adv = Adventure.new
@adv.rungame

This method runs the ‘main loop’ which prints a prompt “> “, gets input from the user and then calls processInput to deal with the input. Notice that the input is read in using gets which gets an entire line of text up to and including the carriage return, it then sets it to lowercase using the downcase method and finally calls chomp to remove the carriage return:

def rungame
     input = ''
     while ($gameRunning)
        print( "> " )
        input = gets.downcase.chomp
        processInput( input )
     end
end

Global, Local and Instance Variables

Before finding out what happens in the processInput method, it might be worth taking a moment to look at the three types of variables I’ve been using in this program. There are those like @adv and @imp which begin with a ‘@’, then there is $gameRunning which starts with ‘$’ and finally there is input which just starts with an alphabetic character.

In Ruby, a variable that begins with ‘@’ is an instance variable. It lives inside the specific instance of some class - that is, its scope is restricted to the scope of a specific object. If it is declared free-standing, like @adv its scope is the ‘main ‘ object which Ruby creates automatically. If it is declared inside a class, like @imp inside the Adventure class, its scope is that of an object created from that class - so here the value of @imp can only be accessed from inside the @adv Adventure object.

A variable that begins with ‘$’ has global scope and it can be accessed anywhere. Here $gameRunning is global. It is set to true on declaration and is set to false when the user decides to quit. Being accessible throughout the game, it can be set to false at any point (this may not, in fact, be desirable and I may decide to change the scope of this variable in a later revision).

Finally, local variables such as input have the ‘tightest’ scope. Here, for example, input is only visible in the method in which it is defined. More examples of these three types of variable can be found in the vars.rb program.

The vars.rb program illustrates the scope of global, local and instance variables.

Processing User Input

Now, having got some input from the user, we have to try to make sense of it and take some appropriate kind of action. This is done in the processInput method. First of all, it tries to match the input with a known command:

command = $commands[input]

This looks a bit like indexing into an array, apart from the fact that the index (input) happens to be string rather than a number. This is explained by the fact that $commands is not an array but a Hash. A Hash is a sequence of key-value pairs in which a unique key can be used to look up an associated value. Some languages call Hashes ‘Dictionaries’ which, in the present case, is a much better description. My $commands Hash is just like a dictionary of synonyms. Each string defines a unique entry, just like the entries in a real dictionary, but its definition (the values with which it is associated) may be repeated just as the same the definition may be given for multiple words in a dictionary. Thus my dictionary defines both the words ‘take’ and ‘get’ to mean “take” (that is, the strings ‘take’ and ‘get’ are both associated with the symbol :take - I explained symbols in part one of this series)...

'take' => :take, 'get' => :take

Notice that the key-value pairs in a Hash are associated using the => ‘assoc operator’, each pair is separated by a comma and the entire Hash is delimited by curly brackets. here is my complete dictionary of commands:

$commands = { 'n' => :n, 's' => :s, 'e' => :e, 'w' => :w,
          'q' => :quit, 'quit' => :quit,
          'take' => :take, 'get' => :take,
          'drop' => :drop,
          'look' => :look }

So, assuming the user entered ‘get’, the following code...

command = $commands[input]

...indexes into the Hash at the ‘position’ defined by the string ‘get’...

'get' => :take

This returns the value :take and this is then assigned to the local variable, command.

The remainder of the processInput method comprises some if..else tests to execute code if the command matches a specific value. If it is nil then no match was made in the Hash so the command is unknown and an error message is displayed:

if it is :quit then the user wants to quit so the global variable, $gamerunning is set to false; this will cause the main loop to exit and the game will end:

if it is a direction then the Implementer class’s moveTo method is called to move the player around the map (as explained in the previous parts of this series):

Note incidentally, that I’ve written a small method, isDirection which tests whether the command can be found in an array of valid ‘direction’ commands (:n,:s,:e,:w). If it is found, true is returned, otherwise false is returned:

def isDirection(command)
  return [:n,:s,:e,:w].include?(command)
end

Currently, only the direction commands have any methods to implement them. When a command is ‘understood’ (i.e. it has a key in the $commands Hash) but has no code to handle it, a ‘not yet implemented’ message is displayed:

Here, then is the entire processInput method:

def processInput( input )
     command = $commands[input]
     if (command == nil)
        puts( "Sorry, I don't understand '#{input}'!" )
     elsif (command == :quit)
        puts( "Game end" )
        $gameRunning = false
     elsif (isDirection(command))
        puts( @imp.moveTo( command ) )
     else
        puts( "Command #{command} not yet implemented" )
     end
end

So, finally my adventure game has gone interactive! You can now run it from a console and enter commands such as ‘n’, ‘s’, ‘w’ and ‘e’ to move around the map and ‘q’ or ‘quit’ to end the game. I haven’t yet dealt with ‘take’ and ‘drop’. Actually, I already have the necessary method to handle these commands (see takeThingFrom in the ThingHolder class) but I haven’t yet ‘wired up’ the command processing bit of the game to that method. You might like to try this yourself. Bear in mind that you will need to do some elementary ‘parsing’ in order to handle a two word command (e.g. “Take sword”) which is why I’ve left the processing of these commands for a later date.

In the next part of this series, I’ll consider ways of handling multiple word commands and I’ll also be looking at ways of creating an alternative user interface.


The License Of This Game Code
Over the past couple of months I have received a few enquiries about the ‘license’ under which the code of these articles is being released. It is pretty simple: the code is free, do with it as you wish - if anything goes wrong, don’t blame me! If anyone wants to credit me, that would be nice (but not obligatory). If anyone wants to send me examples of the games they make, that would be nice too (but also not obligatory). In short, please take the code, use it, modify it, do whatever you want with it - but, most of all, have fun!

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 3
    29 December 2007, by she

    I think your ruby code could be a little cleaned up :-)

    But it’s a question of style of course ... personally I prefer the really on one line.

    I argue that one line is better insofar that, IF you have to debug anyway, ruby is so short that you can quickly find the error, no matter if you use 3 lines, or one line in this example.

    • Adventures In Ruby, Part 3
      29 December 2007, by Huw Collingbourne

      I’m not sure which bit of code you are referring to. Agreed, it’s all a matter of style (I’ve had arguments about where the opening brackets should go in C# too!) but my own preference is for clarity above all else so I tend not to squeeze things onto a single line when that can be avoided.

      The debugging issue (while this isn’t the sole reason for my stylistic preference) is an interesting one. I am one of the developers of the fast Cylon debugger for the Ruby in Steel IDE http://www.sapphiresteel.com/Debugging-Ruby-with-the-Cylon. One of the issues we discovered with Ruby code compressed into single lines is that when you step through the code, the debugger has to stay on a single line when evaluating multiple expressions. That’s not always too helpful ;-)

      As for Ruby programs being short, well, that’s not always true, of course. As you’ll see if you use Cylon to debug your way through a Rails application! :-)

      best wishes

      Huw


Home