I am learning OOP with Ruby. I have 2 classes - Game & Player. Player and Computer are to be created for Tick-Tack-Toe game. My idea is to use Player class to create a player and a computer for the game.
I believe Player class could have interaction with Game class hence Game class is initialised with @player
. Game class should be able to print out player_name
, however, it doesn't happen as I plan.
class Game
attr_accessor :player, :computer
@@board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
def initialize(player, computer)
@player = player
@computer = computer
puts "#{player.name} vs #{computer.name}"
end
def self.player_name
puts "#{player.name}" # line 14
end
def self.computer_name
puts "#{computer.name}"
end
def self.status
puts "#{@@board}"
end
end
class Player
attr_reader :name
def initialize(name)
@name = name
puts "#{name} is online."
end
end
I execute codes below:
p1 = Player.new('John')
p2 = Player.new('Computer')
Game.new(p1, p2)
Game.player_name # line 39
Game.computer_name
Got error message below. The error comes from line 39 and 14.
John is online.
Computer is online.
John vs Computer
test.rb:14:in `player_name': undefined local variable or method `player' for Game:Class (NameError)
puts "#{player.name}"
^^^^^^
Did you mean? player_name
from test.rb:39:in `<main>'
I am really confused where is my mistake.
I am really confused where is my mistake.
You're mixing instance variables / methods with class variables / methods. attr_accessor :player
defines an attribute backed by an instance variable @player
. In initialize
you set that instance variable. With def self.player_name
however you define a class method and the class doesn't know player
(only the instance does).
To fix this, turn your class methods to instance methods: (remove self.
)
class Game
attr_accessor :player, :computer
def initialize(player, computer)
@player = player
@computer = computer
end
def player_name
puts "#{player.name}"
end
def computer_name
puts "#{computer.name}"
end
end
And assign your game instance to a variable, just like you did with p1
and p2
, so you can call these instance methods:
game = Game.new(p1, p2)
game.player_name # prints: John
game.computer_name # prints: Computer
I've left out @@board
to keep the above example small. In general, you should avoid class variables because of their unusual behavior. (see Why is using a class variable in Ruby considered a 'code smell'?)
I would just assign it to another instance variable:
class Game
attr_accessor :player, :computer
def initialize(player, computer)
@board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# ...
end
end