Tic Tac Toe winner method in Board class not passing specs

Hello everyone, I’m back with another question :stuck_out_tongue_winking_eye:. I’m solving the Tic Tac Toe problem and I’m having troubling passing specs for the winner method. I tried running it in Repl.it and it worked so I don’t know why it’s not passing specs. The solution I have so far looks like this:

class Board
  attr_accessor :grid

  def initialize(grid = Array.new(3) {Array.new(3) })
    @grid = grid
  end

  def place_mark(pos, mark)
    row = pos[0]
    column = pos[1]

    @grid[row][column] = mark
  end

  def empty?(pos)
    row = pos[0]
    column = pos[1]

    if @grid[row][column] == nil
      return true
    else
      return false
    end
  end

  def winner
    @grid.each do |row|
      return :X if row.all?(:X)
      return :O if row.all?(:O)
    end

    (0..2).each do |column|
      marks = []
      @grid.each do |row|
        marks << row[column]
      end
      return :X if marks.all?(:X)
      return :O if marks.all?(:O)
      marks = []
    end

    if (@grid[0][0] == :X) && (@grid[1][1] == :X) && (@grid[2][2] == :X)
      return :X
    elsif (@grid[0][0] == :O) && (@grid[1][1] == :O) && (@grid[2][2] == :O)
      return :O
    elsif (@grid[0][2] == :X) && (@grid[1][1] == :X) && (@grid[2][0] == :X)
      return :X
    elsif (@grid[0][2] == :O) && (@grid[1][1] == :O) && (@grid[2][0] == :O)
      return :O
    end

    nil
  end

  def over?
    return true if winner || @grid.none? { |row| row.include?(nil) }
    false
  end
end

Hi Josh,

What is the actual error message and stack trace you are getting when you run the specs? If we take a look on the lines it mentions or compare expected vs got, we can figure out the error much faster than reading through the code!

Hi Matthew,

these are the error messages I receive when I run specs. Instead of giving expected vs got I get an ArgumentError which is confusing because the winner method doesn’t take in an argument.

When I give it an argument just to see what I get, I get an ArgumentError again:

Hi JChoi,

Your error comes from how you are using the method all?. all? expects a block but you gave it a symbol instead. So, you should write a block that will evaluate all the values in your row. Hope that helps!

Michael

1 Like

I can’t believe I couldn’t spot this for hours… Thank you so much Michael!

Hey Michael, do you think you can help me with some other specs I’m unable to pass? I have 4 that I can’t seem to figure out.

for ComputerPlayer class, I get these error messages and my code like like this

class ComputerPlayer
  attr_accessor :mark, :get_move, :name, :board

  def initialize(name)
    @name = name
  end

  def display(board)
    @board = board
  end

  def get_move
    moves = []

    board.each_with_index do |row, idx|
      row.each_with_index do |colum, idx2|
        moves << [idx, idx2] if board[idx][idx2].nil?
      end
    end

    moves.each do |move|
      return move if winning_move?(move)
    end
    moves.sample
  end

  def winning_move?(move)
    board[move[0]][move[1]] = mark
    if board.winner == mark
      board[move[0]][move[1]] = nil
      return true
    else
      board[move[0]][move[1]] = nil
      return false
    end
  end
end

I tried rewriting the method few times and read the spec file multiple times but I don’t seem to be able to get the code to run. The main issue seems to be the ‘each_with_index’ method, which I don’t understand why since it is a valid enumeralbe method.

The next error I have is for the HumanPlayer method. I made a very simple display method but it doesn’t seem to like it. The error message and my code look like this:

class HumanPlayer
  attr_accessor :mark, :get_move, :name, :board

  def initialize(name)
    @name = name
  end

  def display(board)
    print board
  end

  def get_move
    puts "Where would you like to place your mark? (row, col)"
    pos = gets.split(",").map! { |int| int.to_i }
  end
end

my last error message is for the Game class. I think it might just be because I didn’t name my players player1 & player2 but I’m not sure. The error message and my code look like this:

require_relative ‘board’
require_relative ‘human_player’
require_relative ‘computer_player’

class Game
  attr_accessor :current_player, :board

  def initialize(human_player, computer_player)
    @human_player = human_player
    @computer_players = computer_player
    human_player.mark = :O
    computer_player.mark = :X
    @board = Board.new
    @current_player = @human_player
  end

  def play_turn
    board.place_mark(current_player.get_move, current_player.mark)
    current_player.display(board)
    switch_players!
  end

  def switch_players!
    if @current_player == @human_player
      @current_player = @computer_player
    else
      @current_player = @human_player
    end
  end

  def play
    current_player.display(board)

    until board.over?
      play_turn
    end

    if board.winner == :O
      @human_player.display(board)
      puts "#{@huuman_player.name} wins!"
    elsif board.winner == :X
      puts "#{@computer_player.name} wins!"
    else
      puts "TIED"
    end
  end
