I am writing a simple Tic Tac Toe game, in which I have a class for the Board, the Player, the Computer, and the Game itself. In my Board class, I have set a class variable @board (which is an array) as attr_reader, which should disallow writing directly to it. Although the following will not work (as intended)
game_board = Board.new
game_board.board = "some junk"
The following does work, which I don't want to happen
game_board = Board.new
game_board.board[0] = "some junk"
How do I stop the class array variable @board from being written to? Current class code below:
class Board
attr_reader :board
def initialize
create_board
end
private
def create_board
@board = Array.new(3).map{Array.new(3)}
end
end
game_board = Board.new
game_board.board
#=> [[nil,nil,nil],[nil,nil,nil],[nil,nil,nil]]
game_board.board = "junk"
#=> undefined method 'board ='
game_board.board[0] = "junk"
game_board.board
#=> ["junk",[nil,nil,nil],[nil,nil,nil]] #I don't want to allow this!
I tried googling this, but to no avail, however I am a complete beginner, so I may not be using the correct search terms
I believe you need to make the array immutable.
You can use Array#freeze to achieve that.
Your code after that should look like :
class Board
attr_reader :board
def initialize
create_board
end
private
def create_board
@board = Array.new(3).map{Array.new(3).freeze}.freeze
end
end
On running your first example :
>> game_board = Board.new
#<Board:0x00000001648b50 @board=[[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]>
>> game_board.board = "some junk"
NoMethodError: undefined method `board=' for #<Board:0x00000001648b50>
from (irb):14
from /home/alfie/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'
On running your second example :
>> game_board = Board.new
#<Board:0x00000001639e48 @board=[[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]>
>> game_board.board[0] = "some junk"
RuntimeError: can't modify frozen Array
from (irb):16
from /home/alfie/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'
Defining attr_reader
only, without attr_writer
, will prevent assignments to the @board
variable only. In other words, your Board
class does not expose an interface to modify what's stored in @board
, but does nothing to prevent modifications of the initial value.
You could use freeze
:
def create_board
@board = Array.new(3) { Array.new(3).freeze }
@board.freeze
end
(also, you don't need map
there)
Freezing the top-level array and the nested ones will do what you describe, but I guess it will also break your game because modifications will be completely impossible.
What I'd suggest is to not expose @board
at all and consider it private. You should then expose an interface to set values in the board, and provide a method to return a readonly representation of the board.
class Board
def initialize
create_board
end
def []=(x, y, value)
@board[x][y] = value
end
def board
@board.map { |a| a.dup.freeze }.freeze
end
private
def create_board
@board = Array.new(3) { Array.new(3) }
end
end
b = Board.new
b.board
# => [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
b[1,2] = "x"
b[0,0] = "o"
b.board
# => [["o", nil, nil], [nil, nil, "x"], [nil, nil, nil]]
b.board[0] = "junk"
# RuntimeError: can't modify frozen Array
b.board[0][1] = "junk"
# RuntimeError: can't modify frozen Array
b.board
# => [["o", nil, nil], [nil, nil, "x"], [nil, nil, nil]]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With