实现井字棋Minimax算法遇报错:NilClass未定义`<`方法求助
undefined method '<' for nil:NilClass Error in Your Tic-Tac-Toe Minimax Implementation Let's break down why you're hitting this error and how to fix it step by step.
The Root Cause
Your error stems from two critical issues in your code:
1. Inconsistent Return Values in Minimax
Your minimax function has a conflicting return type:
- In terminal states (win/loss/tie), it returns a numerical score (
-10,10,0). - In non-terminal states, it returns a move position (like an integer representing a board square).
When you recursively call minimax and assign the result to score, you're sometimes getting a position instead of a score. Worse, if your tie? function is broken, the function can return nil (when the board is full but tie? doesn't trigger), leading to the NoMethodError when you try to compare nil < best_score.
2. Potential Bug in the tie? Function
If your tie? function doesn't properly check for a full board plus no winner, it won't trigger the terminal state return of 0. This leaves the function to proceed to the end, where best_move stays nil (since there are no available squares to evaluate), and the function returns nil.
Step-by-Step Fix
First: Correct the tie? Function
Make sure it accurately identifies a full board with no winner:
def tie?(board) empty_squares(board).empty? && !user_won?(board) && !computer_won?(board) end
Second: Split Minimax into Two Functions
To eliminate return value confusion, split the logic into two focused parts: one to calculate scores for recursive evaluation, and another to find the best move based on those scores.
1. Minimax Score Calculator (Recursive)
This function only returns numerical scores for internal recursive calls:
def minimax_score(current_board, current_player) # Terminal state checks 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) scores = [] available_squares.each do |square| # Simulate the current player's move current_board[square] = current_player == 'computer' ? COMPUTER_MARKER : PLAYER_MARKER # Recursively get the opponent's best possible score scores << minimax_score(current_board, alternate_player(current_player)) # Undo the move (backtracking) current_board[square] = INITIAL_MARKER end # Maximize score for computer, minimize for human player current_player == 'computer' ? scores.max : scores.min end
2. Best Move Finder
This function uses the score calculator to evaluate all possible moves and select the optimal one:
def find_best_move(current_board) best_score = -Float::INFINITY best_move = nil available_squares = empty_squares(current_board) available_squares.each do |square| # Simulate the computer making this move current_board[square] = COMPUTER_MARKER # Calculate the best score the human player can achieve after this move current_score = minimax_score(current_board, 'player') # Undo the move current_board[square] = INITIAL_MARKER # Update best move if this option yields a better score if current_score > best_score best_score = current_score best_move = square end end best_move end
Third: Update Your Code to Use find_best_move
Replace any calls to your original minimax function with find_best_move—this is the function you'll use to get the computer's next move.
Why This Works
- Consistent Return Values: The recursive
minimax_scorealways returns a number, so you'll never getnil(as long astie?is correct) when comparing scores. - Clear Separation of Concerns: Splitting the logic makes debugging and maintenance easier—you no longer have to juggle returning scores and moves in the same function.
内容的提问来源于stack exchange,提问作者testing09

