Still learning by doing - connecting rooms in a game

Hi,

I am facing another problem with my next “project”.

I have classes for different rooms. In each one I defined a function
which basically performs everything in the room. The player moves
through the rooms.

Here are my problems:

  1. Connecting the rooms
    I can kick everything off in a separate file (the runner) where I create
    the first room and then pass the following room as the argument. I also
    create other rooms in this separate files. But in the second room
    everything crashes, because any of the rooms that could follow are
    basically unrecognized as the local variables.

The other option is to create the following room when needed, that is in
the existing room. However, that would pose the problem because the
newly created would again have limited existence to one room.

Of course there is also the issue of returning to a room which was
visited before.

I am doing this as a task in the book learning ruby the hard way by Zed.

Maybe I’ve made a mistake for trying to put everything in the room
classes and not just the room descriptions.

Anyway, I maintain the code here:
https://code.launchpad.net/~sebastjan-hribar/zombies/trunk

I would appreciate any pointers. I am stuck. I still have a lot of work
to do with various descriptions and particular activities in rooms, but
the main problem is this room connection.

regards
seba

On 21/09/2012, at 5:11 AM, “Sebastjan H.” [email protected] wrote:

I am doing this as a task in the book learning ruby the hard way by Zed.

This book is considered harmful.

Maybe I’ve made a mistake for trying to put everything in the room
classes and not just the room descriptions.

Anyway, I maintain the code here:
https://code.launchpad.net/~sebastjan-hribar/zombies/trunk

I would appreciate any pointers. I am stuck. I still have a lot of work
to do with various descriptions and particular activities in rooms, but
the main problem is this room connection.

I wouldn’t have written the code this way, but I don’t know if you are
following the book or not.

To start with I would have

One Room class that takes a name and description and a visited
attribute. Not a class for each room as you have done.

Create a Map structure that defines the room layout. Something like

[
{:room => :kitchen, :next_rooms => {:north => :living_room,
:south => :basement}},
{:room => : living_room, :next_rooms => {:north => :bathroom, :south
=> :kitchen}},

]

Henry

Henry M. wrote in post #1076876:

On 21/09/2012, at 5:11 AM, “Sebastjan H.” [email protected] wrote:

I am doing this as a task in the book learning ruby the hard way by Zed.

This book is considered harmful.

Why exactly?

Maybe I’ve made a mistake for trying to put everything in the room
classes and not just the room descriptions.

Anyway, I maintain the code here:
https://code.launchpad.net/~sebastjan-hribar/zombies/trunk

I would appreciate any pointers. I am stuck. I still have a lot of work
to do with various descriptions and particular activities in rooms, but
the main problem is this room connection.

I wouldn’t have written the code this way, but I don’t know if you are
following the book or not.

The book suggests a class per room.

To start with I would have

One Room class that takes a name and description and a visited
attribute. Not a class for each room as you have done.

Create a Map structure that defines the room layout. Something like

[
{:room => :kitchen, :next_rooms => {:north => :living_room,
:south => :basement}},
{:room => : living_room, :next_rooms => {:north => :bathroom, :south
=> :kitchen}},

]

Thank you for the idea of mapping the rooms in this way. I’ll try it.
regards,
seba

Henry

Could you be so kind as to suggest another book? I mean there are many
and it’s hard to pick the best one for a beginner.

For a while now the learn the hard way is in the process of rewriting
and maybe it’ll be differentiated from python version. I am using the
old version right now.

seba

On 21/09/2012, at 5:08 PM, “Sebastjan H.” [email protected] wrote:

Henry M. wrote in post #1076876:

On 21/09/2012, at 5:11 AM, “Sebastjan H.” [email protected] wrote:

I am doing this as a task in the book learning ruby the hard way by Zed.

This book is considered harmful.

Why exactly?

It teaches you to write Ruby as if it’s Python and this…

The book suggests a class per room.

There isn’t any reason to have a class per room because the only
difference is the data, not the behaviour.

Henry

Sebastjan H. wrote in post #1076909:

Could you be so kind as to suggest another book? I mean there are many
and it’s hard to pick the best one for a beginner.

For a while now the learn the hard way is in the process of rewriting
and maybe it’ll be differentiated from python version. I am using the
old version right now.

seba

I’ve just bought the pickaxe 1.9 and I’ll start there.

