You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何基于Minimax的Python井字棋AI会覆盖玩家落子?

井字棋AI覆盖玩家落子问题排查与修复建议

问题概述

开发基于Python的人机对战井字棋游戏,采用Minimax算法实现AI玩家,但游戏进行1-2步后,AI会尝试覆盖玩家的落子,无法正常进行游戏直至分出胜负。

核心错误分析

1. 玩家落子未同步到游戏状态板

主程序clickbutton函数中,错误地将按钮对象替换为字符串"X",且未更新board数组记录玩家的落子位置:

def clickbutton(r, c):
    buttons[r][c]["text"]="X"
    buttons[r][c]="X"  # 破坏按钮对象,且未同步更新board
    computerplay()

AI的Minimax算法依赖board数组判断空位,玩家落子后board未更新,导致AI认为该位置仍为空,从而覆盖玩家落子。

2. 空位判断函数逻辑完全颠倒

gametree模块中的isMovesLeft函数逻辑错误,当前代码只要存在非空位置就返回True,正确逻辑应为存在空位(值为0)时返回True:

# 错误逻辑
def isMovesLeft(board) :
    for i in range(3) :
        for j in range(3) :
            if not(board[i][j]==0):
                return True
    return False

此错误导致Minimax算法提前判定无空位,AI无法继续计算后续步骤。

3. HTML转义符号未修正

findBestMove函数中的比较符号>是HTML转义字符,需替换为Python原生的>,否则会引发语法错误:

if (moveVal > bestVal) :  # 错误写法

修复方案

1. 修正玩家落子处理逻辑

更新clickbutton函数,同步更新board数组,并禁用已点击的按钮防止重复操作:

def clickbutton(r, c):
    if board[r][c] == 0:  # 确保位置为空才允许落子
        buttons[r][c]["text"] = "X"
        board[r][c] = "X"
        buttons[r][c].config(state=DISABLED)  # 禁用按钮
        if not is_game_over(board):  # 检查游戏是否结束
            computerplay()

2. 修正空位判断函数

调整isMovesLeft的判断逻辑:

def isMovesLeft(board) :
    for i in range(3) :
        for j in range(3) :
            if board[i][j] == 0:
                return True
    return False

3. 替换HTML转义符号

将findBestMove中的>替换为>:

if (moveVal > bestVal) :

4. 增加游戏结束判断

添加函数判断游戏是否结束(胜负或平局),避免游戏结束后继续落子:

# 主程序中添加
def is_game_over(board):
    # 调用gametree的evaluate函数判断胜负
    score = gametree.evaluate(board)
    if score == 10 or score == -10:
        return True
    # 判断是否平局
    return not gametree.isMovesLeft(board)

完整修正代码

主程序代码

from tkinter import *
import customtkinter
import gametree

customtkinter.set_appearance_mode("Dark")
root = customtkinter.CTk()
root.geometry('500x300')

# 创建标题标签
label = customtkinter.CTkLabel(master=root,
                               text="Tic Tac Toe",
                               width=120,
                               height=50,
                               font=("normal", 20),
                               corner_radius=8)
label.place(relx=0.25, rely=0.8, anchor=CENTER)

# 游戏状态板与按钮矩阵
buttons = [[0,0,0], [0,0,0], [0,0,0]]
board = [[0,0,0], [0,0,0], [0,0,0]]

# 判断游戏是否结束
def is_game_over(board):
    score = gametree.evaluate(board)
    if score == 10 or score == -10:
        return True
    return not gametree.isMovesLeft(board)

# 玩家点击处理
def clickbutton(r, c):
    if board[r][c] == 0 and not is_game_over(board):
        buttons[r][c]["text"] = "X"
        board[r][c] = "X"
        buttons[r][c].config(state=DISABLED)
        if not is_game_over(board):
            computerplay()

# 创建按钮网格
for i in range(3):
    for j in range(3):                                 
        buttons[i][j] = Button(height=3, width=6, font=("Normal", 20),
                               command=lambda r=i, c=j: clickbutton(r,c))
        buttons[i][j].grid(row=i, column=j)

# 创建副标题标签
label = customtkinter.CTkLabel(master=root,
                               text="Player vs. Computer",
                               width=120,
                               height=25,
                               corner_radius=8)
label.place(relx=0.25, rely=0.9, anchor=CENTER)

# AI落子处理
def computerplay():
    bestmove = gametree.findBestMove(board)
    if bestmove != (-1, -1):
        buttons[bestmove[0]][bestmove[1]]['text'] = "O"
        board[bestmove[0]][bestmove[1]] = "O"
        buttons[bestmove[0]][bestmove[1]].config(state=DISABLED)

root.mainloop()

修正后的gametree模块代码

# Python3 program to find the next optimal move for a player
player, opponent = 'O', 'X'

# 判断是否还有空位
def isMovesLeft(board):
    for i in range(3):
        for j in range(3):
            if board[i][j] == 0:
                return True
    return False

# 胜负评估函数
def evaluate(b):
    # 检查行
    for row in range(3):
        if b[row][0] == b[row][1] == b[row][2]:
            if b[row][0] == player:
                return 10
            elif b[row][0] == opponent:
                return -10
    # 检查列
    for col in range(3):
        if b[0][col] == b[1][col] == b[2][col]:
            if b[0][col] == player:
                return 10
            elif b[0][col] == opponent:
                return -10
    # 检查对角线
    if b[0][0] == b[1][1] == b[2][2]:
        if b[0][0] == player:
            return 10
        elif b[0][0] == opponent:
            return -10
    if b[0][2] == b[1][1] == b[2][0]:
        if b[0][2] == player:
            return 10
        elif b[0][2] == opponent:
            return -10
    # 平局或未分胜负
    return 0

# Minimax算法实现
def minimax(board, depth, isMax):
    score = evaluate(board)

    if score == 10:
        return score
    if score == -10:
        return score
    if not isMovesLeft(board):
        return 0

    if isMax:
        best = -1000
        for i in range(3):
            for j in range(3):
                if board[i][j] == 0:
                    board[i][j] = player
                    best = max(best, minimax(board, depth+1, not isMax))
                    board[i][j] = 0
        return best
    else:
        best = 1000
        for i in range(3):
            for j in range(3):
                if board[i][j] == 0:
                    board[i][j] = opponent
                    best = min(best, minimax(board, depth+1, not isMax))
                    board[i][j] = 0
        return best

# 寻找最优落子位置
def findBestMove(board):
    bestVal = -1000
    bestMove = (-1, -1)

    for i in range(3):
        for j in range(3):
            if board[i][j] == 0:
                board[i][j] = player
                moveVal = minimax(board, 0, False)
                board[i][j] = 0
                if moveVal > bestVal:
                    bestMove = (i, j)
                    bestVal = moveVal
    return bestMove

验证说明

修复后,玩家落子会同步更新board数组,AI会基于正确的游戏状态计算最优落子;空位判断逻辑修正后,Minimax算法能正常遍历所有可能步骤;游戏结束判断会阻止后续无效落子,确保游戏正常进行至分出胜负或平局。

内容的提问来源于stack exchange,提问作者Blythe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 09:25:44