2

Now that I've finally worked out how class inheritance works in ruby, I'm designing an inventory system for a text rpg. There are some quirks that I can't figure out, however.

Item class:

class Item
    attr_accessor :name, :type, :attack, :armor, :wearloc, :weight, :price
    def initialize(name, type, attack, armor, wearloc, weight, price)
        @name = name
        @type = type
        @attack = attack
        @armor = armor
        @wearloc = wearloc
        @weight = weight
        @price = price
    end
end

Inventory class:

class Inventory
    attr_accessor :items
    def initialize
        @@items = []
    end

    def add_item(item)
        @@items << item
    end

    attr_accessor :inventory
    def inventory
        @@items.each do |item|
            puts "#{item.name} (#{item.type})"
            if item.attack != nil ; puts " %-20s %00d" % ['Attack', item.attack] ; end
            if item.armor != nil ; puts " %-20s %00d" % ['Armor', item.armor] ; end
            if item.wearloc != nil ; puts " %-20s %00s" % ['Wear', item.wearloc] ; end
            if item.weight != nil ; puts " %-20s %00d" % ['Weight', item.weight] ; end
            if item.price != nil ; puts " %-20s %00d" % ['Price', item.price] ; end
        end
    end
end

Player class:

class Player < Inventory
    attr_accessor :playername
    def initialize(playername)
        @playername = playername
    end
end

Instantiating the inventory, creating a few items, adding them to the inventory all works.

inv = Inventory.new
broadsword = Item.new('a heavy broadsword', 'weapon', 5, nil, 'wield', 15, 2)
breastplate = Item.new('a mithril breastplate', 'armor', nil, 10, 'torso', 15, 5)
ring = Item.new('a gold ring', 'armor', nil, 3, 'finger', 2, 20)
inv.add_item(broadsword)
inv.add_item(breastplate)
inv.add_item(ring)

However, when I call the inventory method from the Player class, I get the desired output, plus the 3 #item objects like this:

a heavy broadsword (weapon)
 Attack               5
 Wear                 wield
 Weight               15
 Price                2
a mithril breastplate (armor)
 Armor                10
 Wear                 torso
 Weight               15
 Price                5
a gold ring (armor)
 Armor                3
 Wear                 finger
 Weight               2
 Price                20
#<Item:0x007fae1b8b11d8>
#<Item:0x007fae1b8b1138>
#<Item:0x007fae1b8b1098>

Why are those last three lines there? I can't figure out where it is coming from or how to fix it.

Update:

Reworked for Item < Inventory < Player inheritance with instance vars instead of class vars.

class Player
    attr_accessor :playername
    def initialize(playername)
        @playername = playername
    end
end

class Inventory < Player
    attr_accessor :items
    def initialize
        @items = []
    end

    def add_item(item)
        @items << item
    end

    attr_accessor :inventory
    def inventory
        @items.each do |item|
            puts "#{item.name} (#{item.type})"
            if item.attack != nil ; puts " %-20s %00d" % ['Attack', item.attack] ; end
            if item.armor != nil ; puts " %-20s %00d" % ['Armor', item.armor] ; end
            if item.wearloc != nil ; puts " %-20s %00s" % ['Wear', item.wearloc] ; end
            if item.weight != nil ; puts " %-20s %00d" % ['Weight', item.weight] ; end
            if item.price != nil ; puts " %-20s %00d" % ['Price', item.price] ; end
        end
        nil
    end
end

class Item < Inventory
    attr_accessor :name, :type, :attack, :armor, :wearloc, :weight, :price
    def initialize(name, type, attack, armor, wearloc, weight, price)
        @name = name
        @type = type
        @attack = attack
        @armor = armor
        @wearloc = wearloc
        @weight = weight
        @price = price
    end
end

