0

I am trying to make a class Zar that would simulate me the rolling of 5 dices(For ruby koans tutorial). I am currently having trouble with accesing the array that I create. I am changing the values but somehow it is now showing. Furthermore I have 3 questions relevant to this question.

  1. When declaring an attribute field with attr_accessor :name how do you acces the new :name created in order to make an array could i just say :name = [0,0,0,0,0] in the initializer? .
  2. How do you access an attribute defined with @name
  3. What's the difference between :name and @name ?

I will post below 2 attempts at making Zar class work: First:

class Zar

 def initialize

  @score = 0
  @rolled = [0,0,0,0,0]
 end 

 def roll

   @rolled.each do |item|
    item = 1 + rand(6)
   end
 end

 def show_rolled

   @rolled.each do |item|
    print item 
   end 
 end

end 

#test
 z = Zar.new()
 z.roll
 z.show_rolled

second:

class Zar

 attr_accessor :rolled
 attr_accessor :score

 def initialize
  @score = 0
  @rolled = [0,0,0,0,0]
 end 

 def roll
 @rolled.each { |item|
    item = 1 + rand(6)
 }
 end
end 

#test
 z = Zar.new()
 z.roll

 print z.rolled
blelump
  • 3,233
  • 1
  • 16
  • 20
Lucian Tarna
  • 1,806
  • 4
  • 25
  • 48

3 Answers3

3

OK, think about

attr_accessor :name

As a shortcut to declare ruby class setter & getter for an instance variable called @name, quick and easy way of writing:

def class Zar
  def name
    @name
  end

  def name=(value)
    @name = value
  end
end

To answer your questions:

  1. You initialize an object instance variable just like you did, through initilize method
  2. You access an instance variable of on object with . z.name for example
  3. :name is how we tell ruby we need setter/ getter/ or both for an instance variable called @name

So, what's wrong with your code, you already seemed to apply all these in the second attempt?

Well, tiny problem in your roll method, you looped through the initilized array but never told the method to change the object @rolled array.

Here is aworking attempt:

class Zar
 attr_accessor :rolled, :score

 def initialize
  @score = 0
  @rolled = [0,0,0,0,0]
 end 

 def roll
  @rolled.map! { |item| item = 1 + rand(6)  }
  # map with bang (!) is the same as: @rolled = @rolled.map { |item| item = 1 + rand(6)  }
 end
end 

#test
 z = Zar.new()
 z.roll

 print z.rolled
Nimir
  • 5,727
  • 1
  • 26
  • 34
  • ah thank you , i tried with map, collect , each and so on . The thing i had not found is that to change the items of an array you need to use "!" :P – Lucian Tarna Nov 02 '14 at 13:18
  • Lucian, `map!` is best, but `arr = arr.map {...}` also works. You may have use of the latter form for some methods that do not have a bang counterpart (e.g., `arr = [1,2,3]; arr = arr.minmax => [1,3]`. – Cary Swoveland Nov 02 '14 at 17:27
2
  1. When declaring an attribute field with: attr_accessor :name how do you acces the new :name created in order to make an array could i just say :name = [0,0,0,0,0] in the initializer? .

You don't. You access @name internally, and name externally. :name is a symbol, it's not the name of the variable.

  1. How do you access an attribute defined with @name

Internally with @name. You cannot access it outside the class without defining accessors

  1. What's the difference between :name and @name ?

Everything. One is an instance variable, the other is a symbol. They're completely different things.


Ok. You're quite confused here.

In Ruby, @name is an instance variable on the current class. You can read and write to it while you're in the context of that class (inside one of its methods) but you cannot read or write to it from outside that class.

To do so, you need to define accessor methods. Typically the accessor methods have the same name as the @variable, but they do not have the @ prefix, because they're methods not instance variables.

So, if I had a variable @name on my class, I would give external code access to reading it with a "getter" method:

def name
  @name
end

Now, people can use some_instance_of_my_class.name to get access to @name. Internally as well, you can now access the name method to read @name, and this is often a good idea.

If I wanted to let people have write access, I'd define a setter, which in Ruby has a special syntax:

def name=(value)
  @name = value
end

Now, people can use some_instance_of_My_class.name = "new name" to write to @name. Internally you can write to name with name=, but you must prefix it with self.name= or Ruby will assume you're defining a new local variable called name.

So, attr_accessor. This is just a method, like any other method, except that it does a little bit of meta-programming for you. When you use attr_accessor :name or attr_accessor 'name', you're simply invoking a method with an argument, and the argument is the name of the method to define. It will create the above def name and def name= methods for you, with the same method bodies, wrapping around an @name variable.

If you'd written attr_accessor :bob, it would give you:

def bob
  @bob
end

def bob=(value)
  @bob = value
end

On to your specific problems:

@rolled.each do |item|
 item = 1 + rand(6)
end

This won't work, because item is passed by value to the block. The item variable inside the loop is it's own variable, and modifying it through assignment doesn't affect the values stored in @rolled. To change those values, you need to assign to @rolled directly.

You can do this easily with map, which overwrites the entire array with a new array:

@rolled = (1..6).map do |roll|
  rand(1..6)
end

You can do this for individual items with an each_with_index, though there isn't any value to doing this when the entire array is filled with 0 anyways:

@rolled.each_with_index do |item,index|
  @rolled[index] = rand(1..6)
end
user229044
  • 232,980
  • 40
  • 330
  • 338
1

About attr_accessor, checkout this answer :What is attr_accessor in Ruby?

tldr, attr_accessor creates for you a get and set method using the passed symbol as method name. It also creates a backing instance variable with the corresponding name.

class Zar
    attr_accessor :score
    attr_accessor :rolled

    def intialize
      self.score = 0 #you could use @score too
      self.rolled = [0,0,0,0,0] #you could use @rolled too
    end
end

About your implementation of the rolled method: When you are iterating with each, your block will get called for every item in the array, and the item will be passed as param. In your case, the variable named 'item' contains the reference to the item in the array.

But then you assign a new value (1 + rand(6)) to the 'item' variable, it doesn't change the value in the array.

There are a bunch of ways to accomplish what you want in ruby, for instance:

def roll
    self.rolled = 6.times.map do
        rand(6)
    end
end

Enumerable is a very important module in Ruby, I would advice you to read the docs a few times.

http://ruby-doc.org/core-2.1.4/Enumerable.html

Community
  • 1
  • 1
Mark Meeus
  • 597
  • 4
  • 11