From: Stefano Crocco Date: 2008-03-04T01:18:15+09:00 Subject: Re: Class and inheritance --Boundary-00=_QbCzH1xO4iXhVUU Content-Type: Multipart/Mixed; boundary="Boundary-00=_QbCzH1xO4iXhVUU" --Boundary-00=_QbCzH1xO4iXhVUU Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit Content-Disposition: inline On Monday 03 March 2008, August0866 wrote: > Hi All, > > I am not a programmer, yet. I am trying ruby on for size, most things > fit ok, but Classes are puzzling me. > > > class Roll > def Roll(base,range) > @base=base > @range=range > @roll = base+random(range) > end > end > > class Stats > def power > @power=Roll.new(10,8) > end > def speed > @speed=Roll.new(10,8) > end > def smarts > @smarts=Roll.new(10,8) > end > end > > class Attribs < Stats > def acc > @acc=0.75*self.power+(self.speed/self.smarts) > end > def health > @health = self.power*(self.smarts/self.speed) > end > end > class Npc < Attribs > def name(name) > @name=name > end > end > > joe=Npc.new > joe.name('Joe') > #puts joe.name.to_s+' = name' > puts joe.power.to_s+' = power' > puts joe.smarts.to_s+' = smarts' > puts joe.speed.to_s+' = speed' > puts joe.health.to_s+' = health' > puts joe.acc+' = acc' > puts joe > puts '=======================' > puts 'Calculations' > puts 0.75*joe.power+(joe.speed/joe.smarts) > > > OK, so why is this broken? There are many reasons your code doesn't work. Let's see what happens running it in ruby (by the way, when asking for help about a piece of code which produces an error, it's better to also show the error message): * FIRST TRY: ./prova.rb:13:in `initialize': wrong number of arguments (2 for 0) (ArgumentError) This means that some initialize method is called with 2 arguments, when it should receive no argument on line 11. The line in question is: @power = Roll.new(10, 8) Apparently, there's no call to initialize here. What you're trying to do in this line, is to create an instance of class Roll, calling Roll.new. For most classes, the 'new' method first creates a new object, then calls its 'initialize' method, which, as the name implies, is used to perform initialization on the object (for example, giving starting values to instance variables). Since you didn't define an initialize method for class Roll, it uses the one defined for the parent class, which is Object. The initialize method defined for class Object takes no argument, so you get an error. Looking at the definition of class Roll, i see you defined a method called Roll, which takes two arguments and sets instance variables, just like initialize should do. I guess you thought that method would have been called by new. Let's rename it to initialize and see if it solves the problem. * SECOND TRY: ./prova.rb:5:in `initialize': undefined method `random' for # (NoMethodError) This is easy: you're calling a method named 'random', which doesn't exist. The method which generates a random number in ruby is called rand, so we'll replace random with rand * THIRD TRY: ./prova.rb:26:in `health': undefined method `/' for # (NoMethodError) from ./prova.rb:41 Now, ruby hits an error while calling the health method: in particular, in line 26 there's a call to a method '/' which is undefined. Line 26 is: @health = self.power*(self.smarts/self.speed) In ruby, operators such as +, -, * and / are simply methods, so that when you write, for example 2 + 3, ruby calls the '+' method of the object 2 passing it the object 3 as argument. In your code, you're dividing self.smarts by self.speed, which are both of class Roll. Class Roll doesn't have a method called '/', so you get the error. This problem could be solved defining a sensible / method for class Roll, or, more simply, by defining a method which allows to get the value of the variable @roll of an instance of roll (which is a number), and use that instead of the instance of Roll itself. It may be worth asking whether class Roll really needs to exist. If you won't require the @base and @range values somewhere else, then it would be much easier to replace the whole class with a method such as this: def roll(base, range) base + rand(range) end At any rate, let's assume that Roll needs to be a class and add a method which allows to extract the @roll instance variable. We could define it by hand, but ruby does it automatically if we add this line inside class Roll: attr_reader :roll This generates a method called roll which returns @roll. Then, we need to replace speed and smarts and power with speed.roll, smarts.roll and power.roll in lines 24, 26 and 47 (we would get an analogous problem with the acc method if we leaved it as it is). * FOURTH TRY # = power # = smarts # = speed 0 = health ./prova.rb:43:in `+': String can't be coerced into Float (TypeError) from ./prova.rb:43 Before looking at the error, there are two things to notice: first, power, smarts and speed aren't numbers but instances of class Roll. If this isn't what you expected, then most likely you should replace class Roll with the method roll as I mentioned above. Second, health is 0 (of course, this will depend on the random number which have been generated). Since both smarts.roll and speed.roll are integers, the division will return 0 if the divider is greater than the divisor. To avoid this, add a call to to_f to one of the operands. to_f will convert the integer to a float, so the division will use decimals. Looking at the error, it simply means that, in line 43, we're trying to add a number with a string. The reason is simply that you forget to call the to_s method on joe.acc as you did for the other ones. * FIFTH TRY # = power # = smarts # = speed 10 = health 10.75 = acc # ======================= Calculations 11.5 Now, it works. Attached, you'll find a copy of the code with all the modifications I spoke about. I hope this helps Stefano --Boundary-00=_QbCzH1xO4iXhVUU Content-Type: application/x-ruby; name="code.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="code.rb" #!/usr/bin/env ruby class Roll attr_reader :roll def initialize(base,range) @base=base @range=range @roll = base+rand(range) end end class Stats def power @power=Roll.new(10,8) end def speed @speed=Roll.new(10,8) end def smarts @smarts=Roll.new(10,8) end end class Attribs < Stats def acc @acc=0.75*self.power.roll+(self.speed.roll.to_f / self.smarts.roll) end def health @health = self.power.roll*(self.smarts.roll.to_f / self.speed.roll) end end class Npc < Attribs def name(name) @name=name end end joe=Npc.new joe.name('Joe') #puts joe.name.to_s+' = name' puts joe.power.to_s+' = power' puts joe.smarts.to_s+' = smarts' puts joe.speed.to_s+' = speed' puts joe.health.to_s+' = health' puts joe.acc.to_s+' = acc' puts joe puts '=======================' puts 'Calculations' puts 0.75*joe.power.roll+(joe.speed.roll.to_f / joe.smarts.roll) --Boundary-00=_QbCzH1xO4iXhVUU-- --Boundary-00=_QbCzH1xO4iXhVUU--