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

PyQt5开发国际象棋游戏出现GUI逐步卡顿问题求助

解决PyQt5国际象棋GUI逐步卡顿问题

核心问题原因

你的代码中重复连接按钮的点击信号是导致卡顿的根本原因:

  • 在initiate_move函数里,每次点击棋子后,都会给所有64个棋盘按钮重新连接play_move槽函数
  • 在play_move函数结束时,又会给所有按钮重新连接initiate_move槽函数
  • 每走一步,每个按钮的信号连接数就会翻倍,到第8步时每个按钮会有256个连接,点击一次会触发几百次函数调用,直接导致GUI线程阻塞无响应

修复方案

改用状态标记来区分当前操作阶段(选棋子/选目标位置),不再反复切换信号连接,同时优化棋盘样式更新逻辑:

关键修改点

  1. 添加状态变量self.selecting_piece = True,标记当前是选择棋子还是目标位置
  2. 移除initiate_move和play_move中所有self.Chess_Board[i].clicked.connect(...)的循环代码,初始化时只连接一次通用的点击处理函数
  3. 用一个变量记录当前选中的按钮,只重置该按钮的样式,避免每次遍历所有64个按钮重置样式
  4. 简化棋盘样式设置逻辑,减少重复操作

修复后的完整代码

import PyQt5
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import uic

import chess
import sys
import tabulate


