Next.js中Minimax函数异常求助:AI落子仅按顺序选择
井字棋AI修复方案
核心问题分析
你的AI无法计算最优落子,主要是以下逻辑错误导致:
1. 胜负判断函数逻辑错误
checkWinner中的平局判断逻辑完全错误,会在列填满但未分出胜负时错误判定为平局,且会覆盖已找到的胜者;同时未正确检查所有格子是否填满来判定平局。
2. Minimax得分体系颠倒
当前得分设置scores = {X:10, O:-10}与AI角色(O)的目标矛盾:AI需要最大化自身得分,但当前设置AI获胜得负分、人类获胜得正分,导致AI主动选择最差走法。
3. 最佳落子初始化逻辑不严谨
findBestMove未处理初始move未赋值的边界情况,且依赖的useEffect触发条件冗余。
修复步骤
1. 修复checkWinner函数
先检查是否有胜者,无胜者时再通过判断所有格子是否填满来确认平局:
const checkWinner = (board: board) => { let winner = null; // 检查胜负组合 for (const combo of winCondition) { const [a, b, c] = combo; if (board[a] && board[a] === board[b] && board[a] === board[c]) { winner = board[a]; break; } } // 检查平局:无胜者且所有格子已填满 if (winner === null) { let isBoardFull = true; for (let i = 0; i < 9; i++) { if (board[i] === "") { isBoardFull = false; break; } } winner = isBoardFull ? "tie" : null; } return winner; };
2. 修正Minimax得分体系
调整得分让AI(O)获胜得正分,人类(X)获胜得负分,匹配AI的目标:
const scores = { X: -10, O: 10, tie: 0, };
3. 优化findBestMove与useEffect逻辑
- 给
move设置初始值并增加边界判断,避免无落子位置时出错 - 精简
useEffect依赖,只保留必要的触发条件:
// 优化后的useEffect useEffect(() => { if (!won && turn === "bot") { findBestMove(); } }, [boardData, turn, won]); // 修复后的findBestMove const findBestMove = () => { let bestScore = -Infinity; let move = -1; const board = { ...boardData }; for (let i = 0; i < 9; i++) { if (board[i] === "") { board[i] = ai; const score = minimax(board, false); board[i] = ""; if (score > bestScore) { bestScore = score; move = i; } } } if (move !== -1) { setBoardData(prev => ({ ...prev, [move]: ai })); setTries(prev => prev + 1); } setTurn("human"); };
完整修复后的代码
"use client"; import React, { useState, useEffect } from "react"; import "./GameMenu.css"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faRefresh } from "@fortawesome/free-solid-svg-icons/faRefresh"; import WonScreen from "./WonScreen"; import { Tooltip } from "react-tooltip"; const GameMenu = () => { type board = { [key: number]: string; }; const defaultBoard: board = { 0: "", 1: "", 2: "", 3: "", 4: "", 5: "", 6: "", 7: "", 8: "", }; const [turn, setTurn] = useState<"human" | "bot">("human"); const [won, setWon] = useState(false); const [boardData, setBoardData] = useState<board>(defaultBoard); const ai = "O"; const human = "X"; const [tries, setTries] = useState(0); const winCondition = [ [0, 1, 2], [0, 4, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [3, 4, 5], [2, 4, 6], [6, 7, 8], ]; const scores = { X: -10, O: 10, tie: 0, }; const updateBoardData = (idx: keyof board) => { if (won || boardData[idx] !== "") return; setTurn("bot"); setTries(prev => prev + 1); setBoardData({ ...boardData, [idx]: human }); }; useEffect(() => { if (!won && turn === "bot") { findBestMove(); } }, [boardData, turn, won]); const checkWinner = (board: board) => { let winner = null; // 检查胜负组合 for (const combo of winCondition) { const [a, b, c] = combo; if (board[a] && board[a] === board[b] && board[a] === board[c]) { winner = board[a]; break; } } // 检查平局 if (winner === null) { let isFull = true; for (let i = 0; i < 9; i++) { if (board[i] === "") { isFull = false; break; } } winner = isFull ? "tie" : null; } return winner; }; useEffect(() => { const win = checkWinner(boardData); if (win === human || win === ai) { setWon(true); } }, [boardData]); const restart = () => { setBoardData(defaultBoard); setWon(false); setTries(0); setTurn("human"); }; const minimax = (board: board, isMaximizing: boolean): number => { const result = checkWinner(board); if (result !== null) { return scores[result]; } let bestScore = isMaximizing ? -Infinity : Infinity; for (let i = 0; i < 9; i++) { if (board[i] === "") { board[i] = isMaximizing ? ai : human; const score = minimax(board, !isMaximizing); board[i] = ""; if (isMaximizing) { bestScore = Math.max(score, bestScore); } else { bestScore = Math.min(score, bestScore); } } } return bestScore; }; const findBestMove = () => { let bestScore = -Infinity; let move = -1; const board = { ...boardData }; for (let i = 0; i < 9; i++) { if (board[i] === "") { board[i] = ai; const score = minimax(board, false); board[i] = ""; if (score > bestScore) { bestScore = score; move = i; } } } if (move !== -1) { setBoardData(prev => ({ ...prev, [move]: ai })); setTries(prev => prev + 1); } setTurn("human"); }; // 补充棋盘渲染逻辑 return ( <div className="game-menu"> <div className="board"> {Object.keys(boardData).map((key) => ( <div key={key} className="cell" onClick={() => updateBoardData(Number(key))} > {boardData[Number(key)]} </div> ))} </div> <button onClick={restart} className="restart-btn"> <FontAwesomeIcon icon={faRefresh} /> 重启游戏 </button> {won && <WonScreen winner={checkWinner(boardData)} onRestart={restart} />} <Tooltip /> </div> ); }; export default GameMenu;
内容的提问来源于stack exchange,提问作者Animesh Acharya
相关产品推荐
相关产品推荐

