C#实现Minimax算法井字棋游戏逻辑异常求助
C# 井字棋Minimax算法异常修复
问题表现
- 玩家在棋盘(1,1)坐标落子"O"后,AI未按预期在(2,1)位置落"X"阻挡玩家获胜,反而选择(1,0)位置落子,直接放任玩家取胜
- 调试抓取Minimax返回值发现所有结果均为0,无法区分落子优先级
核心错误点
- Minimax递归终止顺序错误
现有代码先遍历所有空位执行递归,最后才判断棋盘胜负状态。胜负已分的节点会被当作普通节点继续向下递归落子,导致胜负状态对应的得分完全被覆盖,最终所有返回值趋近于0。Minimax标准逻辑要求先判断当前节点是否为终止节点(获胜/平局),终止节点直接返回得分,非终止节点才遍历落子递归。
- 胜利线校验逻辑漏项
CheckWinner方法中仅校验了3条横线、1条竖线、2条对角线,缺失第一列、第三列的竖线胜利判定,无法正确识别玩家即将连成竖线获胜的场景。 - 得分深度折扣逻辑错误
现有代码根据当前回合方选择深度加减规则,实际深度折扣和回合方无关:AI获胜时得分=10-深度(越快赢得分越高),玩家获胜时得分=-10+深度(越晚输得分越高),平局固定返回0。原逻辑会导致落子优先级判断完全混乱。 - 缺失平局终止判断
没有判断棋盘填满无空位的平局场景,递归终止逻辑不完整。
修复后完整代码
// 全局变量定义(保持和原有逻辑一致) List<int> scores = new List<int>(); int[] index = new int[2]; public static void AIMove(int[,] board) { int bestScore = int.MinValue; scores.Clear(); for (int i = 0; i < board.GetLength(0); i++) { for (int j = 0; j < board.GetLength(1); j++) { if (board[i, j] == -1) { board[i, j] = 1; // AI落子后轮到玩家(turn=0),初始深度为0 var score = MiniMax(board, 0, 0); scores.Add(score); board[i, j] = -1; if (score > bestScore) { bestScore = score; index[0] = i; index[1] = j; } } } } board[index[0], index[1]] = 1; } public static int MiniMax(int[,] board, int turn, int depth) { // 第一步先判断终止状态:胜负已分直接返回折扣后得分 int winner = CheckWinner(board); if (winner != 0) { // AI获胜返回10-depth(优先选最快赢的路径),玩家获胜返回-10+depth(优先选最慢输的路径) return winner == 10 ? 10 - depth : -10 + depth; } // 判断平局:无空位且无胜者 bool isFull = true; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (board[i, j] == -1) { isFull = false; break; } } if (!isFull) break; } if (isFull) return 0; // 非终止节点遍历所有落子可能 int bestScore = turn == 1 ? int.MinValue : int.MaxValue; for (int i = 0; i < board.GetLength(0); i++) { for (int j = 0; j < board.GetLength(1); j++) { if (board[i, j] == -1) { board[i, j] = turn; var score = MiniMax(board, 1 - turn, depth + 1); board[i, j] = -1; // AI回合最大化得分,玩家回合最小化得分 bestScore = turn == 1 ? Math.Max(bestScore, score) : Math.Min(bestScore, score); } } } return bestScore; } public static int CheckWinner(int[,] board) { // 补全全部8条胜利线:3横+3竖+2对角线 int[,] winLines = new int[,] { {board[0,0], board[0,1], board[0,2]}, // 第一横 {board[1,0], board[1,1], board[1,2]}, // 第二横 {board[2,0], board[2,1], board[2,2]}, // 第三横 {board[0,0], board[1,0], board[2,0]}, // 第一竖 {board[0,1], board[1,1], board[2,1]}, // 第二竖 {board[0,2], board[1,2], board[2,2]}, // 第三竖 {board[0,0], board[1,1], board[2,2]}, // 主对角线 {board[0,2], board[1,1], board[2,0]} // 副对角线 }; for (int i = 0; i < 8; i++) { if (winLines[i,0] == 1 && winLines[i,1] ==1 && winLines[i,2] ==1) return 10; // AI获胜 if (winLines[i,0] ==0 && winLines[i,1] ==0 && winLines[i,2] ==0) return -10; // 玩家获胜 } return 0; // 无胜者 }
修复验证
修复后Minimax返回值会正确区分不同落子的得分:玩家在(1,1)落子后,AI选择(2,1)阻挡的得分会高于选择(1,0)的得分,会优先选择阻挡玩家的落子点,不会再出现放任玩家获胜的问题。
内容的提问来源于stack exchange,提问作者HeadEx
相关产品推荐
相关产品推荐