class Chess_App(QtWidgets.QMainWindow):
    def __init__(self):
        super(Chess_App, self).__init__()

        uic.loadUi("CHESS.ui", self)

        self.move_name = ""
        self.move = ""
        self.selected_button = None  # 记录选中的按钮
        self.selecting_piece = True  # 状态标记:True=选棋子,False=选目标位置

        self.board_rows = [8,7,6,5,4,3,2,1]
        self.board_columns = 'a b c d e f g h'.split(" ")

        font = QtGui.QFont()
        font.setPointSize(1)

        self.win = None
        self.draw = None
        self.valid_move = True

        self.Chess_Board = {n:QtWidgets.QPushButton(self) for n in range(64)}

        # 初始化棋盘按钮样式和连接
        for i in range(64):
            self.Chess_Board[i].setFont(font)
            self.Chess_Board[i].setObjectName(f"{self.board_columns[i%8]}{self.board_rows[i//8]}")
            self.Chess_Board[i].setGeometry(60+((i%8)*50),140+((i//8)*50),50,50)
            self.Chess_Board[i].clicked.connect(self.handle_click)  # 只连接一次通用处理函数
            self.reset_button_style(i)  # 初始化样式

        self.lineEdit_2.setDisabled(True)

        self.lineEdit.isReadOnly()
        self.pushButton_A.clicked.connect(self.play_move)
        self.pushButton_B.clicked.connect(self.resignation)
        self.pushButton_C.clicked.connect(self.draw_game)
        self.pushButton_D.clicked.connect(self.save_game)
        self.pushButton_E.clicked.connect(self.reset_game)

        self.board = chess.Board()

        self.chessboard()
        self.show_board()

        self.show()

    def reset_button_style(self, index):
        """重置单个按钮的样式为棋盘原色"""
        if (index // 8) % 2 == 0:
            bg_color = "white" if index % 2 == 0 else "lightgreen"
        else:
            bg_color = "white" if index % 2 == 1 else "lightgreen"
        self.Chess_Board[index].setStyleSheet(f"QPushButton {{background-color : {bg_color}}}")

    def chessboard(self):
        self.pgn = self.board.epd()

        self.final_list = []
        self.printable_list = []

        pieces = self.pgn.split(" ", 1)[0]
        rows = pieces.split("/")

        for row in rows:
            self.initial_list = []
            for chessmen in row:
                if chessmen.isdigit():
                    self.initial_list.extend(["."]*int(chessmen))
                else:
                    self.initial_list.append(chessmen)
            self.final_list.append(self.initial_list)

    def handle_click(self):
        """通用点击处理函数,根据当前状态执行不同逻辑"""
        button = self.sender()
        if self.selecting_piece:
            self.initiate_move(button)
        else:
            self.play_move_from_click(button)

    def initiate_move(self, button):
        """选择棋子的逻辑"""
        # 重置之前选中按钮的样式
        if self.selected_button:
            btn_index = list(self.Chess_Board.keys())[list(self.Chess_Board.values()).index(self.selected_button)]
            self.reset_button_style(btn_index)
        
        self.selected_button = button
        self.move = button.text().upper()

        if self.move and len(self.move) == 1:
            if self.move != "P":
                self.move_name += self.move
            button.setStyleSheet("QPushButton {background-color : cyan}")
        
        self.selecting_piece = False  # 切换到选目标位置状态

    def play_move_from_click(self, button):
        """选择目标位置并执行移动的逻辑"""
        self.textBrowser.setText("")
        self.valid_move = True

        if len(button.text()) == 1:
            self.move_name += f"x{button.objectName()}"
        else:
            self.move_name += button.objectName()

        try:
            self.board.push_san(self.move_name)
        except Exception as ex:
            self.textBrowser.setText(f"{ex}")
            self.valid_move = False

        if self.valid_move:
            # 切换玩家输入框
            if self.lineEdit.isEnabled():  
                self.lineEdit.setDisabled(True)
                self.lineEdit_2.setDisabled(False)
            else:
                self.lineEdit.setDisabled(False)
                self.lineEdit_2.setDisabled(True)
            # 检查游戏结束状态
            if self.board.is_checkmate():
                self.textBrowser.setText("Checkmate !")
                self.win = True
                self.end_the_game()
            elif self.board.is_variant_draw() or self.board.is_stalemate() or self.board.is_fifty_moves() or self.board.is_repetition():
                if self.board.is_fifty_moves():
                    self.textBrowser.setText("Draw by fifty-move rule !")
                elif self.board.is_repetition():
                    self.textBrowser.setText("Draw by three-fold repetition !")
                else:
                    self.textBrowser.setText("Draw !")
                self.draw = True
                self.end_the_game()
            # 更新棋盘
            self.chessboard()
            self.show_board()
        else:
            self.textBrowser.setText("Invalid move, please try again")
        
        # 重置选中状态和移动记录
        if self.selected_button:
            btn_index = list(self.Chess_Board.keys())[list(self.Chess_Board.values()).index(self.selected_button)]
            self.reset_button_style(btn_index)
        self.selected_button = None
        self.move = ""
        self.move_name = ""
        self.selecting_piece = True  # 切回选棋子状态

    def play_move(self):
        """原按钮触发的移动逻辑(保留兼容)"""
        pass

    def show_board(self):
        for x in range(8):
            for y in range(8):
                piece = self.final_list[x][y]
                btn = self.Chess_Board[x*8+y]
                if piece.isupper():
                    btn.setText(piece.upper())
                    btn.setIcon(QtGui.QIcon(f"{piece}@.png"))
                    btn.setIconSize(QtCore.QSize(40,40))
                elif piece.islower():
                    btn.setText(piece.lower())
                    btn.setIcon(QtGui.QIcon(f"{piece}&.png"))
                    btn.setIconSize(QtCore.QSize(40,40))
                else:
                    btn.setIcon(QtGui.QIcon())
                    btn.setText("")

    def reset_game(self):
        self.board.reset()
        self.lineEdit.setText("")
        self.lineEdit_2.setText("")
        self.lineEdit.setDisabled(False)
        self.lineEdit_2.setDisabled(True)
        self.textBrowser.setText("")
        self.selected_button = None
        self.selecting_piece = True
        self.move_name = ""
        self.move = ""
        self.chessboard()
        self.show_board()
        self.end_the_game()

    def draw_game(self):
        self.draw = True
        self.end_the_game()
        self.textBrowser.setText("It's a draw .")

    def resignation(self):
        if self.lineEdit.text() == self.lineEdit_2.text() == "":
            self.textBrowser.setText("Match Aborted")
        else:
            who_resigned = "Black" if self.lineEdit.isEnabled() else "White"
            self.win = self.lineEdit.isEnabled()
            self.end_the_game()
            self.textBrowser.setText(f"{who_resigned} resigned")

    def save_game(self):
        self.chesstable = tabulate.tabulate(self.final_list, tablefmt="grid", stralign="left")
        with open("chess_save_list.txt","a") as writer:
            writer.write(self.chesstable + "\n\n")  # 添加换行分隔保存内容

    def end_the_game(self):
        if self.win is not None or self.draw is not None:
            self.pushButton_A.setDisabled(True)
            self.pushButton_B.setDisabled(True)
            self.pushButton_C.setDisabled(True)
            self.win, self.draw = None, None
        else:
            self.pushButton_A.setDisabled(False)
            self.pushButton_B.setDisabled(False)
            self.pushButton_C.setDisabled(False)


app = QtWidgets.QApplication(sys.argv)
window = Chess_App()
app.exec_()

额外优化建议

  • 棋子图标加载可以提前缓存,避免每次show_board都重新加载图标文件
  • 棋盘样式可以用QSS统一设置,减少逐个按钮设置样式的开销
  • 游戏状态判断可以合并为一次遍历,减少重复调用board.is_xxx()方法

内容的提问来源于stack exchange,提问作者Vishal 10B

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 03:54:15