Search code examples
rubyrecursiontic-tac-toeminimax

Anyone fancy a challenge? minimax tic-tac-toe


I am stuck on implementing the minimax algorithm for a tic tac toe game. I keep getting the same error every time (undefined method `<' for nil:NilClass (NoMethodError)) but me and various people I have now asked, can't seem to fix it.

My minimax implementation is as follows:

def minimax(current_board, current_player)
  if user_won?(current_board)
    return -10
  elsif computer_won?(current_board)
    return 10
  elsif tie?(current_board)
    return 0
  end

  available_squares = empty_squares(current_board)
  moves = []
  score = nil

  available_squares.each do |available_square|
    move = {}
    current_board[available_square] = COMPUTER_MARKER if current_player == 'computer'
    current_board[available_square] = PLAYER_MARKER if current_player == 'player'
    score = minimax(current_board, alternate_player(current_player))
    current_board[available_square] = INITIAL_MARKER
    move[available_square] = score
    moves.push(move)
  end

  best_move = nil
  best_score = current_player == 'computer' ? -Float::INFINITY : Float:: INFINITY

  if current_player == 'computer'
    moves.each do |hsh|
      hsh.each do |move, score|
        if score > best_score
          best_move = move
          best_score = score
        end
      end
    end
  else
    moves.each do |hsh| 
      hsh.each do |move, score|
        if score < best_score
          best_move = move
          best_score = score
        end
      end
    end
  end
  best_move
end

When minimax is called recursively, it sometimes returns nil, which is then appended to the move hash, where move is then appended to the moves array. This causes the < operator to raise the said exception. I don't know why nil is being returned.

I can only assume my logic is flawed. Maybe my board is not being correctly reset after trying possible board states but honestly, I don't know how to proceed. I have tried multiple implementations and always get the same error.

I have been stuck on this for over a week and would really appreciate some help.


Solution

  • I think I have found the problem. In my if tie?(current_board) condition, tie? should take the argument available_squares instead. This stops an exception being raised.

    Also, as @Stefan pointed out (credit to him), putting a default value ensures the correct value is output and would be written as:

    score = minimax(current_board, alternate_player(current_player)) || 0
    

    This solves it.