0

For our assignment, we were meant to create tic-tac-toe on a board of any size and accept an array as the input. I still am not fully grasping the attr_accessor module, and I'm pretty sure I used it incorrectly.

class Board
  attr_accessor :grid

  def initialize(grid = nil)
    if grid.nil?
      @grid = Array.new(3, Array.new(3, nil))
    else
      @grid = grid
    end
  end

  def place_mark(pos, mark)
    @grid[pos[0]][pos[1]] = mark
  end

 end

My main problem is even though it seems 'place_mark' should be placing the input in just one position. I get the following:

game.place_mark([0,0], :X) #=> [[:X, nil, nil], [:X, nil, nil], [:X, nil, nil]]

When I recreated this outside of a class, it worked just as I thought it should. What did I mess up?

orde
  • 5,233
  • 6
  • 31
  • 33
  • Seeing that your class controls `@grid`, you could consider using `attr_reader` instead. – Ja͢ck Apr 29 '17 at 04:27
  • Possible duplicate of [In Ruby, why does Array.new(size, object) create an array consisting of multiple references to the same object?](http://stackoverflow.com/questions/4642395/in-ruby-why-does-array-newsize-object-create-an-array-consisting-of-multiple) – Ja͢ck Apr 29 '17 at 04:28

1 Answers1

1

There is nothing wrong with the Board class nor with attr_accessor. The initialization of the grid however does not work. @grid = Array.new(3, Array.new(3, nil)) is the culprit. The fourth code sample in the docs shows how it should be done, the text above it explains why OP's code behaves like it does. Every Ruby student gets trapped by this at least once.

steenslag
  • 79,051
  • 16
  • 138
  • 171
  • 1
    Oh, thanks for the help. Does Ruby do this to be efficient or are there useful tricks associated with creating nested arrays referencing the same object? – Sean Chowdhury Apr 29 '17 at 01:17
  • @SeanChowdhury It's harder than it sounds to do it the way you expected it to work - when you're not passing a block, the inner call to `Array.new` has already happened, it's *exactly* the same as `inner = Array.new(3,nil); @grid = Array.new(3,inner)`. So we don't have the option of executing that code more than once like we do with a block. So, if we wanted to get different objects in each spot, we'd have to duplicate the one passed in. Do we do a shallow copy or a deep copy? Either will sometimes be wrong. The non-block syntax is only provided as a convenience for immutable values like `2`. – philomory Apr 29 '17 at 04:03
  • To elaborate on the shallow vs deep issue, consider `inner = [Person.new('Jane'),Person.new('Bill'),Person.new('Sally'); outer = Array.new(3,inner)`. Would you want the end result to involve 9 people (some of which share the same name)? Three different lists that happen to contain the same people? Or the same list three times? The 'obvious' answer will be different depending on the example you use, and depending on who you ask. – philomory Apr 29 '17 at 04:08