end

Also I have some other questions about this exercise as well:

  1. Are @grid and board same things?
  2. In Game class, how can the play_turn method know where the current_player should get_move (computer_player or human_player) even though it’s not labeled?
  3. How do I use pry to test out the game? I’ve been doing

[1] pry(main)> load lib/game.rb '
=> true
[2] pry(main)> Game .new( " Josh " , " Computer " )

but I get this message:

NoMethodError: undefined method mark=' for "Josh":String from lib/game.rb:11:ininitialize’
[3] pry(main)>

I know this is a lot but I’ve been working on the same exercise for a long time and it was very difficult to grasp all the materials. I’d also appreciate it if you have any recommendation on where I could find study materials online to help me better understand related materials. Thank you!

Hey Josh,

Michael or I can get back to you in more detail on Monday, but this popped up and I didnt want you to be stuck on it all weekend.

You’re getting the same error for each of the specs that aren’t passing: each_with_index isn’t a method for board. Board is an object you’ve written, so it only has the methods you make for it plus methods that every object in ruby has. Board has an instance variable, @grid that stores data in an array, which does allow enumerable methods on it.

PS, for your last question, there’s alnost the same issue as before where you are trying to use a method on the string “Josh” that is defined for a different object. What object is game supposed to take as an argument?

Good luck and keep at it!

Hey Matthew,

thanks for always answering my questions. But I’m still super confused :sob: Please don’t answer them during the weekends though! I can wait 'till Monday!

I thought board was the same as @grid since it’s used almost the same way. It is treated like an array, having indices and being able to be modified with new marks. Also because in Game class, it’s initialized with @board = Board.new which, I thought, would create a new @grid with the default row/column (grid = Array.new(3) {Array.new(3) }). I don’t really understand why board can’t take array methods (each_with_index for an example) and what it means for an object to be written by me.

I did try to use grid instead of board in the ComputerPlayer class but I still get the same error as well.

For Game class, the initialize method takes in (human_player, computer_player)

  def initialize(human_player, computer_player)
    @human_player = human_player
    @computer_players = computer_player
    human_player.mark = :O
    computer_player.mark = :X
    @board = Board.new
    @current_player = @human_player
  end

so I thought using Game.new("Josh", "Computer").play in Pry would be appropriate since I’m giving names to the values that need to be initialized.

Thank you for reading. I know I’m misunderstanding a lot of concepts and that was probably really hard to read… haha :sweat_smile:

I understand how to run the game in pry now (Game.new(HumanPlayer.new(“Josh”), ComputerPlayer.new(“Computer”))) and know how to display the board in HumanPlayer class. Still not sure why my get_move method isn’t working in the ComputerPlayer class

Hey Josh!

In your player classes, board refers to @board. What is stored at @board? A reference to your board object! In our spec errors, we can confirm this by reading this text: #<Board:0x007fb...a8>. That tells you what the object is and also gives you a unique id for it in memory.

So the next thing to think about is, what is that object? It’s an instance of the class Board, which we wrote in another file and required at the top of our page. The error message undefined method 'each_with_index' gives us our most important clue. Board only has the methods that we defined ourselves in our file, plus a few that every Ruby object has such as inspect, object_id, etc. It also has some instance variables that we wrote to keep track of the data unique to each instance of a Board.

Which brings us to @grid, which is an array of arrays. Arrays are one of our data types that does have enumerable methods such as each on it, so that is what we need to iterate through by accessing board.grid.each_with_index, rather than board.each_with_index

1 Like

I think I’m getting it a little bit. I now get that I can’t use enumerable/other methods on objects that I’ve created (I’m guessing since objects made in classes I’ve created don’t have access to Array class?).

For the board object, in class Game, it’s defined as
@board = Board.new
and in class Board, initialize is defined as

  def initialize(grid = Array.new(3) { Array.new(3) })
    @grid = grid
  end

My understanding for this was that @board in class Game creates a new @grid since in class Board.new creates a new grid and @board equals to grid. But from reading what you wrote, I’m guessing @board equals to whole new class Board and not just a new grid. Is that correct?

I got it!! THANK YOU!!!

1 Like