【问题标题】:Running and exiting PyQt application multiple times多次运行和退出 PyQt 应用程序
【发布时间】:2019-12-12 08:48:31
【问题描述】:

上下文

我正在使用 PyQt 创建我自己版本的棋盘游戏Battleship。 PyQt 主窗口包含自己的和敌人的板。棋盘由玩家“开火”的可点击图块组成。该实现支持 HUMANvsAI 和 AIvsAI 游戏。我想通过模拟测试我的人工智能算法的强度。例如,我想循环运行 1000 场 AIvsAI 游戏,并获得 % 胜利、平均准确率等统计数据。

主要问题

我正在努力多次运行 PyQt 应用程序来收集游戏数据,例如在一个 for 循环中。具体来说,我找不到运行应用程序、退出它并重新运行它的方法。从概念上讲,我正在寻找这样的东西:

# conceptual snippet
  for i in range(n):
    app = QApplication([])
    window = MainWindow(b_size, boat_dict, players)
    app.exec_()

调用退出应用程序并在每次游戏结束时调用:

# conceptual snippet
  if is_game_over():
    sys.exit(app.exec_())

但是这个简单的解决方案打破了 for 循环。欢迎就如何按顺序(例如 for 循环方法)或并行线程多次运行和退出 PyQt 应用程序提供任何反馈。

【问题讨论】:

  • 如果您想查看 AI 与 AI 的结果,为什么还要产生生成 Qt 应用程序的开销呢?为什么不直接隔离 AI 游戏逻辑并观察它呢?我认为生成这样的 GUI 值得投入资源的唯一原因是能够进行人机交互。
  • @JoeHabel 目前逻辑与 PyQt 对象交织在一起。例如,它是类“瓷砖”,它存储自己的游戏数据,关于它是否已经被击中/是否有船/船被击中/船是否沉没等。我同意可以使用列表列表/ numpy 数组作为板子并单独测试 AI,但这是以在没有 PyQt 的情况下重新创建游戏逻辑为代价的。我有兴趣避免这种情况

标签: python loops for-loop pyqt pyqt5


【解决方案1】:

您不应该使用sys.exit(),因为该指令用于终止程序的执行,如果您想终止Qt 应用程序,您必须使用QCoreApplication::quit()(或QCoreApplication::exit(0))。此外,另一个改进是使用多处理:

import random
from multiprocessing import Pool

from PyQt5 import QtCore, QtWidgets


def create_app(i):
    app = QtWidgets.QApplication([])
    w = QtWidgets.QMainWindow()
    w.setWindowTitle("Game-{}".format(i))
    w.show()

    # emulate end-game
    def end_game():
        QtCore.QCoreApplication.quit()

    timeout = random.randint(1000, 2000)  # 1000-2000 milliseconds
    QtCore.QTimer.singleShot(timeout, end_game)
    app.exec_()

    # emulate results
    o = {"victory": random.randint(0, 101), "average": random.randint(0, 101)}
    return o


def main():
    results = []
    pool = Pool(processes=8)
    for i in range(1000):
        r = pool.apply_async(create_app, args=(i,))
        results.append(r)
    pool.close()
    pool.join()
    print([result.get() for result in results])


if __name__ == "__main__":
    main()

【讨论】:

  • 关于使用多处理的一些注意事项,如果您使用的是 Windows,根据您的屏幕截图,您可能需要使用多处理的 freeze_support(我知道我必须支持当我使用 Windows 时,但已经有一段时间了,所以我不确定发生了什么变化)。这就像添加以下导入from multiprocessing import freeze_support 一样简单,然后在main() 调用之上,添加freeze_support() 调用。另请注意,Pool(processes=8) 中的进程数取决于您可用的 cpu 内核。
  • @JoeHabel 感谢您提供的信息,嗯,截图不是我的(我讨厌 Windows,我是 Linux 爱好者),另一方面,我的回答并没有试图解释实现的局限性在每个操作系统中进行多处理,这只是一种基本方法的尝试,因为如果我想做一些看起来你正在试图解决问题的事情:除非提出一个精确的问题,否则 SO 提供的小空间不适合这个。
  • @JoeHabel 我知道您指出的要点,但我对限制的一点解释是有意让 OP 进行调查的。我认为您应该指向 OP 的评论。
  • 你说得对,该评论并不是针对您,而是针对@AverageBoy。我刚刚看到了 OP 的 Windows 屏幕截图,并回想起了 Windows 和多处理的恐怖。
【解决方案2】:

我必须告诉你,你的问题很危险地接近“过于宽泛”的标志。真正的问题是:你想如何跟踪收集到的数据,你想用这些数据做什么?这个问题可能有很多个答案。

您已经发现,您不能使用sys.exit,但您可以通过不同的方式收集数据。
如果您要在受控环境中运行您的应用程序,一个可能的解决方案是序列化您收集的数据,同时控制来自应用程序的整个“收集”过程

半伪代码:

from PyQt5 import QtCore, QtWidgets
import json

class BattleShipWindow(QtWidgets.QMainWindow):
    restart = QtCore.pyqtSignal()

    # ...

    def storeData(self, data):
        # get the app data location
        appDir = QtWidgets.QStandardPath.standardLocations(
            QtCore.QStandardaPaths.AppDataLocation)[0]
        # if it doesn't exists, create it
        if not QtCore.QFile.exists(appDir):
            QtCore.QDir().mkpath(appDir)
        now = QtCore.QDateTime.currentDateTime()
        fileName = str(now.toMSecsSinceEpoch() // 1000)
        # write down the data
        with open(os.path.join(appDir, fileName), 'w') as df:
            df.write(json.dumps(data))

    def getGameData(self):
        # gather your own data here, this is just an example
        return {'gameData': [1, 2, 3]}

    def closeEvent(self, event):
        if QtWidgets.QMessageBox.question(
            self, 'Play again?',
            'Play another game?', 
            QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No
            ) == QtWidgets.QMessageBox.Yes:
                self.storeData(self.getGameData())
                self.restart.emit()


class BattleShipApp(QtWidgets.QApplication):
    def restart(self):
        self.currentGame = BattleShipWindow()
        self.currentGame.restart.connect(self.restart)
        self.currentGame.show()

    def exec_(self):
        self.currentGame = BattleShipWindow()
        self.currentGame.restart.connect(self.restart)
        self.currentGame.show()
        super(BattleShipApp, self).exec_()


if __name__ == '__main__':
    import sys
    app = BattleShipApp(sys.argv)
    sys.exit(app.exec_())

注意:这是一个[半]伪代码,我显然没有测试它。它的目的是展示它的行为方式,所以不要期望它按原样工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-29
    • 2019-12-22
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    • 2016-11-12
    相关资源
    最近更新 更多