Reputation: 1
I finished the pecs of tic tac toe, and was working on improving my computer player. This necessitates creating copies of new board objects from old board objects. I am having trouble creating deep copies of a board.
Here is the code in question:
Class Board
attr_accessor :grid
def initialize(grid = Array.new(3){ Array.new(3)})
@grid = grid
end
def place_mark(cords, sym)
self[cords] = sym
@grid
end
def [](pos)
row, col = pos
@grid[row][col]
end
def []=(pos, mark)
row, col = pos
@grid[row][col] = mark
end
def new_state
grid = @grid.dup
Board.new(grid)
end
end
board = Board.new
new_state = board.new_state # ==> A different object
new_state.place_mark([0,0], :X) # ==> Object with x placed at 0,0
board # ==> Object with x placed at 0,0
Now, when I implement a new_state and then place a mark on the new_state it also places a mark on the state that it was duplicated from.
I understand why if i set up my new_state with just duplicating the object it wouldn't work.But I don't understand why my current implementation doesn't work. I should be storing the grid of the current object, and then creating a new object with that same grid. Any thoughts?
Upvotes: 0
Views: 878
Reputation: 11050
In Ruby, dup
produces a shallow clone and doesn't copy the objects they reference. Since @grid
is an array of arrays, each array in the @grid
array is not copied. That may not have been clear, so hopefully code helps:
grid = [ [:first_row], [:second_row] ]
copy = grid.dup
grid.object_id == copy.object_id # => false, so you have a different array
grid[0].object_id == copy[0].object_id # => true, so the inner array is the same
copy[0][0] = :test_change
grid # => [[:test_change], [:second_row]]
copy # => [[:test_change], [:second_row]]
so the inner arrays are the same object, and modifying it from one place modifies everything referencing it. But, if you modify the 'outer' array, it works like you would expect:
copy[0] = [:updates_correctly]
copy # => [[:updates_correctly], [:second_row]]
grid # => [[:test_change], [:second_row]]
So, knowing all this, it's hopefully a little clearer how you would fix this, you just need to call dup
on the 'inner' arrays. We'll use collect
here as well, which returns a new array, so that we don't need to call dup
on the 'outer' array at all:
copy = grid.collect(&:dup) # => [[:test_change], [:second_row]]
copy[0][0] = :another_change
copy # => [[:another_change], [:second_row]]
grid # => [[:test_change], [:second_row]]
but the elements of those arrays still aren't different objects, if we instead had strings:
grid = [ ['first row'] ]
copy = grid.collect(&:dup)
and modified the string in place, we'd end up changing both:
copy[0][0].upcase!
copy # => [["FIRST ROW"]]
grid # => [["FIRST ROW"]]
how deep you need to go depends on your particular needs.
So, TL;DR you need to change new_state
to look like:
def new_state
grid = @grid.collect(&:dup)
Board.new(grid)
end
and it should work.
Upvotes: 1