regards,
seba

Ok just a few points on the code that you did write.

Firstly as you have given both name() and state() methods you do not
need the line

attr_accessor :name, :state

It does nothing useful give that name() and state() are methods.
However if you removed the name() method then you could have

attr_reader :name

It’s a personal choice. I would personally go for the method but then
is no right way, just don’t do both.

Secondly, as I’m sure you will have noticed, each room has:

def initialize(name)
@name = name
end

def name()
@name
end

So what you need is

class Room
def initialize(name)
@name = name
end

def name()
@name
end
end

and then the class Bathroom would become

class Bathroom < Room
def state()
… All the stuff for state
end
end

That should help a little, I hope :slight_smile:

On Sep 21, 2012, at 6:09 PM 9/21/12, Henry M. [email protected]
wrote:

On 21/09/2012, at 10:01 PM, Sebastjan H. wrote:

I’ve just bought the pickaxe 1.9 and I’ll start there.

If you are going to learn ruby then you should have a copy of the PickAxe, but
the issues I pointed out weren’t specifically Ruby issues, more algorithm and
object oriented design issues. Unfortunately OOD books tend to be a bit dry. The
best way to become better at OOD is to look at good OO code and write lots of code
yourself.

Henry

I don’t want to disagree with Henry, but there are a couple of resources
for OO design that are quite useful. The first is the “Design PAtterns
in Ruby” book. It keeps the design patterns pretty short with concrete
implementations, so it’s a lot better than the classic gang of four
book.

Second is the current Clean Coders video series by Bob Martin. Each
episode is about 12 bucks, and they’re really pretty awesome. They kind
of build on each other, though, so you can end up spending a fair bit.

James

On 21/09/2012, at 10:01 PM, Sebastjan H. wrote:

I’ve just bought the pickaxe 1.9 and I’ll start there.

If you are going to learn ruby then you should have a copy of the
PickAxe, but the issues I pointed out weren’t specifically Ruby issues,
more algorithm and object oriented design issues. Unfortunately OOD
books tend to be a bit dry. The best way to become better at OOD is to
look at good OO code and write lots of code yourself.

Henry

On 22/09/2012, at 1:56 PM, Jams wrote:

I don’t want to disagree with Henry, but there are a couple of resources for OO
design that are quite useful. The first is the “Design PAtterns in Ruby” book. It
keeps the design patterns pretty short with concrete implementations, so it’s a
lot better than the classic gang of four book.

Second is the current Clean Coders video series by Bob Martin. Each episode is
about 12 bucks, and they’re really pretty awesome. They kind of build on each
other, though, so you can end up spending a fair bit.

I was going to suggest the refactoring book because it shows you how to
make your code ‘better’ and gives you reasons why it’s better. It’s a
bit advanced though.

Try http://sourcemaking.com It has a lot of good info about patterns and
refactoring your code to be more OO.

Relevant to you code, take a look at
Pull Up Method or
Extract Superclass

Henry

Thank you all for recommending the reading and all the suggestions and
advices. According to those I’ll try to re-factor the game like so:

  1. Have one class for Room and then have subclasses for specific rooms.
  2. Make a map of the rooms in one of the forms suggested above.

I still have difficulties imagining hot to resolve the interaction
between certain objects.

For example, I wanted to have the kitchen and the dinning room play out
like so:

    a) room description
    b) have the player decide whether or not they take some stuff
    c) second description, prologue to fight
    d) fight with a zombie
    e) exit the room

If I have all this in the room.state() I can’t reference the player
object since I get the undefined local variable or method for player.

So I assume I should put any decision making or fighting in the “runner”
file since the runner knows about all the objects and have the
room.state contain only the description?

If there is another way I can reference these objects? If I initialize
an object like player in the room itself it is again limited to the room
in question, right?

regards
seba

On Fri, Sep 21, 2012 at 12:02 AM, Henry M. [email protected]
wrote:

To start with I would have

One Room class that takes a name and description

Ack.

and a visited attribute.

Well, it depends what kind of attribute you are thinking of: if there
are more players then the attribute would at least have to be a
collection type. Conceptually visit is a relationship between room
and user - with potentially more properties (e.g. time of visit,
order).

Not a class for each room as you have done.

Ack.

Create a Map structure that defines the room layout. Something like

