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 2012-09-20 19:11
on 2012-09-21 00:03
On 21/09/2012, at 5:11 AM, "Sebastjan H." <lists@ruby-forum.com> 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
on 2012-09-21 07:08
Henry Maddocks wrote in post #1076876: > On 21/09/2012, at 5:11 AM, "Sebastjan H." <lists@ruby-forum.com> 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
on 2012-09-21 07:17
On 21/09/2012, at 5:08 PM, "Sebastjan H." <lists@ruby-forum.com> wrote: > Henry Maddocks wrote in post #1076876: >> On 21/09/2012, at 5:11 AM, "Sebastjan H." <lists@ruby-forum.com> 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
on 2012-09-21 07:31
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 2012-09-21 12:01
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
on 2012-09-21 16:39
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 :)
on 2012-09-22 02:10
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 2012-09-22 03:57
On Sep 21, 2012, at 6:09 PM 9/21/12, Henry Maddocks <hmaddocks@me.com> 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 2012-09-22 04:19
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 http://sourcemaking.com/refactoring/pull-up-method or http://sourcemaking.com/refactoring/extract-superclass Henry
on 2012-09-24 13:08
On Fri, Sep 21, 2012 at 12:02 AM, Henry Maddocks <hmaddocks@me.com> 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 2012-09-24 20:46
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 2012-09-24 22:06
On Mon, Sep 24, 2012 at 8:46 PM, Sebastjan H. <lists@ruby-forum.com> 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: http://en.wikipedia.org/wiki/Template_method_pattern > 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.
on 2012-09-25 00:11
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
on 2012-09-29 13:15
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/zomb... regards seba
on 2012-09-29 20:48
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/zomb... > 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.
on 2012-09-30 10:10
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/zomb... >> > 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
on 2012-10-01 22:05
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
on 2012-10-01 22:34
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.
on 2012-10-01 22:46
Jan E. wrote in post #1078238: > 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. >> I didn't want to set a default value, the "test" in the satchel was >> just my primitive way of testing whether the room and the player >> communicate. And yes, I see now that it gets reset. >> 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? >> I was dealing with instance variable modification in my last game where >> the player hp got modified constantly. I can't see the difference. I >> guess it is back to books... seba
on 2012-10-02 00:18
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.
on 2012-10-02 18:57
Peter Hickman wrote in post #1076973: > 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 I tried this and I removed the attr_accessor line and had the methods defined, but then the code doesn't work: class Zombie def initialize(name, hp, paces, target) @name = name @hp = hp @paces = paces @target = target end def name() @name end def hp() @hp end def paces() @paces end def target() @target end end However, the other way around works: having the attr_accessor line and the initialize method in place w/o specific methods: class Zombie attr_accessor :name, :hp, :paces, :target def initialize(name, hp, paces, target) @name = name @hp = hp @paces = paces @target = target end end What am I doing wrong now?
on 2012-10-02 19:34
Sebastjan H. wrote in post #1078385: > I tried this and I removed the attr_accessor line and had the methods > defined, but then the code doesn't work: What do you mean by "doesn't work"? The class does exactly what it should: zombie = Zombie.new 'some_name', 'some_hp', 'some_paces', 'some_target' p zombie puts zombie.name Note that you do *not* have writer methods, so you cannot do something like zombie.name = 'newname' Anyway, the right way to do this is to use attr_accessor.
on 2012-10-02 19:56
Jan E. wrote in post #1078392: > Sebastjan H. wrote in post #1078385: >> I tried this and I removed the attr_accessor line and had the methods >> defined, but then the code doesn't work: > > What do you mean by "doesn't work"? The class does exactly what it > should: > > zombie = Zombie.new 'some_name', 'some_hp', 'some_paces', 'some_target' > p zombie > puts zombie.name > > Note that you do *not* have writer methods, so you cannot do something > like > > zombie.name = 'newname' > > Anyway, the right way to do this is to use attr_accessor. That's what I meant. I guess it wasn't clear from my initial code that I need writer, that is accessor. Sorry if I confused anyone. It's just that I didn't want any duplication as Peter suggested, but then If I understand correctly I need either the writer or accessor to be able to change the values of instance variables. What I meant was, that if omit the attr_accessor completely and only have methods for instance variables defined, then player.hp -=20 does nothing. regards seba
on 2012-10-02 20:12
Sebastjan H. wrote in post #1078397: > Sorry if I confused anyone. It's just that I didn't want any duplication > as Peter suggested, but then If I understand correctly I need either the > writer or accessor to be able to change the values of instance > variables. No, you only need to define setter methods (i. e. methods ending with a "="). class A def initialize @x = 4 end # getter for @x def x @x end # setter for @x def x=(value) @x = value end end a = A.new puts a.x # call getter a.x = 3 # call setter; the same as a.x=(3) puts a.x That's exactly what attr_accessor does: It defines getter and setter methods for the given variables. You could actually define it yourself: class Module def my_attr_accessor *vars vars.each do |var| # getter define_method(var) {instance_variable_get "@#{var}"} # setter define_method("#{var}=") {|val| instance_variable_set "@#{var}", val} end end end class A my_attr_accessor :x, :y def initialize @x = 2 end end a = A.new puts a.x a.x = 12 puts a.x > What I meant was, that if omit the attr_accessor completely and only > have methods for instance variables defined, then > > player.hp -=20 does nothing. You should actually get an error saying that player doesn't have a "hp=" method.
on 2012-10-02 21:05
thank you for this explanation. > >> What I meant was, that if omit the attr_accessor completely and only >> have methods for instance variables defined, then >> >> player.hp -=20 does nothing. > > You should actually get an error saying that player doesn't have a "hp=" > method. actually, I did get that error:)
on 2012-10-02 22:20
Sebastjan H. wrote in post #1078412:
> actually, I did get that error:)
OK, then you should have told us the exact error message.
We don't know what you're doing on your computer, what you've expected
to get and what you actually got. So we need specific information.
Something like:
I tried to set the attribute "name" with
zombie.name = 'somename'
and got a NoMethodError saying "undefined method `name=' for
#<Zombie:0x1d63f28>".
Then we know what's going on and can help fixing the error. But when you
say that the code "doesn't work", it tells us pretty much nothing. This
could mean anything from a crashed Ruby interpreter to a tiny encoding
problem. It's even more difficult when the code *does* work and just
doesn't meet your expections (that we don't know).
The error message is actually the most important part in debugging,
because it tells you exactly *what* went wrong. In this case it's saying
that your object doesn't have a method "name=". And that's all you need
to know. Even if you knew nothing about Ruby and didn't have any access
to other information, you could still fix the error just by doing what
the message is telling you (which, of course, isn't really a good
approach).
on 2012-10-03 06:13
Jan E. wrote in post #1078421: > Sebastjan H. wrote in post #1078412: >> actually, I did get that error:) > > OK, then you should have told us the exact error message. > You're right, I should have put the exact error. It was an unintentional mistake on my behalf. Thank you for your help.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.