Ruby OOP: ゲームオブジェクトの NameError の原因は何ですか?

概要

RubyでOOPを勉強しています。 Game と Player の 2 つのクラスがあります。 三目並べゲーム用にプレーヤーとコンピュータを作成します。私のアイデアは、Player クラスを使用して、ゲーム用のプレーヤーとコンピューターを作成することです。

Player クラスは Game クラスと対話できると思うので、Game クラスは @player で初期化されます。ゲームクラスは player_name を出力できるはずですが、計画どおりにはいきません。

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

以下のコードを実行します。

p1 = Player.new('John')
p2 = Player.new('Computer')
Game.new(p1, p2)
Game.player_name # line 39
Game.computer_name

以下のエラーメッセージが表示されました。エラーは 39 行目と 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>'

どこが間違いなのか本当に困っています。

解決策

インスタンス変数/メソッドとクラス変数/メソッドを混合しています。 attr_accessor :player は、インスタンス変数 @player によってサポートされる属性を定義します。初期化では、そのインスタンス変数を設定します。ただし、def self.player_name を使用すると、クラス メソッドを定義しますが、クラスはプレーヤーを認識しません (インスタンスのみが認識します)。

これを修正するには、クラス メソッドをインスタンス メソッドに変更します: (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

そして、p1 と p2 の場合と同様に、ゲーム インスタンスを変数に割り当てて、これらのインスタンス メソッドを呼び出すことができます。

game = Game.new(p1, p2)
game.player_name    # prints: John
game.computer_name  # prints: Computer

上記の例を小さくするために、@@board を省略しました。一般に、クラス変数は異常な動作をするため、避ける必要があります。 (「Ruby でのクラス変数の使用が「コードの匂い」とみなされるのはなぜですか?」を参照)

それを別のインスタンス変数に代入するだけです。

class Game
  attr_accessor :player, :computer

  def initialize(player, computer)
    @board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

    # ...
  end
end