[
{:room => :kitchen, :next_rooms => {:north => :living_room,
:south => :basement}},
{:room => : living_room, :next_rooms => {:north => :bathroom, :south =>
:kitchen}},

]

I’d rather make the rooms reference their neighbors, maybe like this:

class Room
attr_accessor :name, :description

def neighbors
@neighbors || = {}
end

def connect(direction, other_room)
neighbors[direction] = other_room
other_room.neighbors[reverse_direction(direction)] = self
end

private
def reverse_direction(d)
case d
when :north then :south
when :south then :north
#…
else
raise ArgumentError, “Illegal direction: %p” % [d]
end
end

Kind regards

robert

On Mon, Sep 24, 2012 at 8:46 PM, Sebastjan H. [email protected]
wrote:

like so:

    a) room description
    b) have the player decide whether or not they take some stuff
    c) second description, prologue to fight
    d) fight with a zombie
    e) exit the room

It looks that there is a common algorithm for handling all rooms, so
this algorithm should be implemented only once, maybe in a parent
class of all rooms. It can delegate to the actual implementation of a
room the things related to data: does this room have items to pick?,
the descriptions, the monster and the connections to other rooms. Then
you run this algorithm from a specific room against the player that
enters the room. An example:

class Room

def player_enter player
puts first_description
if has_items? #maybe you could check the return of items instead of
having this method
puts “This is what you can find in the room: #{items}”
# perform the logic for the player to choose or not to pick
end
puts second_description
if has_monster?
fight player, monster
end

puts “Choose a door:”
connections.each {|c| print the connection }

end

And then a specific room just implements the required methods:

class DiningRoom < Room
def first_description
“a typical dining room”
end
def has_items?
true
end
def items
[:gun, :bullets]
end
def second_description
“you hear a strange sound coming from the dark corner”
end
def has_monster?
true
end
def monster
Zombie.new :hp => 100, :power => 5 #or however you instantiate
zombies
end
end

BTW, this pattern is called Template Method:

If I have all this in the room.state() I can’t reference the player
object since I get the undefined local variable or method for player.

I think the method that runs the algorithm could receive the player as
an argument.

So I assume I should put any decision making or fighting in the “runner”
file since the runner knows about all the objects and have the
room.state contain only the description?

Well, the algorithm to go through a room can very well be in other
object other than a parent of all rooms, but still the above applies:
implement in each room the specifics of the room, putting the
algorithm in a single place.

If there is another way I can reference these objects? If I initialize
an object like player in the room itself it is again limited to the room
in question, right?

I don’t think the Room is the right place to initialize the player
object. The player object probably belongs to a Game object or such.
What you can do is pass the appropiate objects where they need to be
used.

Jesus.

I’ve try to follow the above guidelines. I’ve read about the inheritance
and the templates as well. I’m going to use the templates and the
controlling object would be my runner.

So far I made the super class Room and modified the subclass
ChildBedroom.
It plays out ok and the room returns the value of the next room to be
explored. I still haven’t figured out how exactly I am going to do that
in the runner, but I haven’t played with that much yet.

I haven’t learned about the testing yet, so for now to test it, I am
just running the ChildBedroom.rb where I’ve put the childreen_room
instantiation at the end.

Can anyone have a look if I am on the right track with the relationships
between the two classes?

http://bazaar.launchpad.net/~sebastjan-hribar/zombies/trunk/revision/8?start_revid=8

regards
seba

On 25/09/2012, at 8:05 AM, Jess Gabriel y Galn wrote:

If there is another way I can reference these objects? If I initialize
an object like player in the room itself it is again limited to the room
in question, right?

I don’t think the Room is the right place to initialize the player
object. The player object probably belongs to a Game object or such.
What you can do is pass the appropiate objects where they need to be
used.

In games, and a lot of other types of program, there is usually a ‘god’
object or controller object that is tasked with ‘running’ the game.

This object initialises everything (builds the rooms from the map,
initialises the player object) and then enters the game loop.

In the inner game loop the GameController receives input and then
iterates through the game objects (rooms, player, etc) updating their
state.

I true MVC style the rooms and the player don’t need to know about each
other. All the interaction is done inside the GameController object.

Henry

Sebastjan H. wrote in post #1077994:

I’ve try to follow the above guidelines. I’ve read about the inheritance
and the templates as well. I’m going to use the templates and the
controlling object would be my runner.