inv = Inventory.new
broadsword = Item.new('a heavy broadsword', 'weapon', 5, nil, 'wield', 15, 2)
breastplate = Item.new('a mithril breastplate', 'armor', nil, 10, 'torso', 15, 5)
ring = Item.new('a gold ring', 'armor', nil, 3, 'finger', 2, 20)
inv.add_item(broadsword)
inv.add_item(breastplate)
inv.add_item(ring)

player = Player.new('Chris')
# puts player.inventory
puts inv.inventory
oorahduc
  • 185
  • 2
  • 16
  • 4
    If you use a `@@class_variable` in Ruby, you're going to have a bad time. – user513951 Jan 07 '16 at 01:10
  • 1
    You should make the `Item` class a child of `Inventory` – 13aal Jan 07 '16 at 01:16
  • 1
    [codereview.stackexchange.com](http://codereview.stackexchange.com/) is a good place to go to get feedback/suggested improvements on code. Definitely recommend it. – br3nt Jan 07 '16 at 01:19
  • 2
    Some references for my comment about not using class variables: [an ages-old O'Reilly article](http://archive.oreilly.com/pub/post/nubygems_dont_use_class_variab_1.html) and [this SO answer](http://stackoverflow.com/a/10614333/513951) (not the accepted answer) – user513951 Jan 07 '16 at 01:21
  • Thanks JesseSielaff & @Ekult3k. The way I was handling inheritance before, it wouldn't work as an instance variable, but class var worked. I've reworked it a little and updated it above. Now Item is a child of Inventory, which is a child of Player. Now, I'm a little confused though - shouldn't I be able to access the Inventory class methods via the Player class? – oorahduc Jan 07 '16 at 05:58
  • 2
    @Ekult3k: None of these should be inherited from each other. An item is not a kind of inventory, but is contained in inventory. An inventory is not a kind of player, it is carried by a player. Thus, there should be a `@inventory` inside `Player`, and a `@items` array inside `Inventory`, exposed via methods or accessors. Read about "inheritance and composition" (Google has a zillion links). – Amadan Jan 07 '16 at 07:05
  • @Amadan I'm not certain of the superiority of either Ekult3k's suggestion or yours, but as an exercise I removed all inheritance and instantiated Inventory inside Player and added all the items to the inventory that way via accessors. Worked like a charm. – oorahduc Jan 07 '16 at 07:36
  • 2
    @oorahduc: As I said, read on composition and inheritance - you don't have to trust my word. Inheritance is for "A is a kind of B" - Dog is a kind of Mammal, ScreenWindow is a kind of ScreenWidget... Composition (having an instance of another class in an instance variable) is for "A has B": Parent has an Array of Childs, Child has a mother Parent and a father Parent, Unicycle has a Wheel. – Amadan Jan 07 '16 at 07:52
  • That makes total sense. Thank you. – oorahduc Jan 07 '16 at 08:37
  • @Amadan no but an item is stored inside an inventory so the item should be inherited to the inventory, that's my opinion anyways. – 13aal Jan 07 '16 at 15:44
  • 3
    @Ekult3k: Your opinion is wrong. The whole point of inheritance is to share behaviours of related objects. If Animal can eat, or die, Mammal can eat or die, and so Dog can eat or die as well. If a ScreenWidget can hide itself, so can a ScreenWindow. If Item inherits from Inventory, then Item has all the behaviours of Inventory, E.g. if you have `Inventory#clear!` and `Inventory#drop_random_item!`, then you also have `Item#clear!` and `Item#drop_random_item!`, which makes no sense. – Amadan Jan 07 '16 at 15:50

1 Answers1

2

Methods in ruby return the value of the last statement in the method.

In the case of Inventory#inventory the last statement is the @@items.each method. each returns the collection that it was called upon, so the return value is the @@items array.

If you don't want the method to return anything, you can put a nil at the end:

def inventory
  @@items.each do |item|
    # code to print the item stats
  end

  nil
end
br3nt
  • 9,017
  • 3
  • 42
  • 63
  • Ruby is like that :p. It's a good language especially once you learn all the finer intricacies. – br3nt Jan 07 '16 at 01:10