So far I made the super class Room and modified the subclass
ChildBedroom.
It plays out ok and the room returns the value of the next room to be
explored. I still haven’t figured out how exactly I am going to do that
in the runner, but I haven’t played with that much yet.

I haven’t learned about the testing yet, so for now to test it, I am
just running the ChildBedroom.rb where I’ve put the childreen_room
instantiation at the end.

Can anyone have a look if I am on the right track with the relationships
between the two classes?

http://bazaar.launchpad.net/~sebastjan-hribar/zombies/trunk/revision/8?start_revid=8

I’ve also just added the amended runner.rb. I used the same logic as in
another learning game from the book learn the hard way. Now I get the
conversion error, because what is returned by the room.state can’t be
passed into the loop again dut to type error.

Sebastjan H. wrote in post #1078040:

Sebastjan H. wrote in post #1077994:

I’ve try to follow the above guidelines. I’ve read about the inheritance
and the templates as well. I’m going to use the templates and the
controlling object would be my runner.

So far I made the super class Room and modified the subclass
ChildBedroom.
It plays out ok and the room returns the value of the next room to be
explored. I still haven’t figured out how exactly I am going to do that
in the runner, but I haven’t played with that much yet.

I haven’t learned about the testing yet, so for now to test it, I am
just running the ChildBedroom.rb where I’ve put the childreen_room
instantiation at the end.

Can anyone have a look if I am on the right track with the relationships
between the two classes?

http://bazaar.launchpad.net/~sebastjan-hribar/zombies/trunk/revision/8?start_revid=8

I’ve also just added the amended runner.rb. I used the same logic as in
another learning game from the book learn the hard way. Now I get the
conversion error, because what is returned by the room.state can’t be
passed into the loop again dut to type error.

This is the last version of the code for the runner object:

ROOMS = {“side_hallway” => side_hallway, “child_bedroom” =>
child_bedroom}

def runner(map, start, zombie, player)
next_one = start

while true
room = ROOMS[next_one]
puts “\n-----------”
next_one = room.enter_room(zombie, player)
end
end

runner(ROOMS, “child_bedroom”, zombie, player)

The map isn’t complete yet, it’s just for the testing purposes:)

I think I have the basic mechanics in place so I can focus on the rest
of the content.

I now it’s probably not the optimum design, but at least I hope there
aren’t any major errors.

Any feedback is most welcome.
regards
seba

I have problems with pushing items to player’s satchel.

In the room template I have

  item = gets.chomp
  player.satchel.push(item) # or player.satchel << item

and the item doesn’t get pushed into the array.

I can puts the item itself or the existing satchel content I defined
manually, but the above doesn’t work.

And there is no error message like everything would work.

seba

Hi,

the item does get pushed into the array, but when you call
Player#satchel again, the variable @satchel is reset to [‘test’]. So the
previous array with the new item will be overwritten.

Check it:

player = Player.new ‘testname’
player.satchel << ‘testitem’
p player.instance_variable_get :@satchel
player.satchel # this resets @satchel to [‘test’]
p player.instance_variable_get :@satchel

So you have to change those methods. This isn’t good style, anyway,
because a getter method is supposed to do nothing but get or calculate a
certain value. It shouldn’t change the object (except maybe caching of a
calculated value). When you want to set default values, do it in the
initialize method.

Sebastjan H. wrote in post #1078242:

How should I change these methods? I am not looking for the easy way
out by you just telling me;) but I am puzzled and stuck yet again. Why
does it get reset by just calling it?

The method assigns a new array to @satchel. So each time you call it,
the instance variable @satchel is overwritten.

A solution (like I said) is to set the values in the initialize method
and not in the getters. This will also allow you to define hp, rifle
etc. through attr_reader.

However, there may actually be situations where it makes sense to set
default values on the fly isntead of ahead of time (like when the
calculation is very expensive). In this case you’d do something like
this:

def my_getter
@myvar ||= 123
end

The assignment only happens when @myvar isn’t nil (or false). So you
avoid resetting the variable. Note that this doesn’t work if nil or
false can be actual values. In this case you’d need to use the
“defined?” operator:

def my_getter
return @myvar if defined? @myvar
@myvar = 123
end

But in your case, simply use the initialize method. That’s what it’s
for.