From 9e1c3d0d775d4ecaa83a72cfd57a412665f36d11 Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Wed, 5 Feb 2025 23:44:06 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=95=E4=BE=8B?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=EF=BC=8C=E6=89=93=E5=BC=80=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=97=B6=E4=BC=9A=E8=B0=83=E7=94=A8=E5=90=8C=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=8B=96=E5=85=A5?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E4=BD=BF=E5=BE=97=E5=8F=AF=E4=BB=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E6=8B=96=E5=85=A5=E6=96=87=E4=BB=B6=E6=9D=A5=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E6=92=AD=E6=94=BE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 74 +++++++++++++++++++++++++++++++------------ src/mainWindowGUI.py | 33 ++++++++++++++++--- src/mineSweeperGUI.py | 7 ++-- 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/src/main.py b/src/main.py index 3f047db..90d86fe 100644 --- a/src/main.py +++ b/src/main.py @@ -1,14 +1,34 @@ # from fbs_runtime.application_context.PyQt5 import ApplicationContext +import time from PyQt5 import QtWidgets from PyQt5 import QtCore +from PyQt5.QtWidgets import QApplication,QMessageBox +from PyQt5.QtNetwork import QLocalSocket,QLocalServer import sys, os import mainWindowGUI as mainWindowGUI import mineSweeperGUI as mineSweeperGUI import ctypes from ctypes import wintypes - os.environ["QT_FONT_DPI"] = "96" +def on_new_connection(localServer:QLocalServer): + """当新连接进来时,接受连接并将文件路径传递给主窗口""" + socket = localServer.nextPendingConnection() + if socket: + socket.readyRead.connect(lambda: on_ready_read(socket)) + +def on_ready_read(socket:QLocalSocket): + """从socket读取文件路径并传递给主窗口""" + if socket and socket.state() == QLocalSocket.ConnectedState: + # 读取文件路径并调用打开文件 + socket.waitForReadyRead(500) + file_path = socket.readAll().data().decode() + for win in QApplication.topLevelWidgets(): + if isinstance(win, mainWindowGUI.MainWindow): + win.dropFileSignal.emit(file_path) + socket.disconnectFromServer() # 断开连接 + + def find_window(class_name, window_name): """ @@ -37,26 +57,38 @@ def find_window(class_name, window_name): if __name__ == "__main__": # QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - - app = QtWidgets.QApplication (sys.argv) - mainWindow = mainWindowGUI.MainWindow() - ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) - ui.mainWindow.show() - ui.mainWindow.game_setting_path = ui.game_setting_path - - _translate = QtCore.QCoreApplication.translate - hwnd = find_window(None, _translate("MainWindow", "元扫雷")) - - SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity - ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000011) else 1/0 - ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000000) else 1/0 - - - - - sys.exit(app.exec_()) - ... - + try: + app = QtWidgets.QApplication (sys.argv) + serverName = "MineSweeperServer" + socket = QLocalSocket() + socket.connectToServer(serverName) + if socket.waitForConnected(500): + if len(sys.argv) == 2: + filePath = sys.argv[1] + socket.write(filePath.encode()) + socket.flush() + time.sleep(0.5) + app.quit() + else: + localServer = QLocalServer() + localServer.listen(serverName) + localServer.newConnection.connect(lambda: on_new_connection(localServer=localServer)) + mainWindow = mainWindowGUI.MainWindow() + ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) + ui.mainWindow.show() + ui.mainWindow.game_setting_path = ui.game_setting_path + + _translate = QtCore.QCoreApplication.translate + hwnd = find_window(None, _translate("MainWindow", "元扫雷")) + + SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity + ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000011) else 1/0 + ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000000) else 1/0 + + sys.exit(app.exec_()) + ... + except Exception as e: + pass # 最高优先级 # 计时器快捷键切换 diff --git a/src/mainWindowGUI.py b/src/mainWindowGUI.py index 92fb006..11a51ec 100644 --- a/src/mainWindowGUI.py +++ b/src/mainWindowGUI.py @@ -1,23 +1,30 @@ from PyQt5 import QtCore, QtWidgets from PyQt5.QtCore import Qt -from PyQt5.QtCore import QTimer +from PyQt5.QtCore import QTimer, QFileInfo from PyQt5.QtWidgets import QApplication - +from PyQt5.QtGui import QDragEnterEvent, QDropEvent # 重写QMainWindow + + class MainWindow(QtWidgets.QMainWindow): keyRelease = QtCore.pyqtSignal(str) closeEvent_ = QtCore.pyqtSignal() + dropFileSignal = QtCore.pyqtSignal(str) flag_drag_border = False minimum_counter = 0 + def __init__(self): + super(MainWindow, self).__init__() + self.setAcceptDrops(True) + def closeEvent(self, event): self.closeEvent_.emit() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Space and not event.isAutoRepeat(): self.keyRelease.emit('Space') - - def resizeEvent(self,event): + + def resizeEvent(self, event): # 拖拽边框后resize尺寸 if QApplication.mouseButtons() & Qt.LeftButton: self.flag_drag_border = True @@ -35,4 +42,22 @@ def __minimumWindowRelease(self): self.minimum_counter = 0 self.timer_.stop() + def dragEnterEvent(self, event: QDragEnterEvent): + super().dragEnterEvent(event) + if event.mimeData().hasUrls(): + url = event.mimeData().urls()[0] + if url.isLocalFile(): + fileType = QFileInfo(url.toLocalFile()).suffix() + if fileType in ('evf', 'avf', 'rmv', 'mvf'): + event.acceptProposedAction() + return + event.ignore() + def dropEvent(self, event: QDropEvent): + super().dropEvent(event) + files = event.mimeData().urls() + if len(files) > 0: + url = files[0] + if url.isLocalFile(): + filePath = url.toLocalFile() + self.dropFileSignal.emit(filePath) diff --git a/src/mineSweeperGUI.py b/src/mineSweeperGUI.py index 2e2343e..2b9cbbc 100644 --- a/src/mineSweeperGUI.py +++ b/src/mineSweeperGUI.py @@ -19,10 +19,10 @@ # from PyQt5.QtWidgets import QApplication # from country_name import country_name import metaminesweeper_checksum - +from mainWindowGUI import MainWindow class MineSweeperGUI(superGUI.Ui_MainWindow): - def __init__(self, MainWindow, args): + def __init__(self, MainWindow: MainWindow, args): self.mainWindow = MainWindow self.checksum_guard = metaminesweeper_checksum.ChecksumGuard() @@ -110,7 +110,7 @@ def save_evf_file_integrated(): self.relative_path = args[0] # 用本软件打开录像 if len(args) == 2: - self.action_OpenFile(args[1]) + self.action_OpenFile(openfile_name=args[1]) self.trans_language() self.score_board_manager.with_namespace({ @@ -124,6 +124,7 @@ def save_evf_file_integrated(): self.score_board_manager.visible() self.mainWindow.closeEvent_.connect(self.closeEvent_) + self.mainWindow.dropFileSignal.connect(self.action_OpenFile) @property def pixSize(self): From 79b30289286c5d854dd58da417154b035dcb2f0f Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 19:04:39 +0800 Subject: [PATCH 2/7] Merge remote-tracking branch 'upstream/master' --- .github/workflows/python-app.yml | 2 +- README.md | 4 +- cat.ico | Bin 28413 -> 0 bytes media/cat.ico | Bin 28413 -> 90022 bytes requirements.txt | 2 +- src/captureScreen.py | 1 - src/demo_OBR.py | 16 - src/gameScoreBoard.py | 117 ++-- src/gameSettingShortcuts.py | 104 ++- src/gameSettings.py | 152 +++-- src/main.py | 89 +-- src/mineSweeperGUI.py | 358 +++++----- src/minesweeper_master.py | 12 +- src/superGUI.py | 645 +++++++++--------- src/ui/de_DE.ts | 245 +++---- src/ui/en_US.ts | 402 +++-------- src/ui/pl_PL.ts | 250 +++---- src/ui/uiComponents.py | 17 +- src/ui/ui_about.py | 8 +- src/ui/ui_defined_parameter.py | 3 +- src/ui/ui_gameSettingShortcuts.py | 5 +- src/ui/ui_gameSettings.py | 6 +- src/ui/ui_main_board.py | 51 +- src/ui/ui_mine_num_bar.py | 6 +- src/ui/ui_record_pop.py | 2 +- src/ui/ui_score_board.py | 22 +- src/ui/ui_video_control.py | 23 +- ...346\210\220ts\346\226\207\344\273\266.bat" | 6 +- src/videoControl.py | 55 +- uiFiles/main_board.ui | 91 +-- uiFiles/ui_about.ui | 6 +- uiFiles/ui_defined_parameter.ui | 3 + uiFiles/ui_gs.ui | 8 +- uiFiles/ui_gs_shortcuts.ui | 5 +- uiFiles/ui_mine_num_bar.ui | 2 +- uiFiles/ui_score_board.ui | 21 +- uiFiles/ui_video_control.ui | 37 +- "uiFiles/ui\350\275\254py.bat" | 1 + 38 files changed, 1165 insertions(+), 1612 deletions(-) delete mode 100644 cat.ico delete mode 100644 src/demo_OBR.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index f18462a..150e10e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -83,7 +83,7 @@ jobs: - name: Package with PyInstaller run: | - pyinstaller -i cat.ico -w -y --clean --add-data "./media/*;./media" --add-data "./de_DE.qm;." --add-data "./pl_PL.qm;." --add-data "./en_US.qm;." -p src src/main.py -n metaminesweeper + pyinstaller -i "./media/cat.ico" -w -y --clean --add-data "./media/*;./media" --add-data "./de_DE.qm;." --add-data "./pl_PL.qm;." --add-data "./en_US.qm;." -p src src/main.py -n metaminesweeper - name: Move media and qm file run: | diff --git a/README.md b/README.md index fdadde5..ea7aa52 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## 简介 -**元扫雷**是由专业玩家玩家开发的扫雷游戏。这个项目并非简单重复已有的工作,而是集中了一批扫雷游戏的现代化设计。元扫雷所生成的游戏录像得到[开源扫雷网](https://openms.top)的承认并参与世界排名。 +**元扫雷**是由专业玩家开发的扫雷游戏。这个项目并非简单重复已有的工作,而是集中了一批扫雷游戏的现代化设计。元扫雷所生成的游戏录像得到[开源扫雷网](https://openms.top)的承认并参与世界排名。 优势: @@ -134,6 +134,8 @@ Currently in the lengthy development phase, with updates approximately every 1 t 开源扫雷网官方扫雷软件[https://openms.top](https://openms.top) +[![Star History Chart](https://api.star-history.com/svg?repos=eee555/Metasweeper&type=Date)](https://star-history.com/?repos=eee555/Metasweeper#repos=eee555/Metasweeper&eee555/Metasweeper&Date) + ## 赞助 感谢您考虑支持我们的开源项目,赞助时请备注您的称呼(或Github主页)。您的赞助将有助于项目的持续发展和改进,使我们能够继续提高软件的质量(owner许诺向所有contributor按获得赞助时commit数量的比例分配赞助得到的收入)。 diff --git a/cat.ico b/cat.ico deleted file mode 100644 index 5f6244a5522d3ba2dfeccb636444232257d3157a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28413 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 QUSJ1NRn$_ble3QcKUf)6=l}o! diff --git a/media/cat.ico b/media/cat.ico index 5f6244a5522d3ba2dfeccb636444232257d3157a..cabc57504a305ee0b422196dc8d563be28793f63 100644 GIT binary patch literal 90022 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 Sap7HaJMkAEoBSL8_5TATT0Bz# literal 28413 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 QUSJ1NRn$_ble3QcKUf)6=l}o! diff --git a/requirements.txt b/requirements.txt index 6cc5e74..7c75b61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ pyQt5==5.15.7 -ms-toollib==1.4.12 \ No newline at end of file +ms-toollib==1.4.15 \ No newline at end of file diff --git a/src/captureScreen.py b/src/captureScreen.py index 397e995..9e8906b 100644 --- a/src/captureScreen.py +++ b/src/captureScreen.py @@ -98,7 +98,6 @@ def getBoard(self): byteCount = image.byteCount() s = Struct(str(byteCount) + 'B') self.data = s.unpack(bits[0:]) - self.success_flag = True try: self.board = ms_toollib.OBR_board(self.data, self.height, self.width) diff --git a/src/demo_OBR.py b/src/demo_OBR.py deleted file mode 100644 index 0d21ee6..0000000 --- a/src/demo_OBR.py +++ /dev/null @@ -1,16 +0,0 @@ -import ms_toollib -import matplotlib.image as mpimg -import numpy as np -file = r'C:\Users\jia32\Desktop\无标题.png'# 彩色图片 -lena = mpimg.imread(file) -(height, width) = lena.shape[:2] -lena = np.concatenate((lena, 255.0 * np.ones((height, width, 1))), 2) -lena = (np.reshape(lena, -1) * 255).astype(np.uint32) -board = ms_toollib.py_OBR_board(lena, height, width) -print(np.array(board))# 打印识别出的局面 -poss = ms_toollib.py_cal_possibility(board, 99) -print(poss)# 用雷的总数计算概率 -poss_onboard = ms_toollib.py_cal_possibility_onboard(board, 99) -print(poss_onboard)# 用雷的总数计算概率,输出局面对应的位置 -poss_ = ms_toollib.py_cal_possibility_onboard(board, 0.20625) -print(poss_)# 用雷的密度计算概率 \ No newline at end of file diff --git a/src/gameScoreBoard.py b/src/gameScoreBoard.py index 0f1fbc3..8795385 100644 --- a/src/gameScoreBoard.py +++ b/src/gameScoreBoard.py @@ -1,5 +1,4 @@ # 左侧计时器 -import configparser import minesweeper_master as mm from ui.ui_score_board import Ui_Form from ui.uiComponents import RoundQWidget @@ -73,11 +72,11 @@ class gameScoreBoardManager(): video_index = ["bbbv", "op", "isl", "cell0", "cell1", "cell2", "cell3", "cell4", "cell5", "cell6", "cell7", "cell8", "fps", "etime", "stnb", "rqp", "qg", "ioe", "thrp", "corr", "ce", - "ce_s", "bbbv_solved", "bbbv_s", "op_solved", "isl_solved"] + "ce_s", "bbbv_solved", "bbbv_s", "op_solved", "isl_solved", "pluck"] # is_visible = False # 5、错误的表达式,一旦算出报错,永远不再算,显示error - def __init__(self, r_path, score_board_path, game_setting_path, pix_size, parent): + def __init__(self, r_path, score_board_setting, game_setting, pix_size, parent): # 从文件中读取指标并设置 # self.ms_board = None self.pix_size = pix_size @@ -88,44 +87,42 @@ def __init__(self, r_path, score_board_path, game_setting_path, pix_size, parent self.delta_time = 0.0 self.initialized = False - self.game_setting_path = game_setting_path - self.score_board_path = score_board_path - config_score_board = configparser.ConfigParser() - if config_score_board.read(self.score_board_path): - # 计时器配置list[tuple(str, str)] - _score_board_items = config_score_board.items('DEFAULT') - else: - config_score_board["DEFAULT"] = { - "游戏模式": "mode", - "RTime": "f'{time:.3f}'", - "Est RTime": "f'{etime:.3f}'", - "3BV": "f'{bbbv_solved}/{bbbv}'", - "3BV/s": "f'{bbbv_s:.3f}'", - "Ops": "op", - "Isls": "isl", - "Left": "f'{left}@{left_s:.3f}'", - "Right": "f'{right}@{right_s:.3f}'", - "Double": "f'{double}@{double_s:.3f}'", - "STNB": "f'{stnb:.3f}'", - "IOE": "f'{ioe:.3f}'", - "Thrp": "f'{thrp:.3f}'", - "Corr": "f'{corr:.3f}'", - "Path": "f'{path:.1f}'", - } - _score_board_items = list(config_score_board.items('DEFAULT')) - with open(self.score_board_path, 'w') as configfile: - config_score_board.write(configfile) # 将对象写入文件 - self.score_board_items = [[i[0], mm.trans_expression(i[1])] for\ - i in _score_board_items] + self.game_setting = game_setting + self.score_board_setting = score_board_setting + default_config = [ + ("游戏模式", "mode"), + ("RTime", "f'{time:.3f}'"), + ("Est RTime", "f'{etime:.3f}'"), + ("3BV", "f'{bbbv_solved}/{bbbv}'"), + ("3BV/s", "f'{bbbv_s:.3f}'"), + ("Ops", "op"), + ("Isls", "isl"), + ("Left", "f'{left}@{left_s:.3f}'"), + ("Right", "f'{right}@{right_s:.3f}'"), + ("Double", "f'{double}@{double_s:.3f}'"), + ("STNB", "f'{stnb:.3f}'"), + ("IOE", "f'{ioe:.3f}'"), + ("Thrp", "f'{thrp:.3f}'"), + ("Corr", "f'{corr:.3f}'"), + ("Path", "f'{path:.1f}'"), + ("Pluck", "f'{pluck:.3f}'"), + ] + self.score_board_items = self.score_board_setting.get_or_set_section("DEFAULT", default_config) + + self.score_board_setting.sync() + self.update_score_board_items_type() self.index_num = len(self.score_board_items_type) self.ui = ui_Form(r_path, pix_size, parent) self.ui.tableWidget.doubleClicked.connect(self.__table_change) self.ui.tableWidget.clicked.connect(self.__table_ok) - self.ui.tableWidget.cellChanged.connect(self.__cell_changed) + # self.ui.tableWidget.cellChanged.connect(self.__cell_changed) self.ui.pushButton_add.clicked.connect(self.__add_blank_line) + self.ui.QWidget.closeEvent_.connect(self.close) QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Return), self.ui.QWidget).\ activated.connect(self.__table_ok) + self.ui.QWidget.move(game_setting.value("DEFAULT/scoreboardtop", 100, int), + game_setting.value("DEFAULT/scoreboardleft", 200, int)) self.editing_row = -1 # -1不在编辑状态,-2不能编辑(正在游戏) self.editing_column = -1 @@ -160,7 +157,7 @@ def reset(self): ... def cal_index_value(self, ms_board, index_type): - # 原地修改指标数值 + # 原地修改指标数值 self.update_namespace(ms_board, index_type) index_value = [] # for (idx, (_, expression), _type) in enumerate(zip(self.score_board_items, self.score_board_items_type)): @@ -213,7 +210,7 @@ def update_namespace(self, ms_board, index_type): "flag": ms_board.flag, "flag_s": ms_board.flag_s, }) - if index_type == 2: + if index_type >= 2: self.namespace.update({ "rtime": ms_board.rtime, "etime": ms_board.etime, @@ -227,7 +224,11 @@ def update_namespace(self, ms_board, index_type): "thrp": ms_board.thrp, "corr": ms_board.corr, "ce": ms_board.ce, - "ce_s": ms_board.ce_s, # 一直为0 + "ce_s": ms_board.ce_s, + }) + if index_type >= 3: + self.namespace.update({ + "pluck": ms_board.pluck, }) @@ -299,28 +300,31 @@ def __table_ok(self, e = None): if e == None or (self.editing_row >= 0 and self.editing_column >= 0 and (self.editing_row != e.row() or\ self.editing_column != e.column())): # 编辑完成后修改指标值 - self.ui.tableWidget.setDisabled(True) - self.ui.tableWidget.setDisabled(False) + # self.ui.tableWidget.setDisabled(True) + # self.ui.tableWidget.setDisabled(False) new_formula = self.ui.tableWidget.item(self.editing_row, self.editing_column).text() if self.editing_column == 0: if not new_formula: + # 删除键名后并完成编辑后,删除此指标 self.score_board_items.pop(self.editing_row) self.score_board_items_type.pop(self.editing_row) else: - self.score_board_items[self.editing_row][0] = new_formula + # 正常修改键名 + self.score_board_items[self.editing_row] = (new_formula, self.score_board_items[self.editing_row][1]) else: - self.score_board_items[self.editing_row][1] = new_formula + # 正常修改公式 + self.score_board_items[self.editing_row] = (self.score_board_items[self.editing_row][0], new_formula) self.update_score_board_items_type() self.reshow(self.ms_board) self.editing_row = -1 self.editing_column = -1 - def __cell_changed(self, x, y): - # 把计数器里的公式改成新设置的公式 - if y == 0: - t = self.ui.tableWidget.item(x, y).text() - if self.score_board_items[x][0] != t: - self.score_board_items[x][0] = self.ui.tableWidget.item(x, 0).text() + # def __cell_changed(self, x, y): + # # 把计数器里的公式改成新设置的公式 + # if y == 0: + # t = self.ui.tableWidget.item(x, y).text() + # if self.score_board_items[x][0] != t: + # self.score_board_items[x][0] = self.ui.tableWidget.item(x, 0).text() def __add_blank_line(self): # 添加一个空开的行,并刷新显示 @@ -329,22 +333,11 @@ def __add_blank_line(self): self.reshow(self.ms_board) def close(self): - config = configparser.ConfigParser() - config["DEFAULT"] = dict(filter(lambda x: x[0], self.score_board_items)) - config.write(open(self.score_board_path, "w")) - conf = configparser.ConfigParser() - conf.read(self.game_setting_path, encoding='utf-8') - conf.set("DEFAULT", "scoreBoardTop", str(self.ui.QWidget.x())) - conf.set("DEFAULT", "scoreBoardLeft", str(self.ui.QWidget.y())) - conf.write(open(self.game_setting_path, "w", encoding='utf-8')) - self.ui.QWidget.close() - - - - - - - + self.score_board_setting.set_section("DEFAULT", self.score_board_items) + self.score_board_setting.sync() + self.game_setting.set_value("DEFAULT/scoreboardtop", self.ui.QWidget.x()) + self.game_setting.set_value("DEFAULT/scoreboardleft", self.ui.QWidget.y()) + self.game_setting.sync() diff --git a/src/gameSettingShortcuts.py b/src/gameSettingShortcuts.py index e2dd063..aadc30f 100644 --- a/src/gameSettingShortcuts.py +++ b/src/gameSettingShortcuts.py @@ -7,8 +7,8 @@ # 减少ui文件生成的原始的.py的改动 class myGameSettingShortcuts(Ui_Form): - def __init__(self, game_setting_path, ico_path, r_path, parent): - self.game_setting_path = game_setting_path + def __init__(self, game_setting, ico_path, r_path, parent): + self.game_setting = game_setting self.r_path = r_path self.Dialog = RoundQDialog(parent) self.setupUi(self.Dialog) @@ -19,35 +19,32 @@ def __init__(self, game_setting_path, ico_path, r_path, parent): self.alter = False def setParameter(self): - config = configparser.ConfigParser() - config.read(self.game_setting_path, encoding='utf-8') - # modTable = [0,1,4,2,3,5,6,7] modTable = [0,0,0,0,1,4,2,3,5,6,7] - self.comboBox_gamemode4.setCurrentIndex(modTable[config.getint('CUSTOM_PRESET_4','gameMode')]) - self.spinBox_height4.setProperty("value", config.getint('CUSTOM_PRESET_4','row')) - self.spinBox_width4.setProperty("value", config.getint('CUSTOM_PRESET_4','column')) - self.spinBox_pixsize4.setProperty("value", config.getint('CUSTOM_PRESET_4','pixSize')) - self.spinBox_attempt_times_limit4.setProperty("value", config.getint('CUSTOM_PRESET_4','attempt_times_limit')) - self.spinBox_minenum4.setProperty("value", config.getint('CUSTOM_PRESET_4','mineNum')) - self.lineEdit_constraint4.setProperty("value", config["CUSTOM_PRESET_4"]["board_constraint"]) - - self.comboBox_gamemode5.setCurrentIndex(modTable[config.getint('CUSTOM_PRESET_5','gameMode')]) - self.spinBox_height5.setProperty("value", config.getint('CUSTOM_PRESET_5','row')) - self.spinBox_width5.setProperty("value", config.getint('CUSTOM_PRESET_5','column')) - self.spinBox_pixsize5.setProperty("value", config.getint('CUSTOM_PRESET_5','pixSize')) - self.spinBox_attempt_times_limit5.setProperty("value", config.getint('CUSTOM_PRESET_5','attempt_times_limit')) - self.spinBox_minenum5.setProperty("value", config.getint('CUSTOM_PRESET_5','mineNum')) - self.lineEdit_constraint5.setProperty("value", config["CUSTOM_PRESET_5"]["board_constraint"]) - - self.comboBox_gamemode6.setCurrentIndex(modTable[config.getint('CUSTOM_PRESET_6','gameMode')]) - self.spinBox_height6.setProperty("value", config.getint('CUSTOM_PRESET_6','row')) - self.spinBox_width6.setProperty("value", config.getint('CUSTOM_PRESET_6','column')) - self.spinBox_pixsize6.setProperty("value", config.getint('CUSTOM_PRESET_6','pixSize')) - self.spinBox_attempt_times_limit6.setProperty("value", config.getint('CUSTOM_PRESET_6','attempt_times_limit')) - self.spinBox_minenum6.setProperty("value", config.getint('CUSTOM_PRESET_6','mineNum')) - self.lineEdit_constraint6.setProperty("value", config["CUSTOM_PRESET_6"]["board_constraint"]) - + self.comboBox_gamemode4.setCurrentIndex(modTable[self.game_setting.value("CUSTOM_PRESET_4/gamemode", None, int)]) + self.spinBox_height4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/row", None, int)) + self.spinBox_width4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/column", None, int)) + self.spinBox_pixsize4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/pixsize", None, int)) + self.spinBox_attempt_times_limit4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/attempt_times_limit", None, int)) + self.spinBox_minenum4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/mine_num", None, int)) + self.lineEdit_constraint4.setProperty("value", self.game_setting.value("CUSTOM_PRESET_4/board_constraint", None, str)) + + self.comboBox_gamemode5.setCurrentIndex(modTable[self.game_setting.value("CUSTOM_PRESET_5/gamemode", None, int)]) + self.spinBox_height5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/row", None, int)) + self.spinBox_width5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/column", None, int)) + self.spinBox_pixsize5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/pixsize", None, int)) + self.spinBox_attempt_times_limit5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/attempt_times_limit", None, int)) + self.spinBox_minenum5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/mine_num", None, int)) + self.lineEdit_constraint5.setProperty("value", self.game_setting.value("CUSTOM_PRESET_5/board_constraint", None, str)) + + self.comboBox_gamemode6.setCurrentIndex(modTable[self.game_setting.value("CUSTOM_PRESET_6/gamemode", None, int)]) + self.spinBox_height6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/row", None, int)) + self.spinBox_width6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/column", None, int)) + self.spinBox_pixsize6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/pixsize", None, int)) + self.spinBox_attempt_times_limit6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/attempt_times_limit", None, int)) + self.spinBox_minenum6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/mine_num", None, int)) + self.lineEdit_constraint6.setProperty("value", self.game_setting.value("CUSTOM_PRESET_6/board_constraint", None, str)) + self.pushButton.setStyleSheet("border-image: url(" + str(self.r_path.with_name('media').joinpath('button.png')).replace("\\", "/") + ");\n" "font: 16pt \"黑体\";\n" "color:white;font: bold;") @@ -61,32 +58,29 @@ def processParameter(self): # modTable = [0,1,3,4,2,5,6,7] modTable = [0,4,6,7,5,8,9,10] - conf = configparser.ConfigParser() - conf.read(self.game_setting_path, encoding='utf-8') - conf.set("CUSTOM_PRESET_4", "gameMode", str(modTable[self.comboBox_gamemode4.currentIndex()])) - conf.set("CUSTOM_PRESET_4", "row", str(self.spinBox_height4.value())) - conf.set("CUSTOM_PRESET_4", "column", str(self.spinBox_width4.value())) - conf.set("CUSTOM_PRESET_4", "pixSize", str(self.spinBox_pixsize4.value())) - conf.set("CUSTOM_PRESET_4", "attempt_times_limit", str(self.spinBox_attempt_times_limit4.value())) - conf.set("CUSTOM_PRESET_4", "mineNum", str(self.spinBox_minenum4.value())) - conf.set("CUSTOM_PRESET_4", "board_constraint", self.lineEdit_constraint4.text()) - - conf.set("CUSTOM_PRESET_5", "gameMode", str(modTable[self.comboBox_gamemode5.currentIndex()])) - conf.set("CUSTOM_PRESET_5", "row", str(self.spinBox_height5.value())) - conf.set("CUSTOM_PRESET_5", "column", str(self.spinBox_width5.value())) - conf.set("CUSTOM_PRESET_5", "pixSize", str(self.spinBox_pixsize5.value())) - conf.set("CUSTOM_PRESET_5", "attempt_times_limit", str(self.spinBox_attempt_times_limit5.value())) - conf.set("CUSTOM_PRESET_5", "mineNum", str(self.spinBox_minenum5.value())) - conf.set("CUSTOM_PRESET_5", "board_constraint", self.lineEdit_constraint5.text()) - - conf.set("CUSTOM_PRESET_6", "gameMode", str(modTable[self.comboBox_gamemode6.currentIndex()])) - conf.set("CUSTOM_PRESET_6", "row", str(self.spinBox_height6.value())) - conf.set("CUSTOM_PRESET_6", "column", str(self.spinBox_width6.value())) - conf.set("CUSTOM_PRESET_6", "pixSize", str(self.spinBox_pixsize6.value())) - conf.set("CUSTOM_PRESET_6", "attempt_times_limit", str(self.spinBox_attempt_times_limit6.value())) - conf.set("CUSTOM_PRESET_6", "mineNum", str(self.spinBox_minenum6.value())) - conf.set("CUSTOM_PRESET_6", "board_constraint", self.lineEdit_constraint6.text()) - conf.write(open(self.game_setting_path, "w", encoding='utf-8')) + self.game_setting.set_value("CUSTOM_PRESET_4/gamemode", modTable[self.comboBox_gamemode4.currentIndex()]) + self.game_setting.set_value("CUSTOM_PRESET_4/board_constraint", self.lineEdit_constraint4.text()) + self.game_setting.set_value("CUSTOM_PRESET_4/attempt_times_limit", self.spinBox_attempt_times_limit4.value()) + self.game_setting.set_value("CUSTOM_PRESET_4/pixsize", self.spinBox_pixsize4.value()) + self.game_setting.set_value("CUSTOM_PRESET_4/row", self.spinBox_height4.value()) + self.game_setting.set_value("CUSTOM_PRESET_4/column", self.spinBox_width4.value()) + self.game_setting.set_value("CUSTOM_PRESET_4/mine_num", self.spinBox_minenum4.value()) + + self.game_setting.set_value("CUSTOM_PRESET_5/gamemode", modTable[self.comboBox_gamemode5.currentIndex()]) + self.game_setting.set_value("CUSTOM_PRESET_5/board_constraint", self.lineEdit_constraint5.text()) + self.game_setting.set_value("CUSTOM_PRESET_5/attempt_times_limit", self.spinBox_attempt_times_limit5.value()) + self.game_setting.set_value("CUSTOM_PRESET_5/pixsize", self.spinBox_pixsize5.value()) + self.game_setting.set_value("CUSTOM_PRESET_5/row", self.spinBox_height5.value()) + self.game_setting.set_value("CUSTOM_PRESET_5/column", self.spinBox_width5.value()) + self.game_setting.set_value("CUSTOM_PRESET_5/mine_num", self.spinBox_minenum5.value()) + + self.game_setting.set_value("CUSTOM_PRESET_6/gamemode", modTable[self.comboBox_gamemode6.currentIndex()]) + self.game_setting.set_value("CUSTOM_PRESET_6/board_constraint", self.lineEdit_constraint6.text()) + self.game_setting.set_value("CUSTOM_PRESET_6/attempt_times_limit", self.spinBox_attempt_times_limit6.value()) + self.game_setting.set_value("CUSTOM_PRESET_6/pixsize", self.spinBox_pixsize6.value()) + self.game_setting.set_value("CUSTOM_PRESET_6/row", self.spinBox_height6.value()) + self.game_setting.set_value("CUSTOM_PRESET_6/column", self.spinBox_width6.value()) + self.game_setting.set_value("CUSTOM_PRESET_6/mine_num", self.spinBox_minenum6.value()) self.Dialog.close () diff --git a/src/gameSettings.py b/src/gameSettings.py index 85c4c62..6b3d807 100644 --- a/src/gameSettings.py +++ b/src/gameSettings.py @@ -1,63 +1,47 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ui_gs.ui' -# -# Created by: PyQt5 UI code generator 5.9.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtGui import configparser from ui.ui_gameSettings import Ui_Form from ui.uiComponents import RoundQDialog -from PyQt5.QtCore import QPoint -# from PyQt5.QtWidgets import QWidget, QDialog -import json from country_name import country_name from PyQt5.QtGui import QPixmap class ui_Form(Ui_Form): def __init__(self, mainWindow): # 设置界面的参数,不能用快捷键修改的从配置文件里来;能用快捷键修改的从mainWindow来 - self.game_setting_path = mainWindow.game_setting_path + self.game_setting = mainWindow.game_setting self.r_path = mainWindow.r_path - config = configparser.ConfigParser() - config.read(self.game_setting_path, encoding='utf-8') + # config = configparser.ConfigParser() + # config.read(self.game_setting_path, encoding='utf-8') self.gameMode = mainWindow.gameMode - self.transparency = config.getint('DEFAULT','transparency') + self.transparency = self.game_setting.value('DEFAULT/transparency', None, int) + # self.transparency = config.getint('DEFAULT','transparency') self.pixSize = mainWindow.pixSize self.row = mainWindow.row self.column = mainWindow.column self.mineNum = mainWindow.mineNum - self.auto_replay = config.getint("DEFAULT", "auto_replay") - self.allow_auto_replay = config.getboolean("DEFAULT", "allow_auto_replay") - self.auto_notification = config.getboolean("DEFAULT", "auto_notification") - # self.board_constraint = config.getboolean("DEFAULT", "board_constraint") - # self.attempt_times_limit = config.getboolean("DEFAULT", "attempt_times_limit") - - self.player_identifier = config["DEFAULT"]["player_identifier"] - self.race_identifier = config["DEFAULT"]["race_identifier"] - self.unique_identifier = config["DEFAULT"]["unique_identifier"] - self.country = config["DEFAULT"]["country"] - self.autosave_video = config.getboolean("DEFAULT", "autosave_video") - self.filter_forever = config.getboolean("DEFAULT", "filter_forever") - # self.auto_show_score = config.getint("DEFAULT", "auto_show_score") # 自动弹成绩 - self.end_then_flag = config.getboolean("DEFAULT", "end_then_flag") # 游戏结束后自动标雷 - self.cursor_limit = config.getboolean("DEFAULT", "cursor_limit") + self.auto_replay = self.game_setting.value('DEFAULT/auto_replay', None, int) + self.auto_notification = self.game_setting.value('DEFAULT/auto_notification', None, bool) + self.player_identifier = self.game_setting.value('DEFAULT/player_identifier', None, str) + self.race_identifier = self.game_setting.value('DEFAULT/race_identifier', None, str) + self.unique_identifier = self.game_setting.value('DEFAULT/unique_identifier', None, str) + self.country = self.game_setting.value('DEFAULT/country', None, str) + self.autosave_video = self.game_setting.value('DEFAULT/autosave_video', None, bool) + self.filter_forever = self.game_setting.value('DEFAULT/filter_forever', None, bool) + self.end_then_flag = self.game_setting.value('DEFAULT/end_then_flag', None, bool) + self.cursor_limit = self.game_setting.value('DEFAULT/cursor_limit', None, bool) self.board_constraint = mainWindow.board_constraint self.attempt_times_limit = mainWindow.attempt_times_limit self.alter = False self.Dialog = RoundQDialog(mainWindow.mainWindow) - # self.Dialog = QDialog() - self.setupUi (self.Dialog) - self.setParameter () - self.Dialog.setWindowIcon (QtGui.QIcon (str(self.r_path.with_name('media').joinpath('cat.ico')))) - self.pushButton_yes.clicked.connect (self.processParameter) - self.pushButton_no.clicked.connect (self.Dialog.close) - self.comboBox_country.resize.connect (self.set_lineedit_country_geometry) + self.setupUi(self.Dialog) + self.setParameter() + self.Dialog.setWindowIcon(QtGui.QIcon (str(self.r_path.with_name('media').joinpath('cat.ico')))) + self.pushButton_yes.clicked.connect(self.processParameter) + self.pushButton_no.clicked.connect(self.Dialog.close) + self.comboBox_country.resize.connect(self.set_lineedit_country_geometry) self.lineEdit_country.textEdited.connect(lambda x: self.set_combobox_country(x)) self.comboBox_country.activated['QString'].connect(lambda x: self.set_country_flag(x)) self.lineEdit_country.textEdited.connect(lambda x: self.set_country_flag(x)) @@ -96,8 +80,8 @@ def set_combobox_country(self, qtext): def setParameter(self): self.spinBox_pixsize.setValue (self.pixSize) - self.spinBox_auto_replay.setValue (self.auto_replay) - self.checkBox_auto_replay.setChecked(self.allow_auto_replay) + self.spinBox_auto_replay.setValue (abs(self.auto_replay)) + self.checkBox_auto_replay.setChecked(self.auto_replay >= 0) self.checkBox_auto_notification.setChecked(self.auto_notification) self.checkBox_autosave_video.setChecked(self.autosave_video) self.checkBox_filter_forever.setChecked(self.filter_forever) @@ -132,8 +116,8 @@ def processParameter(self): self.alter = True self.transparency = self.horizontalSlider_transparency.value() self.pixSize = self.spinBox_pixsize.value() - self.auto_replay = self.spinBox_auto_replay.value() - self.allow_auto_replay = self.checkBox_auto_replay.isChecked() + v = self.spinBox_auto_replay.value() + self.auto_replay = v if self.checkBox_auto_replay.isChecked() else -v self.auto_notification = self.checkBox_auto_notification.isChecked() self.player_identifier = self.lineEdit_label.text() self.race_identifier = self.lineEdit_race_label.text() @@ -150,43 +134,63 @@ def processParameter(self): # 标准、win7、经典无猜、强无猜、弱无猜、准无猜、强可猜、弱可猜 - conf = configparser.ConfigParser() - conf.read(self.game_setting_path, encoding='utf-8') - conf.set("DEFAULT", "gameMode", str(self.gameMode)) - conf.set("DEFAULT", "transparency", str(self.transparency)) - conf.set("DEFAULT", "pixSize", str(self.pixSize)) - conf.set("DEFAULT", "auto_replay", str(self.auto_replay)) - conf.set("DEFAULT", "allow_auto_replay", str(self.allow_auto_replay)) - conf.set("DEFAULT", "end_then_flag", str(self.end_then_flag)) - conf.set("DEFAULT", "cursor_limit", str(self.cursor_limit)) - conf.set("DEFAULT", "auto_notification", str(self.auto_notification)) - conf.set("DEFAULT", "autosave_video", str(self.autosave_video)) - conf.set("DEFAULT", "filter_forever", str(self.filter_forever)) - # conf.set("DEFAULT", "board_constraint", str(self.board_constraint)) - # conf.set("DEFAULT", "attempt_times_limit", str(self.attempt_times_limit)) - conf.set("DEFAULT", "player_identifier", str(self.player_identifier)) - conf.set("DEFAULT", "race_identifier", str(self.race_identifier)) - conf.set("DEFAULT", "unique_identifier", str(self.unique_identifier)) - conf.set("DEFAULT", "country", str(self.country)) - # conf.write(open(self.game_setting_path, "w", encoding='utf-8')) + self.game_setting.set_value("DEFAULT/transparency", self.transparency) + self.game_setting.set_value("DEFAULT/auto_replay", self.auto_replay) + self.game_setting.set_value("DEFAULT/end_then_flag", self.end_then_flag) + self.game_setting.set_value("DEFAULT/cursor_limit", self.cursor_limit) + self.game_setting.set_value("DEFAULT/auto_notification", self.auto_notification) + self.game_setting.set_value("DEFAULT/autosave_video", self.autosave_video) + self.game_setting.set_value("DEFAULT/filter_forever", self.filter_forever) + self.game_setting.set_value("DEFAULT/player_identifier", self.player_identifier) + self.game_setting.set_value("DEFAULT/race_identifier", self.race_identifier) + self.game_setting.set_value("DEFAULT/unique_identifier", self.unique_identifier) + self.game_setting.set_value("DEFAULT/country", self.country) if (self.row, self.column, self.mineNum) == (8, 8, 10): - conf.set("BEGINNER", "board_constraint", str(self.board_constraint)) - conf.set("BEGINNER", "attempt_times_limit", str(self.attempt_times_limit)) - conf.set("BEGINNER", "pixSize", str(self.pixSize)) + self.game_setting.set_value("BEGINNER/gamemode", self.gameMode) + self.game_setting.set_value("BEGINNER/board_constraint", self.board_constraint) + self.game_setting.set_value("BEGINNER/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("BEGINNER/pixsize", self.pixSize) elif (self.row, self.column, self.mineNum) == (16, 16, 40): - conf.set("INTERMEDIATE", "board_constraint", str(self.board_constraint)) - conf.set("INTERMEDIATE", "attempt_times_limit", str(self.attempt_times_limit)) - conf.set("INTERMEDIATE", "pixSize", str(self.pixSize)) + self.game_setting.set_value("INTERMEDIATE/gamemode", self.gameMode) + self.game_setting.set_value("INTERMEDIATE/board_constraint", self.board_constraint) + self.game_setting.set_value("INTERMEDIATE/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("INTERMEDIATE/pixsize", self.pixSize) elif (self.row, self.column, self.mineNum) == (16, 30, 99): - conf.set("EXPERT", "board_constraint", str(self.board_constraint)) - conf.set("EXPERT", "attempt_times_limit", str(self.attempt_times_limit)) - conf.set("EXPERT", "pixSize", str(self.pixSize)) + self.game_setting.set_value("EXPERT/gamemode", self.gameMode) + self.game_setting.set_value("EXPERT/board_constraint", self.board_constraint) + self.game_setting.set_value("EXPERT/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("EXPERT/pixsize", self.pixSize) + elif (self.row, self.column, self.mineNum) ==\ + (self.game_setting.value("CUSTOM_PRESET_4/row", None, int), + self.game_setting.value("CUSTOM_PRESET_4/column", None, int), + self.game_setting.value("CUSTOM_PRESET_4/mine_num", None, int)): + self.game_setting.set_value("CUSTOM_PRESET_4/gamemode", self.gameMode) + self.game_setting.set_value("CUSTOM_PRESET_4/board_constraint", self.board_constraint) + self.game_setting.set_value("CUSTOM_PRESET_4/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("CUSTOM_PRESET_4/pixsize", self.pixSize) + elif (self.row, self.column, self.mineNum) ==\ + (self.game_setting.value("CUSTOM_PRESET_5/row", None, int), + self.game_setting.value("CUSTOM_PRESET_5/column", None, int), + self.game_setting.value("CUSTOM_PRESET_5/mine_num", None, int)): + self.game_setting.set_value("CUSTOM_PRESET_5/gamemode", self.gameMode) + self.game_setting.set_value("CUSTOM_PRESET_5/board_constraint", self.board_constraint) + self.game_setting.set_value("CUSTOM_PRESET_5/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("CUSTOM_PRESET_5/pixsize", self.pixSize) + elif (self.row, self.column, self.mineNum) ==\ + (self.game_setting.value("CUSTOM_PRESET_6/row", None, int), + self.game_setting.value("CUSTOM_PRESET_6/column", None, int), + self.game_setting.value("CUSTOM_PRESET_6/mine_num", None, int)): + self.game_setting.set_value("CUSTOM_PRESET_6/gamemode", self.gameMode) + self.game_setting.set_value("CUSTOM_PRESET_6/board_constraint", self.board_constraint) + self.game_setting.set_value("CUSTOM_PRESET_6/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("CUSTOM_PRESET_6/pixsize", self.pixSize) else: - conf.set("CUSTOM", "board_constraint", str(self.board_constraint)) - conf.set("CUSTOM", "attempt_times_limit", str(self.attempt_times_limit)) - conf.set("CUSTOM", "pixSize", str(self.pixSize)) - conf.write(open(self.game_setting_path, "w", encoding='utf-8')) + self.game_setting.set_value("CUSTOM/gamemode", self.gameMode) + self.game_setting.set_value("CUSTOM/board_constraint", self.board_constraint) + self.game_setting.set_value("CUSTOM/attempt_times_limit", self.attempt_times_limit) + self.game_setting.set_value("CUSTOM/pixsize", self.pixSize) + self.game_setting.sync() self.Dialog.close () diff --git a/src/main.py b/src/main.py index 90d86fe..06366ca 100644 --- a/src/main.py +++ b/src/main.py @@ -1,23 +1,29 @@ # from fbs_runtime.application_context.PyQt5 import ApplicationContext import time +import time from PyQt5 import QtWidgets from PyQt5 import QtCore -from PyQt5.QtWidgets import QApplication,QMessageBox -from PyQt5.QtNetwork import QLocalSocket,QLocalServer -import sys, os +from PyQt5.QtWidgets import QApplication, QMessageBox +from PyQt5.QtNetwork import QLocalSocket, QLocalServer +from PyQt5.QtWidgets import QApplication +from PyQt5.QtNetwork import QLocalSocket, QLocalServer +import sys +import os import mainWindowGUI as mainWindowGUI import mineSweeperGUI as mineSweeperGUI import ctypes from ctypes import wintypes os.environ["QT_FONT_DPI"] = "96" -def on_new_connection(localServer:QLocalServer): + +def on_new_connection(localServer: QLocalServer): """当新连接进来时,接受连接并将文件路径传递给主窗口""" socket = localServer.nextPendingConnection() if socket: socket.readyRead.connect(lambda: on_ready_read(socket)) -def on_ready_read(socket:QLocalSocket): + +def on_ready_read(socket: QLocalSocket): """从socket读取文件路径并传递给主窗口""" if socket and socket.state() == QLocalSocket.ConnectedState: # 读取文件路径并调用打开文件 @@ -29,21 +35,39 @@ def on_ready_read(socket:QLocalSocket): socket.disconnectFromServer() # 断开连接 +def on_new_connection(localServer: QLocalServer): + """当新连接进来时,接受连接并将文件路径传递给主窗口""" + socket = localServer.nextPendingConnection() + if socket: + socket.readyRead.connect(lambda: on_ready_read(socket)) + + +def on_ready_read(socket: QLocalSocket): + """从socket读取文件路径并传递给主窗口""" + if socket and socket.state() == QLocalSocket.ConnectedState: + # 读取文件路径并调用打开文件 + socket.waitForReadyRead(500) + file_path = socket.readAll().data().decode() + for win in QApplication.topLevelWidgets(): + if isinstance(win, mainWindowGUI.MainWindow): + win.dropFileSignal.emit(file_path) + socket.disconnectFromServer() # 断开连接 + def find_window(class_name, window_name): """ 查找指定窗口的句柄。 - + Args: class_name (str): 要查找的窗口的类名。 window_name (str): 要查找的窗口的标题。 - + Returns: int: 查找到的窗口的句柄。如果未找到窗口,则抛出异常。 - + Raises: ctypes.WinError: 如果未找到指定窗口,则抛出此异常。 - + """ user32 = ctypes.WinDLL('user32', use_last_error=True) user32.FindWindowW.argtypes = [wintypes.LPCWSTR, wintypes.LPCWSTR] @@ -57,35 +81,23 @@ def find_window(class_name, window_name): if __name__ == "__main__": # QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - try: - app = QtWidgets.QApplication (sys.argv) - serverName = "MineSweeperServer" - socket = QLocalSocket() - socket.connectToServer(serverName) - if socket.waitForConnected(500): - if len(sys.argv) == 2: - filePath = sys.argv[1] - socket.write(filePath.encode()) - socket.flush() - time.sleep(0.5) - app.quit() - else: - localServer = QLocalServer() - localServer.listen(serverName) - localServer.newConnection.connect(lambda: on_new_connection(localServer=localServer)) - mainWindow = mainWindowGUI.MainWindow() - ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) - ui.mainWindow.show() - ui.mainWindow.game_setting_path = ui.game_setting_path - - _translate = QtCore.QCoreApplication.translate - hwnd = find_window(None, _translate("MainWindow", "元扫雷")) - - SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity - ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000011) else 1/0 - ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity(hwnd, 0x00000000) else 1/0 - - sys.exit(app.exec_()) + + app = QtWidgets.QApplication(sys.argv) + mainWindow = mainWindowGUI.MainWindow() + ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) + ui.mainWindow.show() + ui.mainWindow.game_setting_path = ui.game_setting_path + + _translate = QtCore.QCoreApplication.translate + hwnd = find_window(None, _translate("MainWindow", "元扫雷")) + + SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity + ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity( + hwnd, 0x00000011) else 1/0 + ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity( + hwnd, 0x00000000) else 1/0 + + sys.exit(app.exec_()) ... except Exception as e: pass @@ -137,6 +149,7 @@ def find_window(class_name, window_name): # bbbv_s, (op_solved), (isl_solved) # 录像静态类:bbbv,op, isl, cell0, cell1, cell2, cell3, cell4, cell5, cell6, # cell7, cell8, fps, (hizi) +# 录像动态类(依赖分析):pluck # 其他类:checksum_ok, race_identifier, mode, is_offical, is_fair # 工具箱中局面状态和鼠标状态的定义: diff --git a/src/mineSweeperGUI.py b/src/mineSweeperGUI.py index 2b9cbbc..67bb848 100644 --- a/src/mineSweeperGUI.py +++ b/src/mineSweeperGUI.py @@ -1,8 +1,8 @@ from PyQt5 import QtCore from PyQt5.QtCore import QTimer, QCoreApplication, Qt, QRect -from PyQt5.QtGui import QPixmap, QKeySequence +from PyQt5.QtGui import QPixmap # from PyQt5.QtWidgets import QLineEdit, QInputDialog, QShortcut -from PyQt5.QtWidgets import QApplication, QFileDialog +from PyQt5.QtWidgets import QApplication, QFileDialog, QWidget import gameDefinedParameter import superGUI, gameAbout, gameSettings, gameSettingShortcuts,\ captureScreen, mine_num_bar, videoControl, gameRecordPop @@ -10,14 +10,14 @@ from githubApi import GitHub, SourceManager import minesweeper_master as mm import ms_toollib as ms -import configparser +# import configparser # from pathlib import Path # import time import os import ctypes import hashlib, uuid # from PyQt5.QtWidgets import QApplication -# from country_name import country_name +from country_name import country_name import metaminesweeper_checksum from mainWindowGUI import MainWindow @@ -34,12 +34,10 @@ def __init__(self, MainWindow: MainWindow, args): self.time_10ms: int = 0 # 已毫秒为单位的游戏时间,全局统一的 self.showTime(self.time_10ms // 100) self.timer_10ms = QTimer() - # 开了高精度反而精度降低 - # self.timer_10ms.setTimerType(Qt.PreciseTimer) self.timer_10ms.setInterval(10) # 10毫秒回调一次的定时器 self.timer_10ms.timeout.connect(self.timeCount) - # text4 = '1' - # self.label_info.setText(text4) + # 开了高精度反而精度降低 + self.timer_10ms.setTimerType(Qt.PreciseTimer) self.mineUnFlagedNum = self.mineNum # 没有标出的雷,显示在左上角 self.showMineNum(self.mineUnFlagedNum) # 在左上角画雷数 @@ -70,8 +68,8 @@ def save_evf_file_integrated(): self.polish_action.triggered.connect(lambda: self.trans_language("pl_PL")) self.german_action.triggered.connect(lambda: self.trans_language("de_DE")) - config = configparser.ConfigParser() - config.read(self.game_setting_path, encoding='utf-8') + # config = configparser.ConfigParser() + # config.read(self.game_setting_path, encoding='utf-8') if (self.row, self.column, self.mineNum) == (8, 8, 10): self.actionChecked('B') @@ -133,34 +131,36 @@ def pixSize(self): @pixSize.setter def pixSize(self, pixSize): pixSize = max(5, pixSize) - if pixSize == self._pixSize: + pixSize = min(255, pixSize) + pixSize = min(32767//self.column, pixSize) + pixSize = min(32767//self.row, pixSize) + if hasattr(self, "_pixSize") and pixSize == self._pixSize: return self.label.set_rcp(self.row, self.column, pixSize) self.label.reloadCellPic(pixSize) if (self.row, self.column, self.mineNum) == (8, 8, 10): - self.predefinedBoardPara[1]['pix_size'] = pixSize + self.predefinedBoardPara[1]['pixsize'] = pixSize elif (self.row, self.column, self.mineNum) == (16, 16, 40): - self.predefinedBoardPara[2]['pix_size'] = pixSize + self.predefinedBoardPara[2]['pixsize'] = pixSize elif (self.row, self.column, self.mineNum) == (16, 30, 99): - self.predefinedBoardPara[3]['pix_size'] = pixSize + self.predefinedBoardPara[3]['pixsize'] = pixSize else: - self.predefinedBoardPara[0]['pix_size'] = pixSize - - self.reimportLEDPic(pixSize) - + self.predefinedBoardPara[0]['pixsize'] = pixSize + self.label.setMinimumSize(QtCore.QSize(pixSize * self.column + 8, pixSize * self.row + 8)) self.label.setMaximumSize(QtCore.QSize(pixSize * self.column + 8, pixSize * self.row + 8)) # self.label.setFixedSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) + self.reimportLEDPic(pixSize) # 重新导入图片,无磁盘io self.label_2.reloadFace(pixSize) self.set_face(14) self.showMineNum(self.mineUnFlagedNum) self.showTime(0) - if pixSize < self._pixSize: + if hasattr(self, "_pixSize") and pixSize < self._pixSize: self._pixSize = pixSize self.minimumWindow() - else: - self._pixSize = pixSize + return + self._pixSize = pixSize @property def gameMode(self): @@ -403,6 +403,7 @@ def mineAreaLeftRelease(self, i, j): self.start_time_unix_2 = QtCore.QDateTime.currentDateTime().\ toMSecsSinceEpoch() self.timer_10ms.start() + # 禁用双击修改指标名称公式 self.score_board_manager.editing_row = -2 self.label.ms_board.step('lr', (i, j)) @@ -683,27 +684,27 @@ def dump_evf_file_data(self): self.label.ms_board.software = superGUI.version self.label.ms_board.mode = self.gameMode - self.label.ms_board.player_identifier = self.player_identifier.encode( "UTF-8" ) - self.label.ms_board.race_identifier = self.race_identifier.encode( "UTF-8" ) - self.label.ms_board.uniqueness_identifier = self.unique_identifier.encode( "UTF-8" ) - self.label.ms_board.country = self.country.encode( "UTF-8" ) + self.label.ms_board.player_identifier = self.player_identifier + self.label.ms_board.race_identifier = self.race_identifier + self.label.ms_board.uniqueness_identifier = self.unique_identifier + self.label.ms_board.country = "XX" if not self.country else country_name[self.country].upper() self.label.ms_board.device_uuid = hashlib.md5(bytes(str(uuid.getnode()).encode())).hexdigest().encode( "UTF-8" ) - self.label.ms_board.generate_evf_v3_raw_data() + self.label.ms_board.generate_evf_v4_raw_data() # 补上校验值 checksum = self.checksum_guard.get_checksum(self.label.ms_board.raw_data[:-1]) - self.label.ms_board.checksum = checksum + self.label.ms_board.checksum_evf_v4 = checksum return elif isinstance(self.label.ms_board, ms.EvfVideo): return elif isinstance(self.label.ms_board, ms.AvfVideo): - self.label.ms_board.generate_evf_v3_raw_data() + self.label.ms_board.generate_evf_v4_raw_data() return elif isinstance(self.label.ms_board, ms.MvfVideo): - self.label.ms_board.generate_evf_v3_raw_data() + self.label.ms_board.generate_evf_v4_raw_data() return elif isinstance(self.label.ms_board, ms.RmvVideo): - self.label.ms_board.generate_evf_v3_raw_data() + self.label.ms_board.generate_evf_v4_raw_data() return @@ -714,26 +715,28 @@ def save_evf_file(self): if not os.path.exists(self.replay_path): os.mkdir(self.replay_path) - if (self.row, self.column, self.mineNum) == (8, 8, 10): + if (self.label.ms_board.row, self.label.ms_board.column, self.label.ms_board.mine_num) == (8, 8, 10): filename_level = "b_" - elif (self.row, self.column, self.mineNum) == (16, 16, 40): + elif (self.label.ms_board.row, self.label.ms_board.column, self.label.ms_board.mine_num) == (16, 16, 40): filename_level = "i_" - elif (self.row, self.column, self.mineNum) == (16, 30, 99): + elif (self.label.ms_board.row, self.label.ms_board.column, self.label.ms_board.mine_num) == (16, 30, 99): filename_level = "e_" else: filename_level = "c_" + if self.game_state == "display" or self.game_state == "showdisplay": + self.label.ms_board.current_time = 999999.9 file_name = self.replay_path + '\\' + filename_level +\ - str(self.gameMode) + '_' +\ + f'{self.label.ms_board.mode}' + '_' +\ f'{self.label.ms_board.rtime:.3f}' +\ '_' + f'{self.label.ms_board.bbbv}' +\ '_' + f'{self.label.ms_board.bbbv_s:.3f}' +\ - '_' + bytes(self.label.ms_board.player_identifier).decode() + '_' + self.label.ms_board.player_identifier if not self.label.ms_board.is_completed: file_name += "_fail" if not self.label.ms_board.is_fair: file_name += "_cheat" - if bytes(self.label.ms_board.software).decode()[0] != "元": + if self.label.ms_board.software[0] != "元": file_name += "_trans" elif not self.checksum_module_ok(): file_name += "_fake" @@ -752,7 +755,8 @@ def gameFailed(self): # 失败后改脸和状态变量 self.set_face(16) - if self.label.ms_board.bbbv_solved / self.label.ms_board.bbbv * 100 <= self.auto_replay: + # “自动重开比例”,大于等于该比例时,不自动重开。负数表示禁用。0相当于禁用,但可以编辑。 + if self.label.ms_board.bbbv_solved / self.label.ms_board.bbbv * 100 < self.auto_replay: self.gameRestart() else: self.gameFinished() @@ -776,7 +780,7 @@ def try_record_pop(self): record_key = "E" LNF = "ENF" else: - raise RuntimeError('没有定义的难度代码') + raise RuntimeError() _translate = QtCore.QCoreApplication.translate @@ -784,7 +788,7 @@ def try_record_pop(self): if self.gameMode == 0: record_key += "FLAG" mode_text = _translate("Form", "标准") - if b.right == 0: + if b.rce == 0: mode_text = _translate("Form", "标准(盲扫)") else: mode_text = _translate("Form", "标准") @@ -810,72 +814,78 @@ def try_record_pop(self): record_key += "WG" mode_text = _translate("Form", "弱可猜") else: - raise RuntimeError('没有定义的模式代码') + raise RuntimeError() - if b.rtime < self.record[record_key]["rtime"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["rtime"] = b.rtime - self.record[LNF]["rtime"] = b.rtime + if b.rtime < self.record_setting.value(f"{record_key}/rtime", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/rtime", b.rtime) + self.record_setting.set_value(f"{LNF}/rtime", b.rtime) else: - self.record[record_key]["rtime"] = b.rtime - elif b.right == 0 and self.gameMode == 0 and b.rtime < self.record[LNF]["rtime"]: - self.record[LNF]["rtime"] = b.rtime + self.record_setting.set_value(f"{record_key}/rtime", b.rtime) + elif b.rce == 0 and self.gameMode == 0 and\ + b.rtime < self.record_setting.value(f"{LNF}/rtime", None, float): + self.record_setting.set_value(f"{LNF}/rtime", b.rtime) nf_items.append(1) else: del_items.append(1) - if b.bbbv_s > self.record[record_key]["bbbv_s"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["bbbv_s"] = b.bbbv_s - self.record[LNF]["bbbv_s"] = b.bbbv_s + if b.bbbv_s > self.record_setting.value(f"{record_key}/bbbv_s", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/bbbv_s", b.bbbv_s) + self.record_setting.set_value(f"{LNF}/bbbv_s", b.bbbv_s) else: - self.record[record_key]["bbbv_s"] = b.bbbv_s - elif b.right == 0 and self.gameMode == 0 and b.bbbv_s > self.record[LNF]["bbbv_s"]: - self.record[LNF]["bbbv_s"] = b.bbbv_s + self.record_setting.set_value(f"{record_key}/bbbv_s", b.bbbv_s) + elif b.rce == 0 and self.gameMode == 0 and\ + b.bbbv_s > self.record_setting.value(f"{LNF}/bbbv_s", None, float): + self.record_setting.set_value(f"{LNF}/bbbv_s", b.bbbv_s) nf_items.append(3) else: del_items.append(3) - if b.stnb > self.record[record_key]["stnb"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["stnb"] = b.stnb - self.record[LNF]["stnb"] = b.stnb + if b.stnb > self.record_setting.value(f"{record_key}/stnb", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/stnb", b.stnb) + self.record_setting.set_value(f"{LNF}/stnb", b.stnb) else: - self.record[record_key]["stnb"] = b.stnb - elif b.right == 0 and self.gameMode == 0 and b.stnb > self.record[LNF]["stnb"]: - self.record[LNF]["stnb"] = b.stnb + self.record_setting.set_value(f"{record_key}/stnb", b.stnb) + elif b.rce == 0 and self.gameMode == 0 and\ + b.stnb > self.record_setting.value(f"{LNF}/stnb", None, float): + self.record_setting.set_value(f"{LNF}/stnb", b.stnb) nf_items.append(5) else: del_items.append(5) - if b.ioe > self.record[record_key]["ioe"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["ioe"] = b.ioe - self.record[LNF]["ioe"] = b.ioe + if b.ioe > self.record_setting.value(f"{record_key}/ioe", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/ioe", b.ioe) + self.record_setting.set_value(f"{LNF}/ioe", b.ioe) else: - self.record[record_key]["ioe"] = b.ioe - elif b.right == 0 and self.gameMode == 0 and b.ioe > self.record[LNF]["ioe"]: - self.record[LNF]["ioe"] = b.ioe + self.record_setting.set_value(f"{record_key}/ioe", b.ioe) + elif b.rce == 0 and self.gameMode == 0 and\ + b.ioe > self.record_setting.value(f"{LNF}/ioe", None, float): + self.record_setting.set_value(f"{LNF}/ioe", b.ioe) nf_items.append(7) else: del_items.append(7) - if b.path < self.record[record_key]["path"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["path"] = b.path - self.record[LNF]["path"] = b.path + if b.path < self.record_setting.value(f"{record_key}/path", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/path", b.path) + self.record_setting.set_value(f"{LNF}/path", b.path) else: - self.record[record_key]["path"] = b.path - elif b.right == 0 and self.gameMode == 0 and b.path < self.record[LNF]["path"]: - self.record[LNF]["path"] = b.path + self.record_setting.set_value(f"{record_key}/path", b.path) + elif b.rce == 0 and self.gameMode == 0 and\ + b.path < self.record_setting.value(f"{LNF}/path", None, float): + self.record_setting.set_value(f"{LNF}/path", b.path) nf_items.append(9) else: del_items.append(9) - if b.rqp < self.record[record_key]["rqp"]: - if b.right == 0 and self.gameMode == 0: - self.record[record_key]["rqp"] = b.rqp - self.record[LNF]["rqp"] = b.rqp + if b.rqp < self.record_setting.value(f"{record_key}/rqp", None, float): + if b.rce == 0 and self.gameMode == 0: + self.record_setting.set_value(f"{record_key}/rqp", b.rqp) + self.record_setting.set_value(f"{LNF}/rqp", b.rqp) else: - self.record[record_key]["rqp"] = b.rqp - elif b.right == 0 and self.gameMode == 0 and b.rqp < self.record[LNF]["rqp"]: - self.record[LNF]["rqp"] = b.rqp + self.record_setting.set_value(f"{record_key}/rqp", b.rqp) + elif b.rce == 0 and self.gameMode == 0 and\ + b.rqp < self.record_setting.value(f"{LNF}/rqp", None, float): + self.record_setting.set_value(f"{LNF}/rqp", b.rqp) nf_items.append(11) else: del_items.append(11) @@ -883,25 +893,25 @@ def try_record_pop(self): # pb相关的弹窗。仅高级(不分FL还是NF) if self.gameMode == 0: if b.level == 3: - if b.rtime < self.record["BEGINNER"][str(b.bbbv)]: - self.record["BEGINNER"][str(b.bbbv)] = b.rtime + if b.rtime < self.record_setting.value(f"BEGINNER/{b.bbbv}", None, float): + self.record_setting.set_value(f"BEGINNER/{b.bbbv}", b.rtime) del_items += [14, 15] else: del_items += [13, 14, 15] elif b.level == 4: - if b.rtime < self.record["INTERMEDIATE"][str(b.bbbv)]: - self.record["INTERMEDIATE"][str(b.bbbv)] = b.rtime + if b.rtime < self.record_setting.value(f"INTERMEDIATE/{b.bbbv}", None, float): + self.record_setting.set_value(f"INTERMEDIATE/{b.bbbv}", b.rtime) del_items += [13, 15] else: del_items += [13, 14, 15] elif b.level == 5: - if b.rtime < self.record["EXPERT"][str(b.bbbv)]: - self.record["EXPERT"][str(b.bbbv)] = b.rtime + if b.rtime < self.record_setting.value(f"EXPERT/{b.bbbv}", None, float): + self.record_setting.set_value(f"EXPERT/{b.bbbv}", b.rtime) del_items += [13, 14] else: del_items += [13, 14, 15] else: - raise RuntimeError('没有定义的难度代码') + raise RuntimeError() else: del_items += [13, 14, 15] @@ -960,14 +970,13 @@ def predefined_Board(self, k): column = self.predefinedBoardPara[k]['column'] mine_num = self.predefinedBoardPara[k]['mine_num'] self.setBoard_and_start(row, column, mine_num) - if self.predefinedBoardPara[k]['pix_size'] != self.pixSize: - self.pixSize = self.predefinedBoardPara[k]['pix_size'] + self.pixSize = self.predefinedBoardPara[k]['pixsize'] if isinstance(self.label.ms_board, ms.BaseVideo): self.label.ms_board.reset(row, column, self.pixSize) else: # 解决播放录像时快捷键切换难度报错 self.label.ms_board = ms.BaseVideo([[0] * column for _ in range(row)], self.pixSize) - self.gameMode = self.predefinedBoardPara[k]['game_mode'] + self.gameMode = self.predefinedBoardPara[k]['gamemode'] self.score_board_manager.with_namespace({ "mode": self.gameMode, }) @@ -1008,26 +1017,26 @@ def setBoard(self, row, column, mineNum): self.mineNum = mineNum if (row, column, mineNum) == (8, 8, 10): self.actionChecked('B') - self.pixSize = self.predefinedBoardPara[1]['pix_size'] - self.gameMode = self.predefinedBoardPara[1]['game_mode'] + self.pixSize = self.predefinedBoardPara[1]['pixsize'] + self.gameMode = self.predefinedBoardPara[1]['gamemode'] self.board_constraint = self.predefinedBoardPara[1]['board_constraint'] self.attempt_times_limit = self.predefinedBoardPara[1]['attempt_times_limit'] elif (row, column, mineNum) == (16, 16, 40): self.actionChecked('I') - self.pixSize = self.predefinedBoardPara[2]['pix_size'] - self.gameMode = self.predefinedBoardPara[2]['game_mode'] + self.pixSize = self.predefinedBoardPara[2]['pixsize'] + self.gameMode = self.predefinedBoardPara[2]['gamemode'] self.board_constraint = self.predefinedBoardPara[2]['board_constraint'] self.attempt_times_limit = self.predefinedBoardPara[2]['attempt_times_limit'] elif (row, column, mineNum) == (16, 30, 99): self.actionChecked('E') - self.pixSize = self.predefinedBoardPara[3]['pix_size'] - self.gameMode = self.predefinedBoardPara[3]['game_mode'] + self.pixSize = self.predefinedBoardPara[3]['pixsize'] + self.gameMode = self.predefinedBoardPara[3]['gamemode'] self.board_constraint = self.predefinedBoardPara[3]['board_constraint'] self.attempt_times_limit = self.predefinedBoardPara[3]['attempt_times_limit'] else: self.actionChecked('C') - self.pixSize = self.predefinedBoardPara[0]['pix_size'] - self.gameMode = self.predefinedBoardPara[0]['game_mode'] + self.pixSize = self.predefinedBoardPara[0]['pixsize'] + self.gameMode = self.predefinedBoardPara[0]['gamemode'] self.board_constraint = self.predefinedBoardPara[0]['board_constraint'] self.attempt_times_limit = self.predefinedBoardPara[0]['attempt_times_limit'] @@ -1053,22 +1062,16 @@ def action_NEvent(self): if ui.alter: self.pixSize = ui.pixSize - # for i in range(4): - # self.predefinedBoardPara[i]['pix_size'] = ui.pixSize - self.gameStart() - self.gameMode = ui.gameMode - if not ui.allow_auto_replay: - self.auto_replay = -1 - else: - self.auto_replay = ui.auto_replay + self.auto_replay = ui.auto_replay self.end_then_flag = ui.end_then_flag self.cursor_limit = ui.cursor_limit self.auto_notification = ui.auto_notification self.player_identifier = ui.player_identifier self.label_info.setText(self.player_identifier) self.race_identifier = ui.race_identifier + # 国家或地区名的全称,例如”中国“ self.country = ui.country self.set_country_flag() self.autosave_video = ui.autosave_video @@ -1079,25 +1082,25 @@ def action_NEvent(self): if (self.row, self.column, self.mineNum) == (8, 8, 10): self.predefinedBoardPara[1]['attempt_times_limit'] = self.attempt_times_limit self.predefinedBoardPara[1]['board_constraint'] = self.board_constraint - self.predefinedBoardPara[1]['game_mode'] = ui.gameMode - # self.predefinedBoardPara[1]['pix_size'] = ui.pixSize + self.predefinedBoardPara[1]['gamemode'] = ui.gameMode elif (self.row, self.column, self.mineNum) == (16, 16, 40): self.predefinedBoardPara[2]['attempt_times_limit'] = self.attempt_times_limit self.predefinedBoardPara[2]['board_constraint'] = self.board_constraint - self.predefinedBoardPara[2]['game_mode'] = ui.gameMode - # self.predefinedBoardPara[2]['pix_size'] = ui.pixSize + self.predefinedBoardPara[2]['gamemode'] = ui.gameMode elif (self.row, self.column, self.mineNum) == (16, 30, 99): self.predefinedBoardPara[3]['attempt_times_limit'] = self.attempt_times_limit self.predefinedBoardPara[3]['board_constraint'] = self.board_constraint - self.predefinedBoardPara[3]['game_mode'] = ui.gameMode - # self.predefinedBoardPara[3]['pix_size'] = ui.pixSize + self.predefinedBoardPara[3]['gamemode'] = ui.gameMode else: self.predefinedBoardPara[0]['attempt_times_limit'] = self.attempt_times_limit self.predefinedBoardPara[0]['board_constraint'] = self.board_constraint - self.predefinedBoardPara[0]['gameMode'] = ui.gameMode - # self.predefinedBoardPara[0]['pix_size'] = ui.pixSize + self.predefinedBoardPara[0]['gamemode'] = ui.gameMode self.mainWindow.setWindowOpacity(ui.transparency / 100) + for child in self.mainWindow.findChildren(QWidget): + # 设置子窗口的透明度 + child.setWindowOpacity(ui.transparency / 100) + self.score_board_manager.with_namespace({ "race_identifier": ui.race_identifier, "mode": self.gameMode, @@ -1107,7 +1110,7 @@ def action_NEvent(self): def action_QEvent(self): # 快捷键设置的回调 self.actionChecked('Q') - ui = gameSettingShortcuts.myGameSettingShortcuts(self.game_setting_path, + ui = gameSettingShortcuts.myGameSettingShortcuts(self.game_setting, self.ico_path, self.r_path, self.mainWindow) ui.Dialog.setModal(True) @@ -1135,10 +1138,10 @@ def action_AEvent(self): def auto_Update(self): data = { "Github": "https://api.github.com/repos/", - # "Gitee": "https://api.gitee.com/repos/", + "Gitee": "https://api.gitee.com/repos/", } update_dialog = CheckUpdateGui(GitHub(SourceManager(data), "eee555", - "Metasweeper", superGUI.version.decode( "UTF-8" ), "(\d+\.\d+\.\d+)"), parent = self) + "Metasweeper", superGUI.version, "(\d+\.\d+\.\d+)"), parent = self) update_dialog.setModal(True) update_dialog.show() update_dialog.exec_() @@ -1277,9 +1280,9 @@ def showScores(self): # self.label.setMouseTracking(True) mineNum = self.mineNum # 删去用户标的雷,因为可能标错 - game_board = list(map(lambda x: list(map(lambda y: min(y, 10), x)), - self.label.ms_board.game_board)) - ans = ms.cal_possibility_onboard(game_board, mineNum) + # game_board = list(map(lambda x: list(map(lambda y: min(y, 10), x)), + # self.label.ms_board.game_board)) + ans = ms.cal_possibility_onboard(self.label.ms_board.game_board, mineNum) self.label.boardPossibility = ans[0] self.label.update() @@ -1307,14 +1310,12 @@ def mineKeyReleaseEvent(self, keyName): def refreshSettingsDefault(self): # 刷新游戏设置.ini里默认部分的设置,与当前游戏里一致, # 除了transparency、mainwintop和mainwinleft - conf = configparser.ConfigParser() - conf.read(self.game_setting_path, encoding='utf-8') - conf.set("DEFAULT", "gamemode", str(self.gameMode)) - conf.set("DEFAULT", "pixsize", str(self.pixSize)) - conf.set("DEFAULT", "row", str(self.row)) - conf.set("DEFAULT", "column", str(self.column)) - conf.set("DEFAULT", "minenum", str(self.mineNum)) - conf.write(open(self.game_setting_path, "w", encoding='utf-8')) + self.game_setting.set_value("DEFAULT/gamemode", str(self.gameMode)) + self.game_setting.set_value("DEFAULT/pixsize", str(self.pixSize)) + self.game_setting.set_value("DEFAULT/row", str(self.row)) + self.game_setting.set_value("DEFAULT/column", str(self.column)) + self.game_setting.set_value("DEFAULT/minenum", str(self.minenum)) + self.game_setting.sync() # 打开录像文件的回调 def action_OpenFile(self, openfile_name = None): @@ -1327,15 +1328,19 @@ def action_OpenFile(self, openfile_name = None): return self.set_face(14) - if openfile_name[-3:] == "avf": - video = ms.AvfVideo(openfile_name) - elif openfile_name[-3:] == "rmv": - video = ms.RmvVideo(openfile_name) - elif openfile_name[-3:] == "evf": - video = ms.EvfVideo(openfile_name) - elif openfile_name[-3:] == "mvf": - video = ms.MvfVideo(openfile_name) - else: + try: + if openfile_name[-3:] == "avf": + video = ms.AvfVideo(openfile_name) + elif openfile_name[-3:] == "rmv": + video = ms.RmvVideo(openfile_name) + elif openfile_name[-3:] == "evf": + # evf版本过时以后会引发异常 + video = ms.EvfVideo(openfile_name) + elif openfile_name[-3:] == "mvf": + video = ms.MvfVideo(openfile_name) + else: + return + except: return self.play_video(video) @@ -1366,11 +1371,10 @@ def play_video(self, video): "mouse_trace", "vision_transfer", "survive_poss"]) # 组织录像评论 - event_len = video.events_len comments = [] - for event_id in range(event_len): - t = video.events_time(event_id) - comment = video.events_comments(event_id) + for event in video.events: + t = event.time + comment = event.comments if comment: comments.append((t, [i.split(': ') for i in comment.split(';')[:-1]])) # 调整窗口 @@ -1388,7 +1392,8 @@ def play_video(self, video): self.timer_video = QTimer() self.timer_video.timeout.connect(self.video_playing_step) - self.ui_video_control = videoControl.ui_Form(self.r_path, video, comments, self.mainWindow) + self.ui_video_control = videoControl.ui_Form(self.r_path, video, comments, + self.game_setting, self.mainWindow) # self.mainWindow.closeEvent_.connect(self.ui_video_control.QWidget.close) self.ui_video_control.pushButton_play.clicked.connect(self.video_play) self.ui_video_control.pushButton_replay.clicked.connect(self.video_replay) @@ -1405,14 +1410,18 @@ def play_video(self, video): self.video_time_step = 0.01 # 录像时间的步长,定时器始终是10毫秒 self.label.paint_cursor = True self.video_playing = True # 录像正在播放 - self.timer_video.start(10) + # 禁用双击修改指标名称公式 + self.score_board_manager.editing_row = -2 video.video_playing_pix_size = self.label.pixSize self.label.ms_board = video # 改成录像的标识 - self.label_info.setText(bytes(self.label.ms_board.player_identifier).decode()) + # print(self.label.ms_board.player_identifier) + self.label_info.setText(self.label.ms_board.player_identifier) # 改成录像的国旗 - self.set_country_flag(bytes(self.label.ms_board.country).decode()) + self.set_country_flag(self.label.ms_board.country) + + self.timer_video.start(10) def video_playing_step(self): @@ -1422,11 +1431,11 @@ def video_playing_step(self): self.timer_video.stop() self.video_playing = False self.label.update() - self.score_board_manager.show(self.label.ms_board, index_type = 2) + self.score_board_manager.show(self.label.ms_board, index_type = 3) self.video_time += self.video_time_step self.showTime(int(self.video_time)) self.ui_video_control.horizontalSlider_time.blockSignals(True) - self.ui_video_control.horizontalSlider_time.setValue(int(self.video_time * 100)) + self.ui_video_control.horizontalSlider_time.setValue(int(self.video_time * 1000)) self.ui_video_control.horizontalSlider_time.blockSignals(False) self.ui_video_control.doubleSpinBox_time.blockSignals(True) self.ui_video_control.doubleSpinBox_time.setValue(self.label.ms_board.time) @@ -1453,15 +1462,15 @@ def video_set_speed(self, speed): def video_set_time(self, t): # 把录像定位到某一个时刻。是拖动进度条的回调 - self.video_time = t / 100 + self.video_time = t / 1000 self.label.ms_board.current_time = self.video_time self.label.update() - self.score_board_manager.show(self.label.ms_board, index_type = 2) + self.score_board_manager.show(self.label.ms_board, index_type = 3) def video_set_a_time(self, t): # 把录像定位到某一段时间,默认前后一秒,自动播放。是点录像事件的回调 - self.video_time = (t - 100) / 100 - self.video_stop_time = (t + 100) / 100 # 大了也没关系,ms_toollib自动处理 + self.video_time = (t - 1000) / 1000 + self.video_stop_time = (t + 1000) / 1000 # 大了也没关系,ms_toollib自动处理 self.timer_video.start() self.video_playing = True @@ -1531,37 +1540,28 @@ def closeEvent_(self): # 主窗口关闭的回调 self.unlimit_cursor() # self.score_board_manager.close() - conf = configparser.ConfigParser() - conf.read(self.game_setting_path, encoding='utf-8') - # conf.set("DEFAULT", "gamemode", str(self.gameMode)) - conf.set("DEFAULT", "mainWinTop", str(self.mainWindow.x())) - conf.set("DEFAULT", "mainWinLeft", str(self.mainWindow.y())) - # conf.set("DEFAULT", "pixsize", str(self.pixSize)) - conf.set("DEFAULT", "row", str(self.row)) - conf.set("DEFAULT", "column", str(self.column)) - conf.set("DEFAULT", "minenum", str(self.mineNum)) + self.game_setting.set_value("DEFAULT/mainWinTop", str(self.mainWindow.x())) + self.game_setting.set_value("DEFAULT/mainWinLeft", str(self.mainWindow.y())) + self.game_setting.set_value("DEFAULT/row", str(self.row)) + self.game_setting.set_value("DEFAULT/column", str(self.column)) + self.game_setting.set_value("DEFAULT/mineNum", str(self.mineNum)) if (self.row, self.column, self.mineNum) == (8, 8, 10): - conf.set("BEGINNER", "gamemode", str(self.gameMode)) - conf.set("BEGINNER", "pixsize", str(self.pixSize)) + self.game_setting.set_value("BEGINNER/gamemode", str(self.gameMode)) + self.game_setting.set_value("BEGINNER/pixsize", str(self.pixSize)) elif (self.row, self.column, self.mineNum) == (16, 16, 40): - conf.set("INTERMEDIATE", "gamemode", str(self.gameMode)) - conf.set("INTERMEDIATE", "pixsize", str(self.pixSize)) + self.game_setting.set_value("INTERMEDIATE/gamemode", str(self.gameMode)) + self.game_setting.set_value("INTERMEDIATE/pixsize", str(self.pixSize)) elif (self.row, self.column, self.mineNum) == (16, 30, 99): - conf.set("EXPERT", "gamemode", str(self.gameMode)) - conf.set("EXPERT", "pixsize", str(self.pixSize)) + self.game_setting.set_value("EXPERT/gamemode", str(self.gameMode)) + self.game_setting.set_value("EXPERT/pixsize", str(self.pixSize)) else: - conf.set("CUSTOM", "gamemode", str(self.gameMode)) - conf.set("CUSTOM", "pixsize", str(self.pixSize)) - - conf.write(open(self.game_setting_path, "w", encoding='utf-8')) - - conf = configparser.ConfigParser() - conf.read(self.record_path, encoding='utf-8') - for key_name in self.record_key_name_list: - conf[key_name] = self.record[key_name] - with open(self.record_path, 'w') as configfile: - conf.write(configfile) # 将对象写入文件 + self.game_setting.set_value("CUSTOM/gamemode", str(self.gameMode)) + self.game_setting.set_value("CUSTOM/pixsize", str(self.pixSize)) + + self.game_setting.sync() + self.record_setting.sync() + diff --git a/src/minesweeper_master.py b/src/minesweeper_master.py index 5456012..a35708d 100644 --- a/src/minesweeper_master.py +++ b/src/minesweeper_master.py @@ -412,12 +412,12 @@ def debug_ms_board(ms_board): -def updata_ini(file_name: str, data): - conf = configparser.ConfigParser() - conf.read(file_name, encoding='utf-8') - for i in data: - conf.set(i[0], i[1], str(i[2])) - conf.write(open(file_name, "w", encoding='utf-8')) +# def updata_ini(file_name: str, data): +# conf = configparser.ConfigParser() +# conf.read(file_name, encoding='utf-8') +# for i in data: +# conf.set(i[0], i[1], str(i[2])) +# conf.write(open(file_name, "w", encoding='utf-8')) def main(): # # 测试枚举法判雷速度算例 diff --git a/src/superGUI.py b/src/superGUI.py index d79cb42..9c8933c 100644 --- a/src/superGUI.py +++ b/src/superGUI.py @@ -1,22 +1,160 @@ from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt, QSize, QTimer from PyQt5.QtCore import QTranslator -from os import environ +# from os import environ from PyQt5.QtWidgets import QApplication import configparser from PyQt5.QtGui import QPalette, QPixmap, QIcon -# from PyQt5.QtWidgets import QScrollArea -# from PyQt5.QtSvg import QSvgWidget from ui.ui_main_board import Ui_MainWindow from pathlib import Path from gameScoreBoard import gameScoreBoardManager -import minesweeper_master as mm -# import metaminesweeper_checksum from country_name import country_name - - -version = "元3.2.0".encode( "UTF-8" ) - +import os +from typing import List, Tuple + +version = "元3.2.1" + +class IniConfig: + def __init__(self, file_path): + """ + 初始化 IniConfig 对象 + :param file_path: 配置文件的路径 + """ + self.file_path = file_path + # QSettings的键名是无序的,无法使用 + self.config = configparser.ConfigParser() + self.config.default_section = "" + # 如果文件不存在则创建 + if not os.path.exists(file_path): + with open(file_path, 'w', encoding="utf-8"): + pass + # 读取配置文件 + self.config.read(file_path, encoding="utf-8") + + def _parse_key(self, key): + """ + 解析 "section/key" 格式的键,返回节和键 + :param key: 格式为 "section/key" 的键 + :return: 节和键的元组 + """ + parts = key.split('/', 1) + if len(parts) == 2: + section, key = parts + else: + section = 'DEFAULT' + key = parts[0] + return section, key + + def value(self, key, default=None, value_type=str): + """ + 根据键获取值,如果键不存在则返回默认值 + :param key: 格式为 "section/key" 的键 + :param default: 键不存在时的默认值,默认为 None + :param value_type: 值的类型,默认为字符串类型 + :return: 获取到的值或默认值 + """ + section, key = self._parse_key(key) + if not self.config.has_section(section): + return default + if self.config.has_option(section, key): + if value_type == int: + return self.config.getint(section, key) + elif value_type == float: + return self.config.getfloat(section, key) + elif value_type == bool: + return self.config.getboolean(section, key) + else: + return self.config.get(section, key) + else: + return default + + def get_or_set_value(self, key, default=None, value_type=str): + """ + 获取值,如果键不存在则设置默认值并返回 + :param key: 格式为 "section/key" 的键 + :param default: 键不存在时的默认值,默认为 None + :param value_type: 值的类型,默认为字符串类型 + :return: 获取到的值或默认值 + """ + section, key = self._parse_key(key) + value = self.value(key=f"{section}/{key}", default=default, value_type=value_type) + if value == default: + self.set_value(key=f"{section}/{key}", value=default) + return value + + def get_or_set_section(self, section, default: List[Tuple[str,str]], + force_add=False) -> List[Tuple[str,str]]: + """ + 获取或设置 section 的内容。如果 section 为空,则重新设置为默认内容。 + :param section: 要获取或设置的 section 名称 + :param default: 默认的 section 内容,默认为 section_dict + :param force_add: 是否强制补全default中的所有键 + :return: section 的内容 + """ + if not self.config.has_section(section): + self.config.add_section(section) + for (key, value) in default: + self.config.set(section, str(key), str(value)) + return default + if len(self.config[section]) == 0: + for (key, value) in default: + self.config.set(section, str(key), str(value)) + return default + if force_add: + for (key, value) in default: + self.get_or_set_value(f"{section}/{key}", str(value)) + return list(self.config.items(section)) + + def set_value(self, key, value): + """ + 设置键值对 + :param key: 格式为 "section/key" 的键 + :param value: 要设置的值 + """ + section, key = self._parse_key(key) + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, key, str(value)) + + + def set_section(self, section, section_value: List[Tuple[str,str]]): + """ + 设置 section 的内容。全量替换。假如有重复的键,例如key,改为key, key(2), key(3)... + key假如为空字符串,替换为行号的id + :param section: 要获取或设置的 section 名称 + :param section_value: section 内容 + """ + # key假如为空字符串,替换为行号的id + section_value = [(idv, v[1]) if v[0] == "" else v for (idv, v) in enumerate(section_value)] + # 假如有重复的键,例如key,改为key, key(2), key(3)... + key_set = set() + for (idv, v) in enumerate(section_value): + key = v[0] + if key not in key_set: + key_set.add(key) + else: + idx = 2 + while (new_key := key + f"({idx})") in key_set: + idx += 1 + key_set.add(new_key) + section_value[idv] = (new_key, v[1]) + + if not self.config.has_section(section): + self.config.add_section(section) + + # 遍历并删除每个 option + for option in list(self.config[section].keys()): + self.config.remove_option(section, option) + + for (key, value) in section_value: + self.set_value(f"{section}/{key}", str(value)) + + def sync(self): + """ + 将内存中的配置写入文件 + """ + with open(self.file_path, 'w', encoding="utf-8") as f: + self.config.write(f) class Ui_MainWindow(Ui_MainWindow): minimum_counter = 0 # 最小化展示窗口有关 @@ -32,9 +170,11 @@ def __init__(self, MainWindow, args): # 录像保存位置 self.replay_path = str(r_path.with_name('replay')) # 记录了全局游戏设置 - self.game_setting_path = str(r_path.with_name('gameSetting.ini')) + game_setting_path = str(r_path.with_name('gameSetting.ini')) + self.game_setting = IniConfig(game_setting_path) # 个人记录,用来弹窗 - self.record_path = str(r_path.with_name('record.ini')) + record_path = str(r_path.with_name('record.ini')) + self.record_setting = IniConfig(record_path) self.ico_path = str(r_path.with_name('media').joinpath('cat.ico')) @@ -65,7 +205,7 @@ def __init__(self, MainWindow, args): self.read_or_create_record() self.label.setPath(r_path) self.label_2.setPath(r_path) - _scoreBoardTop, _scoreBoardLeft = self.read_or_create_game_setting() + self.read_or_create_game_setting() self.initMineArea() @@ -77,13 +217,14 @@ def __init__(self, MainWindow, args): # 记录了计数器的配置,显示哪些指标等等 score_board_path = str(r_path.with_name('scoreBoardSetting.ini')) - self.score_board_manager = gameScoreBoardManager(r_path, score_board_path, - self.game_setting_path, + self.score_board_setting = IniConfig(score_board_path) + self.score_board_manager = gameScoreBoardManager(r_path, self.score_board_setting, + self.game_setting, self.pixSize, MainWindow) - self.score_board_manager.ui.QWidget.move(_scoreBoardTop, _scoreBoardLeft) + # self.score_board_manager.ui.QWidget.move(_scoreBoardTop, _scoreBoardLeft) - self.importLEDPic(self.pixSize) # 导入图片 + # self.importLEDPic() # 导入图片 # self.label.setPath(r_path) @@ -181,79 +322,95 @@ def importLEDPic(self, pixSize): def reimportLEDPic(self, pixSize): # 重新将资源的尺寸缩放到希望的尺寸、比例 - self.pixmapNum = {key:value.copy().scaled(int(pixSize * 1.5), int(pixSize * 1.5)) for key,value in self.pixmapNumPix.items()} - self.pixmapLEDNum = {key:value.copy().scaled(pixSize, int(pixSize * 1.75)) for key,value in self.pixmapLEDNumPix.items()} + if hasattr(self, "pixmapNumPix"): + self.pixmapNum = {key:value.copy().scaled(int(pixSize * 1.5), int(pixSize * 1.5)) + for key,value in self.pixmapNumPix.items()} + self.pixmapLEDNum = {key:value.copy().scaled(pixSize, int(pixSize * 1.75)) + for key,value in self.pixmapLEDNumPix.items()} + else: + self.importLEDPic(pixSize) def readPredefinedBoardPara(self): # 从配置中更新出快捷键1, 2, 3, 4、5、6的定义(0是自定义) - config = configparser.ConfigParser() - config.read(self.game_setting_path, encoding='utf-8') - self.predefinedBoardPara[0] = { - "game_mode": config.getint('CUSTOM','gamemode'), - "row": 8, - "column": 8, - "pix_size": config.getint('CUSTOM','pixsize'), - "mine_num": 10, - "board_constraint": config["CUSTOM"]["board_constraint"], - "attempt_times_limit": config.getint('CUSTOM','attempt_times_limit'), - } - self.predefinedBoardPara[1] = { - "game_mode": config.getint('BEGINNER','gamemode'), - "row": 8, - "column": 8, - "pix_size": config.getint('BEGINNER','pixsize'), - "mine_num": 10, - "board_constraint": config["BEGINNER"]["board_constraint"], - "attempt_times_limit": config.getint('BEGINNER','attempt_times_limit'), - } - self.predefinedBoardPara[2] = { - "game_mode": config.getint('INTERMEDIATE','gamemode'), - "row": 16, - "column": 16, - "pix_size": config.getint('INTERMEDIATE','pixsize'), - "mine_num": 40, - "board_constraint": config["INTERMEDIATE"]["board_constraint"], - "attempt_times_limit": config.getint('INTERMEDIATE','attempt_times_limit'), - } - self.predefinedBoardPara[3] = { - "game_mode": config.getint('EXPERT','gamemode'), - "row": 16, - "column": 30, - "pix_size": config.getint('EXPERT','pixsize'), - "mine_num": 99, - "board_constraint": config["EXPERT"]["board_constraint"], - "attempt_times_limit": config.getint('EXPERT','attempt_times_limit'), - } - self.predefinedBoardPara[4] = { - "game_mode": config.getint('CUSTOM_PRESET_4','gameMode'), - "row": config.getint('CUSTOM_PRESET_4','row'), - "column": config.getint('CUSTOM_PRESET_4','column'), - "pix_size": config.getint('CUSTOM_PRESET_4','pixSize'), - "mine_num": config.getint('CUSTOM_PRESET_4','mineNum'), - "board_constraint": config["CUSTOM_PRESET_4"]["board_constraint"], - "attempt_times_limit": config.getint('CUSTOM_PRESET_4','attempt_times_limit'), - } - - self.predefinedBoardPara[5] = { - "game_mode": config.getint('CUSTOM_PRESET_5','gameMode'), - "row": config.getint('CUSTOM_PRESET_5','row'), - "column": config.getint('CUSTOM_PRESET_5','column'), - "pix_size": config.getint('CUSTOM_PRESET_5','pixSize'), - "mine_num": config.getint('CUSTOM_PRESET_5','mineNum'), - "board_constraint": config["CUSTOM_PRESET_5"]["board_constraint"], - "attempt_times_limit": config.getint('CUSTOM_PRESET_5','attempt_times_limit'), - } - - self.predefinedBoardPara[6] = { - "game_mode": config.getint('CUSTOM_PRESET_6','gameMode'), - "row": config.getint('CUSTOM_PRESET_6','row'), - "column": config.getint('CUSTOM_PRESET_6','column'), - "pix_size": config.getint('CUSTOM_PRESET_6','pixSize'), - "mine_num": config.getint('CUSTOM_PRESET_6','mineNum'), - "board_constraint": config["CUSTOM_PRESET_6"]["board_constraint"], - "attempt_times_limit": config.getint('CUSTOM_PRESET_6','attempt_times_limit'), - } + s = [ + ("gamemode", 0), + ("row", 8), + ("column", 8), + ("pixsize", 20), + ("mine_num", 10), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("CUSTOM", s, True) + self.predefinedBoardPara[0] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 0), + ("row", 8), + ("column", 8), + ("pixsize", 20), + ("mine_num", 10), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("BEGINNER", s, True) + self.predefinedBoardPara[1] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 0), + ("row", 16), + ("column", 16), + ("pixsize", 20), + ("mine_num", 40), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("INTERMEDIATE", s, True) + self.predefinedBoardPara[2] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 0), + ("row", 16), + ("column", 30), + ("pixsize", 20), + ("mine_num", 99), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("EXPERT", s, True) + self.predefinedBoardPara[3] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 5), + ("row", 16), + ("column", 16), + ("pixsize", 20), + ("mine_num", 72), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("CUSTOM_PRESET_4", s, True) + self.predefinedBoardPara[4] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 5), + ("row", 16), + ("column", 30), + ("pixsize", 20), + ("mine_num", 120), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("CUSTOM_PRESET_5", s, True) + self.predefinedBoardPara[5] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + s = [ + ("gamemode", 5), + ("row", 24), + ("column", 36), + ("pixsize", 20), + ("mine_num", 200), + ("board_constraint", ""), + ("attempt_times_limit", 100000), + ] + s = self.game_setting.get_or_set_section("CUSTOM_PRESET_6", s, True) + self.predefinedBoardPara[6] = { k: int(v) if isinstance(v, str) and v.isdigit() else v for (k, v) in s } + def minimumWindow(self): # 最小化展示窗口,并固定尺寸 @@ -286,267 +443,99 @@ def trans_language(self, language = ""): app.removeTranslator(self.trans) self.retranslateUi(self.mainWindow) self.score_board_manager.retranslateUi(self.score_board_manager.ui.QWidget) - mm.updata_ini(self.game_setting_path, [("DEFAULT", "language", language)]) + self.game_setting.set_value("DEFAULT/language", language) + self.game_setting.sync() + # mm.updata_ini(self.game_setting_path, [("DEFAULT", "language", language)]) self.language = language - def read_or_create_game_setting(self): - config = configparser.ConfigParser() - if config.read(self.game_setting_path, encoding='utf-8'): - self.mainWindow.setWindowOpacity((config.getint('DEFAULT', 'transparency') + 1) / 100) - # self._pixSize = config.getint('DEFAULT', 'pixSize') - self.mainWindow.move(config.getint('DEFAULT', 'mainWinTop'), config.getint('DEFAULT', 'mainWinLeft')) - # self.score_board_manager.ui.QWidget.move(config.getint('DEFAULT', 'scoreBoardTop'), - # config.getint('DEFAULT', 'scoreBoardLeft')) - _scoreBoardTop = config.getint('DEFAULT', 'scoreBoardTop') - _scoreBoardLeft = config.getint('DEFAULT', 'scoreBoardLeft') - self.row = config.getint("DEFAULT", "row") - self.column = config.getint("DEFAULT", "column") - - # self.label.set_rcp(self.row, self.column, self.pixSize) - - self.mineNum = config.getint("DEFAULT", "mineNum") - # self.gameMode = config.getint('DEFAULT', 'gameMode') - # 完成度低于该百分比炸雷自动重开 - if config.getboolean("DEFAULT", "allow_auto_replay"): - self.auto_replay = config.getint("DEFAULT", "auto_replay") - else: - self.auto_replay = -1 - self.auto_notification = config.getboolean("DEFAULT", "auto_notification") - - self.player_identifier = config["DEFAULT"]["player_identifier"] - self.race_identifier = config["DEFAULT"]["race_identifier"] - self.unique_identifier = config["DEFAULT"]["unique_identifier"] - self.country = config["DEFAULT"]["country"] - self.autosave_video = config.getboolean("DEFAULT", "autosave_video") - self.filter_forever = config.getboolean("DEFAULT", "filter_forever") - self.language = config["DEFAULT"]["language"] - # self.auto_show_score = config.getint("DEFAULT", "auto_show_score") # 自动弹成绩 - self.end_then_flag = config.getboolean("DEFAULT", "end_then_flag") # 游戏结束后自动标雷 - self.cursor_limit = config.getboolean("DEFAULT", "cursor_limit") - if (self.row, self.column, self.mineNum) == (8, 8, 10): - self._pixSize = config.getint('BEGINNER', 'pixsize') - self.label.set_rcp(self.row, self.column, self.pixSize) - self.gameMode = config.getint('BEGINNER', 'gamemode') - self.board_constraint = config["BEGINNER"]["board_constraint"] - self.attempt_times_limit = config.getint('BEGINNER', 'attempt_times_limit') - elif (self.row, self.column, self.mineNum) == (16, 16, 40): - self._pixSize = config.getint('INTERMEDIATE', 'pixsize') - self.label.set_rcp(self.row, self.column, self.pixSize) - self.gameMode = config.getint('INTERMEDIATE', 'gamemode') - self.board_constraint = config["INTERMEDIATE"]["board_constraint"] - self.attempt_times_limit = config.getint('INTERMEDIATE', 'attempt_times_limit') - elif (self.row, self.column, self.mineNum) == (16, 30, 99): - self._pixSize = config.getint('EXPERT', 'pixsize') - self.label.set_rcp(self.row, self.column, self.pixSize) - self.gameMode = config.getint('EXPERT', 'gamemode') - self.board_constraint = config["EXPERT"]["board_constraint"] - self.attempt_times_limit = config.getint('EXPERT', 'attempt_times_limit') - else: - self._pixSize = config.getint('CUSTOM', 'pixsize') - self.label.set_rcp(self.row, self.column, self.pixSize) - self.gameMode = config.getint('CUSTOM', 'gamemode') - self.board_constraint = config["CUSTOM"]["board_constraint"] - self.attempt_times_limit = config.getint('CUSTOM', 'attempt_times_limit') - else: - # 找不到配置文件就初始化 - self.min3BV = 100 - self.max3BV = 381 - self.mainWindow.setWindowOpacity(1) - self._pixSize = 20 - self.mainWindow.move(100, 200) - _scoreBoardTop = 100 - _scoreBoardLeft = 100 - self.row = 16 - self.column = 30 - self.label.set_rcp(self.row, self.column, self.pixSize) - self.gameMode = 0 - self.mineNum = 99 - self.auto_replay = 30 - self.allow_auto_replay = False - self.auto_notification = True - self.allow_min3BV = False - self.allow_max3BV = False - self.player_identifier = "匿名玩家(anonymous player)" - self.race_identifier = "" - self.unique_identifier = "" - self.country = "" - self.autosave_video = True - self.filter_forever = False - self.end_then_flag = True - self.cursor_limit = False - self.board_constraint = "" - self.attempt_times_limit = 100000 - if environ.get('LANG', None) == "zh_CN": - self.language = "zh_CN" - else: - self.language = "en_US" - config["DEFAULT"] = {'transparency': 100, - 'mainWinTop': 100, - 'mainWinLeft': 200, - 'scoreBoardTop': 100, - 'scoreBoardLeft': 100, - 'row': 16, - 'column': 30, - 'mineNum': 99, - "auto_replay": 30, - "allow_auto_replay": False, - "auto_notification": True, - "player_identifier": "匿名玩家(anonymous player)", - "race_identifier": "", - "unique_identifier": "", - "country": "", - "autosave_video": True, - "filter_forever": False, - "end_then_flag": True, - "cursor_limit": False, - "language": self.language, - } - config["BEGINNER"] = {"gameMode": 0, - "pixSize": 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["INTERMEDIATE"] = {"gameMode": 0, - "pixSize": 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["EXPERT"] = {"gameMode": 0, - "pixSize": 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["CUSTOM"] = {"gameMode": 0, - "pixSize": 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["CUSTOM_PRESET_4"] = {'row': 16, - 'column': 16, - 'minenum': 72, - 'gameMode': 5, - 'pixSize': 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["CUSTOM_PRESET_5"] = {'row': 16, - 'column': 30, - 'minenum': 120, - 'gamemode': 5, - 'pixsize': 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - config["CUSTOM_PRESET_6"] = {'row': 24, - 'column': 36, - 'minenum': 200, - 'gamemode': 5, - 'pixsize': 20, - "board_constraint": "", - "attempt_times_limit": 100000, - } - with open(self.game_setting_path, 'w', encoding='utf-8') as configfile: - config.write(configfile) # 将对象写入文件 - self.label_2.reloadFace(self.pixSize) - return _scoreBoardTop, _scoreBoardLeft + ''' + 读取或创建游戏设置。 + ''' + transparency = self.game_setting.get_or_set_value('DEFAULT/transparency', 100, int) + self.mainWindow.setWindowOpacity(transparency / 100) + mainWinTop = self.game_setting.get_or_set_value("DEFAULT/mainwintop", 100, int) + mainWinLeft = self.game_setting.get_or_set_value("DEFAULT/mainwinleft", 200, int) + self.mainWindow.move(mainWinTop, mainWinLeft) + self.row = self.game_setting.get_or_set_value("DEFAULT/row", 16, int) + self.column = self.game_setting.get_or_set_value("DEFAULT/column", 30, int) + self.mineNum = self.game_setting.get_or_set_value("DEFAULT/mineNum", 99, int) + self.mineUnFlagedNum = self.mineNum + # “自动重开比例”,大于等于该比例时,不自动重开。负数表示禁用。0相当于禁用,但可以编辑。 + self.auto_replay = self.game_setting.get_or_set_value("DEFAULT/auto_replay", 30, int) + # self.allow_auto_replay = self.game_setting.get_or_set_value("DEFAULT/allow_auto_replay", True, bool) + self.auto_notification = self.game_setting.get_or_set_value("DEFAULT/auto_notification", True, bool) + self.player_identifier = self.game_setting.get_or_set_value("DEFAULT/player_identifier", "匿名玩家(anonymous player)", str) + self.race_identifier = self.game_setting.get_or_set_value("DEFAULT/race_identifier", "", str) + self.unique_identifier = self.game_setting.get_or_set_value("DEFAULT/unique_identifier", "", str) + self.country = self.game_setting.get_or_set_value("DEFAULT/country", "", str) + self.autosave_video = self.game_setting.get_or_set_value("DEFAULT/autosave_video", True, bool) + self.filter_forever = self.game_setting.get_or_set_value("DEFAULT/filter_forever", False, bool) + self.language = self.game_setting.get_or_set_value("DEFAULT/language", "en_US", str) + self.end_then_flag = self.game_setting.get_or_set_value("DEFAULT/end_then_flag", True, bool) + self.cursor_limit = self.game_setting.get_or_set_value("DEFAULT/cursor_limit", False, bool) + match (self.row, self.column, self.mineNum): + case (8, 8, 10): + level = "BEGINNER" + case (16, 16, 40): + level = "INTERMEDIATE" + case (16, 30, 99): + level = "EXPERT" + case _: + level = "CUSTOM" + self.pixSize = self.game_setting.get_or_set_value(f"{level}/pixsize", 20, int) + self.label.set_rcp(self.row, self.column, self.pixSize) + self.gameMode = self.game_setting.get_or_set_value(f"{level}/gamemode", 0, int) + self.board_constraint = self.game_setting.get_or_set_value(f"{level}/board_constraint", "", str) + self.attempt_times_limit = self.game_setting.get_or_set_value(f"{level}/attempt_times_limit", 100000, int) + self.game_setting.sync() def read_or_create_record(self): - config = configparser.ConfigParser() record_key_name_list = ["BFLAG", "BNF", "BWIN7", "BSS", "BWS", "BCS", "BTBS", "BSG", "BWG", "IFLAG", "INF", "IWIN7", "ISS", "IWS", "ICS", "ITBS", "ISG", "IWG", "EFLAG", "ENF", "EWIN7", "ESS", "EWS", "ECS", "ETBS", "ESG", "EWG"] self.record_key_name_list = record_key_name_list + ["BEGINNER", "INTERMEDIATE", "EXPERT"] - if config.read(self.record_path): - self.record = {} - for record_key_name in record_key_name_list: - self.record[record_key_name] = {'rtime': config.getfloat(record_key_name, 'rtime'), - 'bbbv_s': config.getfloat(record_key_name, 'bbbv_s'), - 'stnb': config.getfloat(record_key_name, 'stnb'), - 'ioe': config.getfloat(record_key_name, 'ioe'), - 'path': config.getfloat(record_key_name, 'path'), - 'rqp': config.getfloat(record_key_name, 'rqp'), - } - self.record["BEGINNER"] = dict(zip(map(lambda x: str(x), range(1, 55)), - map(lambda x: config.\ - getfloat('BEGINNER', str(x)), - range(1, 55)))) - self.record["INTERMEDIATE"] = dict(zip(map(lambda x: str(x), range(1, 217)), - map(lambda x: config.\ - getfloat('INTERMEDIATE', str(x)), - range(1, 217)))) - self.record["EXPERT"] = dict(zip(map(lambda x: str(x), range(1, 382)), - map(lambda x: config.\ - getfloat('EXPERT', str(x)), - range(1, 382)))) - else: - # 找不到配置文件就初始化 - # 只有标准模式记录pb,且分nf/flag - self.record = {} - record_init_dict = {'rtime': 999.999, - 'bbbv_s': 0.000, - 'stnb': 0.000, - 'ioe': 0.000, - 'path': 999999.999, - 'rqp': 999999.999, - } - for record_key_name in record_key_name_list: - self.record[record_key_name] = record_init_dict.copy() - - self.record["BEGINNER"] = dict.fromkeys(map(lambda x: str(x), range(1, 55)), 999.999) - self.record["INTERMEDIATE"] = dict.fromkeys(map(lambda x: str(x), range(1, 217)), 999.999) - self.record["EXPERT"] = dict.fromkeys(map(lambda x: str(x), range(1, 382)), 999.999) - config["BFLAG"] = self.record["BFLAG"] - config["BNF"] = self.record["BNF"] - config["BWIN7"] = self.record["BWIN7"] - config["BSS"] = self.record["BSS"] - config["BWS"] = self.record["BWS"] - config["BCS"] = self.record["BCS"] - config["BTBS"] = self.record["BTBS"] - config["BSG"] = self.record["BSG"] - config["BWG"] = self.record["BWG"] - - config["IFLAG"] = self.record["IFLAG"] - config["INF"] = self.record["INF"] - config["IWIN7"] = self.record["IWIN7"] - config["ISS"] = self.record["ISS"] - config["IWS"] = self.record["IWS"] - config["ICS"] = self.record["ICS"] - config["ITBS"] = self.record["ITBS"] - config["ISG"] = self.record["ISG"] - config["IWG"] = self.record["IWG"] - - config["EFLAG"] = self.record["EFLAG"] - config["ENF"] = self.record["ENF"] - config["EWIN7"] = self.record["EWIN7"] - config["ESS"] = self.record["ESS"] - config["EWS"] = self.record["EWS"] - config["ECS"] = self.record["ECS"] - config["ETBS"] = self.record["ETBS"] - config["ESG"] = self.record["ESG"] - config["EWG"] = self.record["EWG"] - - config["BEGINNER"] = self.record["BEGINNER"] - config["INTERMEDIATE"] = self.record["INTERMEDIATE"] - config["EXPERT"] = self.record["EXPERT"] - with open(self.record_path, 'w') as configfile: - config.write(configfile) # 将对象写入文件 + # self.record = {} + record_norm = [ + ('rtime', 999.999), + ('bbbv_s', 0.000), + ('stnb', 0.000), + ('ioe', 0.000), + ('path', 999999.999), + ('rqp', 999999.999), + ] + for k in record_key_name_list: + self.record_setting.get_or_set_section(k, record_norm, True) + self.record_setting.get_or_set_section("BEGINNER", + [(i, 999.999) for i in range(1, 55)], + True) + self.record_setting.get_or_set_section("INTERMEDIATE", + [(i, 999.999) for i in range(1, 217)], + True) + self.record_setting.get_or_set_section("EXPERT", + [(i, 999.999) for i in range(1, 382)], + True) + self.record_setting.sync() def set_country_flag(self, country = None): if country == None: country = self.country # 设置右下角国旗图案 if country not in country_name: - self.label_flag.clear() - self.label_flag.update() + file_path = self.r_path.with_name('media') / (country.lower() + ".svg") + if os.path.exists(file_path): + flag_name = file_path + else: + self.label_flag.clear() + self.label_flag.update() + return else: - fn = country_name[country] - pixmap = QPixmap(str(self.r_path.with_name('media') / \ - (fn + ".svg"))).scaled(51, 31) - self.label_flag.setPixmap(pixmap) - self.label_flag.update() + flag_name = self.r_path.with_name('media') / (country_name[country] + ".svg") + pixmap = QPixmap(str(flag_name)).scaled(51, 31) + self.label_flag.setPixmap(pixmap) + self.label_flag.update() diff --git a/src/ui/de_DE.ts b/src/ui/de_DE.ts index 7b58b74..f8dad6c 100644 --- a/src/ui/de_DE.ts +++ b/src/ui/de_DE.ts @@ -3,148 +3,128 @@ Form - - - 游戏设置 - Spiel-Einstellungen - 确定 Bestimmen Sie - + 取消 Stornierung - + 结束后标雷 Markierte Minen nach dem Ende - + 标识: Markierungen: - - 勾选后永远使用筛选法埋雷,否则会智能改用调整法 - Verwenden Sie immer die Screening-Methode für Minen, wenn sie überprüft wird, andernfalls wird auf intelligente Weise zur Anpassungsmethode gewechselt - - - + 永远使用筛选法埋雷(不推荐) Verwenden Sie zum Verlegen von Minen immer eine Abschirmung (nicht empfohlen) - + 自动保存录像(推荐) Automatisches Speichern von Videos (empfohlen) - + 游戏模式: Spielmodus: - + 方格边长: Quadratische Seitenlängen: - + 标准 Standard - + Win7 Win7 - + 强无猜 Stark & Ungeschnitten - + 弱无猜 Schwäche ohne zu raten - + 经典无猜 - Rennen fahren ohne zu raten + - + 准无猜 Nicht erraten - + 强可猜 Starke Vermutung - + 弱可猜 Schwach erratbar - - 允许记录弹窗(推荐) - Aufzeichnungs-Pop-ups zulassen (empfohlen) - - - + 尝试次数: Anzahl der Versuche: - + 局面约束: Situative Zwänge: - + 自动重开: Automatische Wiedereröffnung: - + 国家或地区: Land oder Region: - + 窗口透明度: Transparenz der Fenster: - - - 比赛后缀: - Suffix anpassen: - 关于 Über - + 项目主页: Projekt-Homepage: - + 资料教程: Informations-Tutorials: - + ①本软件可以不受任何限制地复制、储存、传播。 ②任何人可以在任何一个项目中使用本项目源代码的任何一个部分,同时欢迎在本项目主页提出宝贵的意见。 ①Diese Software kann ohne jegliche Einschränkungen kopiert, gespeichert und verteilt werden. @@ -156,100 +136,70 @@ Space - + 自定义设置 Benutzerdefinierte Einstellungen - + 行数(row) Anzahl der Zeilen - + 列数(column) Anzahl der Spalten - + 雷数(number) Der Donner zählt - - - 快捷键设置 - Shortcut-Einstellungen - 模式: Modus: - + 雷数: Anzahl der Donner: - + 高度: Höhe: - + 宽度: Breite: - + 边长: Seitenlänge: - + 快捷键[4] Tastenkombinationen [4] - + 快捷键[5] Tastenkombinationen [5] - + 快捷键[6] Tastenkombinationen [6] - + 计数器 Zähler - - - 新建行 - Neubau Bank - - - - 1 - 1 - - - - 3 - 3 - - - - 指标 - Indikatoren - - - - 表达式 - Ausdrücke - 恭喜打破: @@ -311,176 +261,171 @@ Fortgeschrittene - + 勾选后永远使用筛选法埋雷,否则会适时改用调整法 - + 完成后自动将录像保存到replay文件夹下 - - 经典无猜 - - - - + 允许纪录弹窗(推荐) - + 用于参加比赛 - + 比赛标识: - + 光标不能超出边框 - + 用于与其他人相区分,但不希望排名网站和软件展示出来 - + 个性标识: + + + 重播 + + + + + 播放/暂停 + + + + + 滑动滚轮修改播放速度 + + + + + 时间 + + + + + 标签 + + + + + 事件 + + MainWindow - + 元扫雷 Meta Minesweeper - + 游戏 Spiele - + 设置 Einstellungen - + 语言设置 Spracheinstellungen - + 帮助 Hilfe - + 新游戏 Neue Spiele - + 初级 Junior - + 中级 Zwischenbericht - + 高级 Fortgeschrittene - + 自定义 Personalisierung - + 退出 Rücknahme - + 游戏设置 Spiel-Einstellungen - + 关于 Über - - 词典 - Wörterbuch - - - - D - D - - - - 入门教程 - Einführendes Tutorial - - - - R - R - - - + 快捷键设置 Shortcut-Einstellungen - + 打开 Öffnen Sie - - English - English - - - - 中文 - 中文 - - - + 鼠标设置 Maus-Einstellungen - + 保存 - - Ctrl+S + + 回放 - - 回放 + + 检查更新 diff --git a/src/ui/en_US.ts b/src/ui/en_US.ts index 9bfe286..e5611d3 100644 --- a/src/ui/en_US.ts +++ b/src/ui/en_US.ts @@ -3,133 +3,103 @@ Form - - - 游戏设置 - Game Settings - 确定 OK - + 取消 Cancel - - 窗口透明度 - Window transparency - - - - % - % - - - + 结束后标雷 Fill Finished Board With Flags - + 标识: User identifier: - - 比赛后缀: - Tournament: - - - - 勾选后永远使用筛选法埋雷,否则会智能改用调整法 - When ticked, always use filtering algorithm to generate boards, otherwise the game will decide to use the adjustment algorithm - - - + 永远使用筛选法埋雷(不推荐) Use filtering algorithm (not recommended) - + 自动保存录像(推荐) Auto save video (recommended) - - 国家: - Country: - - - + 游戏模式: Mode: - + 方格边长: Zoom: - + 标准 Standard - + Win7 Win7 - + 强无猜 Strict no guess - + 弱无猜 Weak no guess - + 经典无猜 Classic no guess - + 准无猜 Blessing mode - + 强可猜 Guessable no guess - + 弱可猜 Lucky mode - + 自动重开: Auto restart: - + 允许纪录弹窗(推荐) Allow popovers for best scores (recommended) - + 尝试次数: Attempts: - + 局面约束: Constraint: @@ -144,262 +114,102 @@ Space - - <html><head/><body><p><span style=" font-size:14pt; font-weight:600; color:#000000;">项目主页:</span></p></body></html> - <html><head/><body><p><span style=" font-size:14pt; font-weight:600; color:#000000;">Home:</span></p></body></html> - - - - <html><head/><body><p><a href="https://github.com/eee555/Solvable-Minesweeper"><span style=" font-size:12pt; font-weight:600; text-decoration: underline; color:#0000ff;">https://github.com/eee555/Solvable-Minesweeper</span></a></p></body></html> - <html><head/><body><p><a href="https://github.com/eee555/Solvable-Minesweeper"><span style=" font-size:12pt; font-weight:600; text-decoration: underline; color:#0000ff;">https://github.com/eee555/Solvable-Minesweeper</span></a></p></body></html> - - - - <html><head/><body><p><span style=" font-size:14pt; font-weight:600;">资料教程:</span></p></body></html> - <html><head/><body><p><span style=" font-size:14pt; font-weight:600;">Tutorials:</span></p></body></html> - - - - <html><head/><body><p><a href="https://github.com/putianyi889/Minesweeper-makes-me-happy/wiki"><span style=" font-size:12pt; font-weight:600; text-decoration: underline; color:#0000ff;">https://github.com/putianyi889/Minesweeper-makes-me-happy/wiki</span></a></p></body></html> - <html><head/><body><p><a href="https://github.com/putianyi889/Minesweeper-makes-me-happy/wiki"><span style=" font-size:12pt; font-weight:600; text-decoration: underline; color:#0000ff;">https://github.com/putianyi889/Minesweeper-makes-me-happy/wiki</span></a></p></body></html> - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">①本软件可以不受任何限制地复制、储存、传播。</span></p> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">②任何人可以在任何一个项目中使用本项目源代码的任何<br />一个部分,同时欢迎在本项目主页提出宝贵的意见。</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">①This software can be copied, stored and transmitted without any restrictions.</span></p> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">②Anyone can use any part of the project source code in any project, and you are welcome to make valuable comments on the project home page.</span></p></body></html> - - - + 自定义设置 Custom Settings - - - 高度 - Height - - - - 宽度 - Width - - - - 雷数 - Mines - - - - 快捷键设置 - Shortcuts - 模式: Mode: - + 雷数: Mines: - + 高度: Height: - + 宽度: Width: - + 边长: Zoom: - + 快捷键[4] Shortcut [4] - + 快捷键[5] Shortcut [5] - + 快捷键[6] Shortcut [6] - - 允许记录弹窗(推荐) - Allow popovers for best scores (recommended) - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'微软雅黑'; font-size:9pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">①本软件可以不受任何限制地复制、储存、传播。</span></p> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">②任何人可以在任何一个项目中使用本项目源代码的任何一个部分,同时欢迎在本项目主页提出宝贵的意见。</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">①This software can be copied, stored and transmitted without any restrictions.</span></p> -<p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:12pt; font-weight:600;">②Anyone can use any part of the project source code in any project, and you are welcome to make valuable comments on the project home page.</span></p></body></html> - - - + 行数(row) row - + 列数(column) column - + 雷数(number) mine number - + 项目主页: Home: - + 资料教程: Tutorials: - + ①本软件可以不受任何限制地复制、储存、传播。 ②任何人可以在任何一个项目中使用本项目源代码的任何一个部分,同时欢迎在本项目主页提出宝贵的意见。 ①This software can be copied, stored and transmitted without any restrictions. ②Anyone can use any part of the project source code in any project, and you are welcome to make valuable comments on the project home page. - + 国家或地区: Country or region: - + 窗口透明度: Window transparency: - + 计数器 Counters - - - 新建行 - - - - - 1 - 1 - - - - 3 - 3 - - - - 指标 - - - - - 表达式 - - 恭喜打破: Congratulations: - - - Flag Time成绩! - Flag Time Record! - - - - NF Time成绩! - NF Time Record! - - - - Flag 3BV/s成绩! - Flag 3BV/s Record! - - - - NF 3BV/s成绩! - NF 3BV/s Record! - - - - Flag STNB成绩! - Flag STNB Record! - - - - NF STNB成绩! - NF STNB Record! - - - - Flag IOE成绩! - Flag IOE Record! - - - - NF IOE成绩! - NF IOE Record! - - - - Flag Path成绩! - Flag Path Record! - - - - NF Path成绩! - NF Path Record! - - - - Flag RQP成绩! - Flag RQP Record! - - - - NF RQP成绩! - NF RQP Record! - 初级 @@ -421,12 +231,12 @@ p, li { white-space: pre-wrap; } Expert - + 勾选后永远使用筛选法埋雷,否则会适时改用调整法 Tick the box to always use the filtering method to lay mines, otherwise the adjustment method will be used at the right time - + 完成后自动将录像保存到replay文件夹下 After completion, automatically save the recording to the replay folder @@ -466,177 +276,157 @@ p, li { white-space: pre-wrap; } RQP score! - - 经典无猜 - Classic no guess - - - + 用于参加比赛 The credential for participating in the competition - + 比赛标识: Championship identifier: - + 光标不能超出边框 The cursor cannot move out of the border - + 用于与其他人相区分,但不希望排名网站和软件展示出来 Used to distinguish from others, but not intended to be displayed on ranking websites and software - + 个性标识: Unique identifier: + + + 重播 + Replay + + + + 播放/暂停 + Play/Pause + + + + 滑动滚轮修改播放速度 + Scroll the mouse wheel to adjust the playing speed + + + + 时间 + Time + + + + 标签 + Label + + + + 事件 + Event + MainWindow - - 预留、备用 - Reserved - - - + 游戏 Game - + 设置 Options - + 帮助 Help - + 新游戏 New Game - + 初级 Beginner - + 中级 Intermediate - + 高级 Expert - + 自定义 Custom - + 退出 Exit - + 游戏设置 Game Settings - - A - A - - - - 词典 - Dictionary - - - - D - D - - - - 入门教程 - Getting Started - - - - R - R - - - + 快捷键设置 Shortcut Settings - + 打开 Open - + 语言设置 Language - + 元扫雷 Metasweeper - + 关于 About - - English - English - - - - 中文 - 中文 - - - + 鼠标设置 Mouse Settings - - M - M - - - + 保存 Save - - Ctrl+S - Ctrl+S - - - + 回放 Replay + + + 检查更新 + Check update + diff --git a/src/ui/pl_PL.ts b/src/ui/pl_PL.ts index 7164771..8df54f6 100644 --- a/src/ui/pl_PL.ts +++ b/src/ui/pl_PL.ts @@ -3,153 +3,128 @@ Form - - - 游戏设置 - Ustawienia gry - 确定 Czy na pewno - + 取消 Anuluj - + 结束后标雷 Po zakończeniu znaku - + 标识: Logotyp: - - 勾选后永远使用筛选法埋雷,否则会智能改用调整法 - Po sprawdzeniu zawsze używaj metody przesiewania, aby zakopać kopalnię, w przeciwnym razie inteligentnie przełączy się na metodę regulacji - - - + 永远使用筛选法埋雷(不推荐) Zawsze używaj metody przesiewowej do zakopywania min (niezalecane) - + 自动保存录像(推荐) Automatyczne zapisywanie nagrań (zalecane) - + 游戏模式: Tryb gry: - + 方格边长: Długość boku siatki: - + 标准 norma - + Win7 Win7 - + 强无猜 Silny, bez zgadywania - + 弱无猜 Słaby bez zgadywania - + 经典无猜 - Wyścigi bez zgadywania + - + 准无猜 Quasi-zgadywanie - + 强可猜 Mocne odgadnięcie - + 弱可猜 Słabe do odgadnięcia - - 允许记录弹窗(推荐) - Zezwalaj na nagrywanie wyskakujących okienek (zalecane) - - - + 尝试次数: Liczba prób: - + 局面约束: Ograniczenia sytuacyjne: - + 自动重开: Automatyczne ponowne otwarcie: - - % - % - - - + 国家或地区: Kraj lub region: - + 窗口透明度: Przezroczystość okna: - - - 比赛后缀: - Sufiks konkursowy: - 关于 o - + 项目主页: Strona główna projektu: - + 资料教程: Samouczek informacyjny: - + ①本软件可以不受任何限制地复制、储存、传播。 ②任何人可以在任何一个项目中使用本项目源代码的任何一个部分,同时欢迎在本项目主页提出宝贵的意见。 (1) Niniejsze oprogramowanie może być kopiowane, przechowywane i rozpowszechniane bez żadnych ograniczeń. @@ -161,100 +136,70 @@ Space - + 自定义设置 Dostosowywanie ustawień - + 行数(row) Liczba wierszy - + 列数(column) Liczba kolumn - + 雷数(number) Liczba grzmotów - - - 快捷键设置 - Ustawienia skrótu - 模式: Tryb: - + 雷数: Liczba grzmotów: - + 高度: Wysokość: - + 宽度: Szerokość: - + 边长: Długość boku: - + 快捷键[4] Skróty[4] - + 快捷键[5] Skróty[5] - + 快捷键[6] Skróty[6] - + 计数器 lada - - - 新建行 - Tworzenie nowego wiersza - - - - 1 - 1 - - - - 3 - 3 - - - - 指标 - indeks - - - - 表达式 - wyrażenie - 恭喜打破: @@ -316,176 +261,171 @@ Starszy - + 勾选后永远使用筛选法埋雷,否则会适时改用调整法 - + 完成后自动将录像保存到replay文件夹下 - - 经典无猜 - - - - + 允许纪录弹窗(推荐) - + 用于参加比赛 - + 比赛标识: - + 光标不能超出边框 - + 用于与其他人相区分,但不希望排名网站和软件展示出来 - + 个性标识: + + + 重播 + + + + + 播放/暂停 + + + + + 滑动滚轮修改播放速度 + + + + + 时间 + + + + + 标签 + + + + + 事件 + + MainWindow - + 元扫雷 Trałowiec Meta - + 游戏 Gra - + 设置 Zakładać - + 语言设置 Ustawienia językowe - + 帮助 Pomoc - + 新游戏 Nowa gra - + 初级 młodszy - + 中级 pośredni - + 高级 Starszy - + 自定义 Dostosowywania - + 退出 kończyć - + 游戏设置 Ustawienia gry - + 关于 o - - 词典 - słownik - - - - D - D - - - - 入门教程 - Samouczki wprowadzające - - - - R - R - - - + 快捷键设置 Ustawienia skrótu - + 打开 Otwórz go - - English - English - - - - 中文 - 中文 - - - + 鼠标设置 Ustawienia myszy - + 保存 - - Ctrl+S + + 回放 - - 回放 + + 检查更新 diff --git a/src/ui/uiComponents.py b/src/ui/uiComponents.py index a8a0d70..78f92d3 100644 --- a/src/ui/uiComponents.py +++ b/src/ui/uiComponents.py @@ -17,6 +17,7 @@ # ui相关的小组件,非窗口 class RoundQDialog(QDialog): + closeEvent_ = QtCore.pyqtSignal() def __init__(self, parent=None): # 可以随意拖动的圆角、阴影对话框 super(RoundQDialog, self).__init__(parent) @@ -76,9 +77,13 @@ def mouseMoveEvent(self, e): self.move(e.globalPos() - self.m_DragPosition) e.accept() + def closeEvent(self, event): + self.closeEvent_.emit() + class RoundQWidget(QWidget): barSetMineNum = QtCore.pyqtSignal(int) barSetMineNumCalPoss = QtCore.pyqtSignal() + closeEvent_ = QtCore.pyqtSignal() def __init__(self, parent=None): # 可以随意拖动的圆角、阴影对话框 super(RoundQWidget, self).__init__(parent) @@ -138,6 +143,9 @@ def mouseMoveEvent(self, e): self.move(e.globalPos() - self.m_DragPosition) e.accept() + def closeEvent(self, event): + self.closeEvent_.emit() + class StatusLabel (QtWidgets.QLabel): # 最上面的脸的控件,在这里重写一些方法 @@ -155,11 +163,10 @@ def __init__(self, parent=None): def reloadFace(self, pixSize): # 重新修改脸的大小,叫rescale_face更妥 self.pixSize = pixSize - self.pixmap1 = QPixmap(self.smilefacedown_path).scaled(int(self.pixSize * 1.5), int(self.pixSize * 1.5)) - self.pixmap2 = QPixmap(self.smileface_path).scaled(int(self.pixSize * 1.5), int(self.pixSize * 1.5)) - # self.resize(QtCore.QSize(int(self.pixSize * 1.5), int(self.pixSize * 1.5))) - self.setMinimumSize(QtCore.QSize(int(self.pixSize * 1.5), int(self.pixSize * 1.5))) - self.setMaximumSize(QtCore.QSize(int(self.pixSize * 1.5), int(self.pixSize * 1.5))) + self.pixmap1 = QPixmap(self.smilefacedown_path).scaled(int(pixSize * 1.5), int(pixSize * 1.5)) + self.pixmap2 = QPixmap(self.smileface_path).scaled(int(pixSize * 1.5), int(pixSize * 1.5)) + self.setMinimumSize(QtCore.QSize(int(pixSize * 1.5), int(pixSize * 1.5))) + self.setMaximumSize(QtCore.QSize(int(pixSize * 1.5), int(pixSize * 1.5))) def setPath(self, r_path): # 告诉脸,相对路径 diff --git a/src/ui/ui_about.py b/src/ui/ui_about.py index 2ccf687..ab174c6 100644 --- a/src/ui/ui_about.py +++ b/src/ui/ui_about.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_about.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -18,6 +18,7 @@ def setupUi(self, Form): Form.setMinimumSize(QtCore.QSize(600, 382)) Form.setMaximumSize(QtCore.QSize(600, 580)) Form.setSizeIncrement(QtCore.QSize(0, 0)) + Form.setWindowTitle("") Form.setWindowOpacity(10.0) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setObjectName("verticalLayout") @@ -92,7 +93,7 @@ def setupUi(self, Form): self.label_4.setAccessibleName("") self.label_4.setAccessibleDescription("") self.label_4.setLayoutDirection(QtCore.Qt.LeftToRight) - self.label_4.setText("

https://fff666.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B

") + self.label_4.setText("

https://openms.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B

") self.label_4.setTextFormat(QtCore.Qt.RichText) self.label_4.setWordWrap(True) self.label_4.setOpenExternalLinks(True) @@ -139,6 +140,7 @@ def setupUi(self, Form): self.pushButton.setStyleSheet("border-image: url(media/button.png);\n" "font: 16pt \"黑体\";\n" "color:white;font: bold;") + self.pushButton.setShortcut("Space") self.pushButton.setAutoDefault(False) self.pushButton.setFlat(False) self.pushButton.setObjectName("pushButton") @@ -153,10 +155,8 @@ def setupUi(self, Form): def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "关于")) self.label_race_label_2.setText(_translate("Form", "项目主页:")) self.label_race_label.setText(_translate("Form", "资料教程:")) self.label_race_label_3.setText(_translate("Form", "①本软件可以不受任何限制地复制、储存、传播。\n" "②任何人可以在任何一个项目中使用本项目源代码的任何一个部分,同时欢迎在本项目主页提出宝贵的意见。")) self.pushButton.setText(_translate("Form", "确定")) - self.pushButton.setShortcut(_translate("Form", "Space")) diff --git a/src/ui/ui_defined_parameter.py b/src/ui/ui_defined_parameter.py index 9c63eb9..9a741d8 100644 --- a/src/ui/ui_defined_parameter.py +++ b/src/ui/ui_defined_parameter.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_defined_parameter.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -43,6 +43,7 @@ def setupUi(self, Form): self.pushButton_3.setStyleSheet("border-image: url(media/button.png);\n" "font: 16pt \"黑体\";\n" "color:white;font: bold;") + self.pushButton_3.setShortcut("Return") self.pushButton_3.setAutoDefault(False) self.pushButton_3.setFlat(False) self.pushButton_3.setObjectName("pushButton_3") diff --git a/src/ui/ui_gameSettingShortcuts.py b/src/ui/ui_gameSettingShortcuts.py index b2193fe..802d2ae 100644 --- a/src/ui/ui_gameSettingShortcuts.py +++ b/src/ui/ui_gameSettingShortcuts.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_gs_shortcuts.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -23,6 +23,7 @@ def setupUi(self, Form): Form.setMinimumSize(QtCore.QSize(598, 380)) Form.setMaximumSize(QtCore.QSize(598, 380)) Form.setMouseTracking(True) + Form.setWindowTitle("") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("media/cat.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) Form.setWindowIcon(icon) @@ -59,6 +60,7 @@ def setupUi(self, Form): self.pushButton.setStyleSheet("border-image: url(media/button.png);\n" "font: 16pt \"黑体\";\n" "color:white;font: bold;") + self.pushButton.setShortcut("Return") self.pushButton.setAutoDefault(False) self.pushButton.setFlat(False) self.pushButton.setObjectName("pushButton") @@ -743,7 +745,6 @@ def setupUi(self, Form): def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "快捷键设置")) self.pushButton_2.setText(_translate("Form", "取消")) self.pushButton.setText(_translate("Form", "确定")) self.comboBox_gamemode4.setItemText(0, _translate("Form", "标准")) diff --git a/src/ui/ui_gameSettings.py b/src/ui/ui_gameSettings.py index 59d4cb8..7c351b8 100644 --- a/src/ui/ui_gameSettings.py +++ b/src/ui/ui_gameSettings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_gs.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -20,6 +20,7 @@ def setupUi(self, Form): Form.setMaximumSize(QtCore.QSize(580, 608)) Form.setMouseTracking(True) Form.setFocusPolicy(QtCore.Qt.ClickFocus) + Form.setWindowTitle("") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("media/cat.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) Form.setWindowIcon(icon) @@ -41,6 +42,7 @@ def setupUi(self, Form): self.pushButton_yes.setStyleSheet("border-image: url(media/button.png);\n" "font: 16pt \"微软雅黑\";\n" "color:white;") + self.pushButton_yes.setShortcut("Return") self.pushButton_yes.setAutoDefault(False) self.pushButton_yes.setFlat(False) self.pushButton_yes.setObjectName("pushButton_yes") @@ -438,6 +440,7 @@ def setupUi(self, Form): self.label_transparency.setObjectName("label_transparency") self.horizontalLayout_3.addWidget(self.label_transparency) self.horizontalSlider_transparency = QtWidgets.QSlider(self.horizontalWidget_transparency) + self.horizontalSlider_transparency.setMinimum(1) self.horizontalSlider_transparency.setMaximum(100) self.horizontalSlider_transparency.setSingleStep(1) self.horizontalSlider_transparency.setOrientation(QtCore.Qt.Horizontal) @@ -586,7 +589,6 @@ def setupUi(self, Form): def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "游戏设置")) self.pushButton_yes.setText(_translate("Form", "确定")) self.pushButton_no.setText(_translate("Form", "取消")) self.checkBox_end_then_flag.setText(_translate("Form", "结束后标雷")) diff --git a/src/ui/ui_main_board.py b/src/ui/ui_main_board.py index b52f06c..2b31b62 100644 --- a/src/ui/ui_main_board.py +++ b/src/ui/ui_main_board.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'main_board.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -312,18 +312,6 @@ def setupUi(self, MainWindow): self.actiongaun_yv.setFont(font) self.actiongaun_yv.setShortcut("A") self.actiongaun_yv.setObjectName("actiongaun_yv") - self.actionxis = QtWidgets.QAction(MainWindow) - font = QtGui.QFont() - font.setFamily("微软雅黑") - font.setPointSize(12) - self.actionxis.setFont(font) - self.actionxis.setObjectName("actionxis") - self.actionrumjc = QtWidgets.QAction(MainWindow) - font = QtGui.QFont() - font.setFamily("微软雅黑") - font.setPointSize(12) - self.actionrumjc.setFont(font) - self.actionrumjc.setObjectName("actionrumjc") self.action_kuaijiejian = QtWidgets.QAction(MainWindow) font = QtGui.QFont() font.setFamily("微软雅黑") @@ -338,30 +326,6 @@ def setupUi(self, MainWindow): self.actionopen.setFont(font) self.actionopen.setShortcut("O") self.actionopen.setObjectName("actionopen") - self.chinese = QtWidgets.QAction(MainWindow) - self.chinese.setText("中文") - self.chinese.setIconText("中文") - self.chinese.setToolTip("中文") - self.chinese.setStatusTip("") - font = QtGui.QFont() - font.setFamily("微软雅黑") - font.setPointSize(12) - self.chinese.setFont(font) - self.chinese.setObjectName("chinese") - self.english = QtWidgets.QAction(MainWindow) - self.english.setText("English") - self.english.setIconText("English") - self.english.setToolTip("English") - self.english.setStatusTip("") - font = QtGui.QFont() - font.setFamily("微软雅黑") - font.setPointSize(12) - self.english.setFont(font) - self.english.setObjectName("english") - self.english_2 = QtWidgets.QAction(MainWindow) - self.english_2.setObjectName("english_2") - self.chinese_2 = QtWidgets.QAction(MainWindow) - self.chinese_2.setObjectName("chinese_2") self.chinese_action = QtWidgets.QAction(MainWindow) self.chinese_action.setText("中文") self.chinese_action.setIconText("中文") @@ -403,6 +367,7 @@ def setupUi(self, MainWindow): font.setFamily("微软雅黑") font.setPointSize(12) self.polish_action.setFont(font) + self.polish_action.setShortcut("") self.polish_action.setObjectName("polish_action") self.german_action = QtWidgets.QAction(MainWindow) self.german_action.setText("Deutsch") @@ -414,16 +379,20 @@ def setupUi(self, MainWindow): font.setFamily("微软雅黑") font.setPointSize(12) self.german_action.setFont(font) + self.german_action.setShortcut("") self.german_action.setObjectName("german_action") self.action_save = QtWidgets.QAction(MainWindow) + self.action_save.setShortcut("Ctrl+S") self.action_save.setObjectName("action_save") self.action_replay = QtWidgets.QAction(MainWindow) + self.action_replay.setShortcut("R") self.action_replay.setObjectName("action_replay") self.actionauto_update = QtWidgets.QAction(MainWindow) font = QtGui.QFont() font.setFamily("微软雅黑") font.setPointSize(12) self.actionauto_update.setFont(font) + self.actionauto_update.setShortcut("") self.actionauto_update.setObjectName("actionauto_update") self.menu.addAction(self.actionopen) self.menu.addSeparator() @@ -470,19 +439,11 @@ def retranslateUi(self, MainWindow): self.actiontui_chu.setText(_translate("MainWindow", "退出")) self.actionyouxi_she_zhi.setText(_translate("MainWindow", "游戏设置")) self.actiongaun_yv.setText(_translate("MainWindow", "关于")) - self.actionxis.setText(_translate("MainWindow", "词典")) - self.actionxis.setShortcut(_translate("MainWindow", "D")) - self.actionrumjc.setText(_translate("MainWindow", "入门教程")) - self.actionrumjc.setShortcut(_translate("MainWindow", "R")) self.action_kuaijiejian.setText(_translate("MainWindow", "快捷键设置")) self.actionopen.setText(_translate("MainWindow", "打开")) - self.english_2.setText(_translate("MainWindow", "English")) - self.chinese_2.setText(_translate("MainWindow", "中文")) self.action_mouse.setText(_translate("MainWindow", "鼠标设置")) self.action_save.setText(_translate("MainWindow", "保存")) - self.action_save.setShortcut(_translate("MainWindow", "Ctrl+S")) self.action_replay.setText(_translate("MainWindow", "回放")) - self.action_replay.setShortcut(_translate("MainWindow", "R")) self.actionauto_update.setText(_translate("MainWindow", "检查更新")) from ui.mineLabel import mineLabel from ui.mineNumLabel import mineNumLabel diff --git a/src/ui/ui_mine_num_bar.py b/src/ui/ui_mine_num_bar.py index 7c3326e..b584054 100644 --- a/src/ui/ui_mine_num_bar.py +++ b/src/ui/ui_mine_num_bar.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_mine_num_bar.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -18,6 +18,7 @@ def setupUi(self, Form): Form.setMinimumSize(QtCore.QSize(140, 382)) Form.setMaximumSize(QtCore.QSize(140, 382)) Form.setSizeIncrement(QtCore.QSize(0, 0)) + Form.setWindowTitle("") Form.setWindowOpacity(10.0) self.verticalSlider = QtWidgets.QSlider(Form) self.verticalSlider.setGeometry(QtCore.QRect(30, 60, 22, 261)) @@ -68,5 +69,4 @@ def setupUi(self, Form): QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "雷数设置")) + pass diff --git a/src/ui/ui_record_pop.py b/src/ui/ui_record_pop.py index 7eb887c..fc7acc9 100644 --- a/src/ui/ui_record_pop.py +++ b/src/ui/ui_record_pop.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_record_pop.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. diff --git a/src/ui/ui_score_board.py b/src/ui/ui_score_board.py index d987c9c..5a7ccc1 100644 --- a/src/ui/ui_score_board.py +++ b/src/ui/ui_score_board.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_score_board.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -18,6 +18,7 @@ def setupUi(self, Form): Form.setMinimumSize(QtCore.QSize(264, 30)) Form.setMaximumSize(QtCore.QSize(264, 1000)) Form.setSizeIncrement(QtCore.QSize(0, 0)) + Form.setWindowTitle("") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("media/cat.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) Form.setWindowIcon(icon) @@ -74,13 +75,7 @@ def setupUi(self, Form): self.tableWidget.setGridStyle(QtCore.Qt.DashLine) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(2) - self.tableWidget.setRowCount(3) - item = QtWidgets.QTableWidgetItem() - self.tableWidget.setVerticalHeaderItem(0, item) - item = QtWidgets.QTableWidgetItem() - self.tableWidget.setVerticalHeaderItem(1, item) - item = QtWidgets.QTableWidgetItem() - self.tableWidget.setVerticalHeaderItem(2, item) + self.tableWidget.setRowCount(0) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setFamily("微软雅黑") @@ -124,16 +119,5 @@ def setupUi(self, Form): def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "计数器")) self.label_counter.setText(_translate("Form", "计数器")) - item = self.tableWidget.verticalHeaderItem(0) - item.setText(_translate("Form", "新建行")) - item = self.tableWidget.verticalHeaderItem(1) - item.setText(_translate("Form", "1")) - item = self.tableWidget.verticalHeaderItem(2) - item.setText(_translate("Form", "3")) - item = self.tableWidget.horizontalHeaderItem(0) - item.setText(_translate("Form", "指标")) - item = self.tableWidget.horizontalHeaderItem(1) - item.setText(_translate("Form", "表达式")) from ui.uiComponents import ScoreTable diff --git a/src/ui/ui_video_control.py b/src/ui/ui_video_control.py index e75b075..432aee4 100644 --- a/src/ui/ui_video_control.py +++ b/src/ui/ui_video_control.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_video_control.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -18,6 +18,7 @@ def setupUi(self, Form): Form.setMinimumSize(QtCore.QSize(480, 640)) Form.setMaximumSize(QtCore.QSize(600, 640)) Form.setSizeIncrement(QtCore.QSize(0, 0)) + Form.setWindowTitle("") Form.setWindowOpacity(10.0) self.horizontalSlider_time = QtWidgets.QSlider(Form) self.horizontalSlider_time.setGeometry(QtCore.QRect(20, 40, 481, 31)) @@ -85,6 +86,7 @@ def setupUi(self, Form): self.horizontalSlider_time.setObjectName("horizontalSlider_time") self.pushButton_replay = QtWidgets.QPushButton(Form) self.pushButton_replay.setGeometry(QtCore.QRect(20, 90, 41, 41)) + self.pushButton_replay.setToolTipDuration(0) self.pushButton_replay.setStyleSheet("border-image: url(media/replay.svg);\n" "QPushButton::hover{\n" " background-color: rgb(170, 255, 255);\n" @@ -93,14 +95,16 @@ def setupUi(self, Form): self.pushButton_replay.setObjectName("pushButton_replay") self.pushButton_play = QtWidgets.QPushButton(Form) self.pushButton_play.setGeometry(QtCore.QRect(70, 90, 41, 41)) + self.pushButton_play.setToolTipDuration(0) self.pushButton_play.setStyleSheet("border-image: url(media/play.svg);") self.pushButton_play.setText("") self.pushButton_play.setObjectName("pushButton_play") self.label_speed = SpeedLabel(Form) self.label_speed.setGeometry(QtCore.QRect(142, 90, 51, 41)) - self.label_speed.setStyleSheet("border-image: url(media/speed.svg);\n" + self.label_speed.setStyleSheet("QLabel {border-image: url(media/speed.svg);\n" "font: 12pt \"微软雅黑\";\n" -"color: #50A6EA;") +"color: #50A6EA;}") + self.label_speed.setText("1") self.label_speed.setAlignment(QtCore.Qt.AlignCenter) self.label_speed.setObjectName("label_speed") self.label_2 = QtWidgets.QLabel(Form) @@ -112,7 +116,7 @@ def setupUi(self, Form): self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName("label_2") self.doubleSpinBox_time = QtWidgets.QDoubleSpinBox(Form) - self.doubleSpinBox_time.setGeometry(QtCore.QRect(370, 80, 131, 61)) + self.doubleSpinBox_time.setGeometry(QtCore.QRect(280, 80, 221, 61)) font = QtGui.QFont() font.setFamily("微软雅黑") font.setPointSize(20) @@ -123,12 +127,14 @@ def setupUi(self, Form): self.doubleSpinBox_time.setStyleSheet("font: 20pt \"微软雅黑\";\n" "color: #50A6EA;\n" "background-color: rgb(240, 240, 240);") + self.doubleSpinBox_time.setWrapping(False) self.doubleSpinBox_time.setFrame(False) self.doubleSpinBox_time.setAlignment(QtCore.Qt.AlignCenter) - self.doubleSpinBox_time.setDecimals(2) + self.doubleSpinBox_time.setDecimals(3) self.doubleSpinBox_time.setMinimum(-999.99) self.doubleSpinBox_time.setMaximum(999.99) - self.doubleSpinBox_time.setSingleStep(0.01) + self.doubleSpinBox_time.setSingleStep(0.001) + self.doubleSpinBox_time.setStepType(QtWidgets.QAbstractSpinBox.DefaultStepType) self.doubleSpinBox_time.setObjectName("doubleSpinBox_time") self.scrollArea = QtWidgets.QScrollArea(Form) self.scrollArea.setGeometry(QtCore.QRect(20, 150, 481, 471)) @@ -172,8 +178,9 @@ def setupUi(self, Form): def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "雷数设置")) - self.label_speed.setText(_translate("Form", "1")) + self.pushButton_replay.setToolTip(_translate("Form", "重播")) + self.pushButton_play.setToolTip(_translate("Form", "播放/暂停")) + self.label_speed.setToolTip(_translate("Form", "滑动滚轮修改播放速度")) self.label_time.setText(_translate("Form", "时间")) self.label_tag.setText(_translate("Form", "标签")) self.label_event.setText(_translate("Form", "事件")) diff --git "a/src/ui/\347\224\237\346\210\220ts\346\226\207\344\273\266.bat" "b/src/ui/\347\224\237\346\210\220ts\346\226\207\344\273\266.bat" index 1a0b949..dcb9998 100644 --- "a/src/ui/\347\224\237\346\210\220ts\346\226\207\344\273\266.bat" +++ "b/src/ui/\347\224\237\346\210\220ts\346\226\207\344\273\266.bat" @@ -1,5 +1,5 @@ -pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py -ts en_US.ts +pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py ui_video_control.py -ts en_US.ts -noobsolete -pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py -ts pl_PL.ts +pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py ui_video_control.py -ts pl_PL.ts -noobsolete -pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py -ts de_DE.ts \ No newline at end of file +pylupdate5 ui_gameSettings.py ui_main_board.py ui_about.py ui_defined_parameter.py ui_gameSettingShortcuts.py ui_score_board.py ui_record_pop.py ui_video_control.py -ts de_DE.ts -noobsolete \ No newline at end of file diff --git a/src/videoControl.py b/src/videoControl.py index 0eb3e2f..41e9e46 100644 --- a/src/videoControl.py +++ b/src/videoControl.py @@ -8,7 +8,6 @@ from PyQt5 import QtCore from ui.uiComponents import RoundQWidget, CommentLabel from ui.ui_video_control import Ui_Form -from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget class ui_Form(QWidget, Ui_Form): @@ -16,13 +15,15 @@ class ui_Form(QWidget, Ui_Form): # barSetMineNumCalPoss = QtCore.pyqtSignal(int) # time_current = 0.0 - def __init__(self, r_path, video, comments, parent): + def __init__(self, r_path, video, comments, game_setting, parent): super (ui_Form, self).__init__ () self.QWidget = RoundQWidget(parent) self.setupUi(self.QWidget) - self.horizontalSlider_time.setMaximum(int(video.video_end_time * 100 + 1)) - self.horizontalSlider_time.setMinimum(int((video.video_start_time) * 100 - 1)) + self.game_setting = game_setting + self.QWidget.closeEvent_.connect(self.close) + self.horizontalSlider_time.setMaximum(int(video.video_end_time * 1000)) + self.horizontalSlider_time.setMinimum(int(video.video_start_time * 1000)) self.horizontalSlider_time.valueChanged[int].connect(self.set_double_spin_box_time) self.doubleSpinBox_time.valueChanged[float].connect(self.set_horizontal_slider_time) @@ -44,18 +45,19 @@ def __init__(self, r_path, video, comments, parent): self.comments_labels.append([c1, c2, c3]) comment_row += 1 self.scrollAreaWidgetContents.setFixedHeight(42 * (comment_row + 1)) - self.pushButton_replay.setStyleSheet("border-image: url(" + str(r_path.with_name('media').joinpath('replay.svg')).replace("\\", "/") + ");") - self.pushButton_play.setStyleSheet("border-image: url(" + str(r_path.with_name('media').joinpath('play.svg')).replace("\\", "/") + ");") - self.label_speed.setStyleSheet("border-image: url(" + str(r_path.with_name('media').joinpath('speed.svg')).replace("\\", "/") + ");\n" + self.pushButton_replay.setStyleSheet("QPushButton{border-image: url(" + str(r_path.with_name('media').joinpath('replay.svg')).replace("\\", "/") + ");}") + self.pushButton_play.setStyleSheet("QPushButton{border-image: url(" + str(r_path.with_name('media').joinpath('play.svg')).replace("\\", "/") + ");}") + self.label_speed.setStyleSheet("QLabel{border-image: url(" + str(r_path.with_name('media').joinpath('speed.svg')).replace("\\", "/") + ");\n" "font: 12pt \"微软雅黑\";\n" -"color: #50A6EA;") +"color: #50A6EA;}") self.label_2.setStyleSheet("border-image: url(" + str(r_path.with_name('media').joinpath('mul.svg')).replace("\\", "/") + ");\n" "font: 12pt \"微软雅黑\";\n" "color: #50A6EA;") - - + self.QWidget.move(game_setting.value("DEFAULT/videocontroltop", 100, int), + game_setting.value("DEFAULT/videocontrolleft", 300, int)) + def set_double_spin_box_time(self, int_time): - self.doubleSpinBox_time.setValue(int_time / 100) + self.doubleSpinBox_time.setValue(int_time / 1000) self.horizontalSlider_time.blockSignals(True) self.horizontalSlider_time.setValue(int_time) self.horizontalSlider_time.blockSignals(False) @@ -65,28 +67,13 @@ def set_double_spin_box_time(self, int_time): def set_horizontal_slider_time(self, float_time): self.doubleSpinBox_time.blockSignals(True) - self.horizontalSlider_time.setValue(int(float_time * 100)) + self.horizontalSlider_time.setValue(int(float_time * 1000)) self.doubleSpinBox_time.blockSignals(False) - self.videoSetTime.emit(int(float_time * 100)) + self.videoSetTime.emit(int(float_time * 1000)) # self.time_current = float_time - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + def close(self): + self.game_setting.set_value("DEFAULT/videocontroltop", self.QWidget.x()) + self.game_setting.set_value("DEFAULT/videocontrolleft", self.QWidget.y()) + self.game_setting.sync() + diff --git a/uiFiles/main_board.ui b/uiFiles/main_board.ui index d857fe2..ac143bc 100644 --- a/uiFiles/main_board.ui +++ b/uiFiles/main_board.ui @@ -801,34 +801,6 @@ A - - - 词典 - - - - 微软雅黑 - 12 - - - - D - - - - - 入门教程 - - - - 微软雅黑 - 12 - - - - R - - 快捷键设置 @@ -857,56 +829,6 @@ O - - - 中文 - - - 中文 - - - 中文 - - - - - - - 微软雅黑 - 12 - - - - - - English - - - English - - - English - - - - - - - 微软雅黑 - 12 - - - - - - English - - - - - 中文 - - 中文 @@ -995,6 +917,9 @@ 12 + + + @@ -1018,13 +943,16 @@ 12 + + + 保存 - Ctrl+S + Ctrl+S @@ -1032,7 +960,7 @@ 回放 - R + R @@ -1045,6 +973,9 @@ 12 + + + diff --git a/uiFiles/ui_about.ui b/uiFiles/ui_about.ui index d10c811..08fbb24 100644 --- a/uiFiles/ui_about.ui +++ b/uiFiles/ui_about.ui @@ -29,7 +29,7 @@ - 关于 + 10.000000000000000 @@ -212,7 +212,7 @@ Qt::LeftToRight - <html><head/><body><p><a href="https://fff666.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B"><span style=" text-decoration: underline; color:#0000ff;">https://fff666.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B</span></a></p></body></html> + <html><head/><body><p><a href="https://fff666.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B"><span style=" text-decoration: underline; color:#0000ff;">https://openms.top/#/guide/[80.%E6%95%99%E7%A8%8B.%E8%BD%AF%E4%BB%B6]%E5%85%83%E6%89%AB%E9%9B%B7%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B</span></a></p></body></html> Qt::RichText @@ -348,7 +348,7 @@ color:white;font: bold; 确定 - Space + Space false diff --git a/uiFiles/ui_defined_parameter.ui b/uiFiles/ui_defined_parameter.ui index db64da4..60324de 100644 --- a/uiFiles/ui_defined_parameter.ui +++ b/uiFiles/ui_defined_parameter.ui @@ -91,6 +91,9 @@ color:white;font: bold; 确定 + + Return + false diff --git a/uiFiles/ui_gs.ui b/uiFiles/ui_gs.ui index 3ceaf7e..b762140 100644 --- a/uiFiles/ui_gs.ui +++ b/uiFiles/ui_gs.ui @@ -32,7 +32,7 @@ Qt::ClickFocus - 游戏设置 + @@ -84,6 +84,9 @@ color:white; 确定 + + Return + false @@ -1026,6 +1029,9 @@ color: #3d3d3d; + + 1 + 100 diff --git a/uiFiles/ui_gs_shortcuts.ui b/uiFiles/ui_gs_shortcuts.ui index 53deab3..811e8ea 100644 --- a/uiFiles/ui_gs_shortcuts.ui +++ b/uiFiles/ui_gs_shortcuts.ui @@ -32,7 +32,7 @@ true - 快捷键设置 + @@ -107,6 +107,9 @@ color:white;font: bold; 确定 + + Return + false diff --git a/uiFiles/ui_mine_num_bar.ui b/uiFiles/ui_mine_num_bar.ui index 500574e..1de01b7 100644 --- a/uiFiles/ui_mine_num_bar.ui +++ b/uiFiles/ui_mine_num_bar.ui @@ -29,7 +29,7 @@ - 雷数设置 + 10.000000000000000 diff --git a/uiFiles/ui_score_board.ui b/uiFiles/ui_score_board.ui index 88209d9..74d6c22 100644 --- a/uiFiles/ui_score_board.ui +++ b/uiFiles/ui_score_board.ui @@ -29,7 +29,7 @@ - 计数器 + @@ -207,24 +207,9 @@ false - - - 新建行 - - - - - 1 - - - - - 3 - - - 指标 + @@ -234,7 +219,7 @@ - 表达式 + diff --git a/uiFiles/ui_video_control.ui b/uiFiles/ui_video_control.ui index 3adb5d2..706783a 100644 --- a/uiFiles/ui_video_control.ui +++ b/uiFiles/ui_video_control.ui @@ -29,7 +29,7 @@ - 雷数设置 + 10.000000000000000 @@ -140,6 +140,12 @@ QSlider::add-page:horizontal:disabled, QSlider::sub-page:horizontal:disabled, QS 41 + + 重播 + + + 0 + border-image: url(media/replay.svg); QPushButton::hover{ @@ -159,6 +165,12 @@ QPushButton::hover{ 41 + + 播放/暂停 + + + 0 + border-image: url(media/play.svg); @@ -175,13 +187,16 @@ QPushButton::hover{ 41 + + 滑动滚轮修改播放速度 + - border-image: url(media/speed.svg); + QLabel {border-image: url(media/speed.svg); font: 12pt "微软雅黑"; -color: #50A6EA; +color: #50A6EA;} - 1 + 1 Qt::AlignCenter @@ -211,9 +226,9 @@ color: #50A6EA; - 370 + 280 80 - 131 + 221 61 @@ -231,6 +246,9 @@ color: #50A6EA; color: #50A6EA; background-color: rgb(240, 240, 240); + + false + false @@ -238,7 +256,7 @@ background-color: rgb(240, 240, 240); Qt::AlignCenter - 2 + 3 -999.990000000000009 @@ -247,7 +265,10 @@ background-color: rgb(240, 240, 240); 999.990000000000009 - 0.010000000000000 + 0.001000000000000 + + + QAbstractSpinBox::DefaultStepType diff --git "a/uiFiles/ui\350\275\254py.bat" "b/uiFiles/ui\350\275\254py.bat" index a4f9b06..732c68e 100644 --- "a/uiFiles/ui\350\275\254py.bat" +++ "b/uiFiles/ui\350\275\254py.bat" @@ -16,4 +16,5 @@ copy /y ui_defined_parameter.py ..\src\ui\ui_defined_parameter.py copy /y ui_gameSettingShortcuts.py ..\src\ui\ui_gameSettingShortcuts.py copy /y ui_record_pop.py ..\src\ui\ui_record_pop.py copy /y ui_about.py ..\src\ui\ui_about.py +copy /y ui_video_control.py ..\src\ui\ui_video_control.py pause \ No newline at end of file From 96d2a89894be6901df10f2687684fabb5fffd58c Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 19:29:29 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 52 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/main.py b/src/main.py index 06366ca..4057165 100644 --- a/src/main.py +++ b/src/main.py @@ -81,25 +81,40 @@ def find_window(class_name, window_name): if __name__ == "__main__": # QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - + try: app = QtWidgets.QApplication(sys.argv) - mainWindow = mainWindowGUI.MainWindow() - ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) - ui.mainWindow.show() - ui.mainWindow.game_setting_path = ui.game_setting_path - - _translate = QtCore.QCoreApplication.translate - hwnd = find_window(None, _translate("MainWindow", "元扫雷")) - - SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity - ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity( - hwnd, 0x00000011) else 1/0 - ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity( - hwnd, 0x00000000) else 1/0 - - sys.exit(app.exec_()) - ... - except Exception as e: + serverName = "MineSweeperServer" + socket = QLocalSocket() + socket.connectToServer(serverName) + if socket.waitForConnected(500): + if len(sys.argv) == 2: + filePath = sys.argv[1] + socket.write(filePath.encode()) + socket.flush() + time.sleep(0.5) + app.quit() + else: + localServer = QLocalServer() + localServer.listen(serverName) + localServer.newConnection.connect( + lambda: on_new_connection(localServer=localServer)) + mainWindow = mainWindowGUI.MainWindow() + ui = mineSweeperGUI.MineSweeperGUI(mainWindow, sys.argv) + ui.mainWindow.show() + # ui.mainWindow.game_setting = ui.game_setting + + _translate = QtCore.QCoreApplication.translate + hwnd = find_window(None, _translate("MainWindow", "元扫雷")) + + SetWindowDisplayAffinity = ctypes.windll.user32.SetWindowDisplayAffinity + ui.disable_screenshot = lambda: ... if SetWindowDisplayAffinity( + hwnd, 0x00000011) else 1/0 + ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity( + hwnd, 0x00000000) else 1/0 + + sys.exit(app.exec_()) + ... + except: pass # 最高优先级 @@ -169,4 +184,3 @@ def find_window(class_name, window_name): # MouseState::ChordingNotFlag => Ok(6), # MouseState::DownUpAfterChording => Ok(7), # MouseState::Undefined => Ok(8), - From 4047c3edc7ae9ba36e02593d30f4b202c7b4c700 Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 19:33:20 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main.py b/src/main.py index 97c99ae..9d7f27d 100644 --- a/src/main.py +++ b/src/main.py @@ -13,9 +13,6 @@ os.environ["QT_FONT_DPI"] = "96" -def on_new_connection(localServer: QLocalServer): - - def on_new_connection(localServer: QLocalServer): """当新连接进来时,接受连接并将文件路径传递给主窗口""" socket = localServer.nextPendingConnection() @@ -23,9 +20,6 @@ def on_new_connection(localServer: QLocalServer): socket.readyRead.connect(lambda: on_ready_read(socket)) -def on_ready_read(socket: QLocalSocket): - - def on_ready_read(socket: QLocalSocket): """从socket读取文件路径并传递给主窗口""" if socket and socket.state() == QLocalSocket.ConnectedState: From b4bc4ece5e61607dc38af4255b559fdfc31f7dc3 Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 21:10:48 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0gitee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CheckUpdateGui.py | 33 ++-- src/githubApi.py | 69 ++++---- src/mineSweeperGUI.py | 383 ++++++++++++++++++++++-------------------- 3 files changed, 254 insertions(+), 231 deletions(-) diff --git a/src/CheckUpdateGui.py b/src/CheckUpdateGui.py index 0804faa..68226f7 100644 --- a/src/CheckUpdateGui.py +++ b/src/CheckUpdateGui.py @@ -89,8 +89,11 @@ def initUi(self): row1.addWidget(QLabel(self.release.tag_name)) row1.addItem(QSpacerItem( 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - self.dateTimeLabel.setText(QDateTime.fromString( - self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString("yyyy-MM-dd hh:mm:ss")) + self.dateTimeLabel.hide() + if self.release.assets_created_at != "": + self.dateTimeLabel.setText(QDateTime.fromString( + self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString("yyyy-MM-dd hh:mm:ss")) + self.dateTimeLabel.show() row1.addWidget(self.dateTimeLabel) row1.addItem(QSpacerItem( 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) @@ -108,15 +111,19 @@ def initUi(self): formLayout.addRow(QObject.tr(self, "html_url"), urlLabel) formLayout.addRow(QObject.tr(self, "name"), QLabel(self.release.assets_name)) - formLayout.addRow(QObject.tr(self, "content_type"), - QLabel(self.release.assets_content_type)) - formLayout.addRow(QObject.tr(self, "size"), QLabel( - str(f"{self.release.assets_size / 1000000:.2f} MB"))) - formLayout.addRow(QObject.tr(self, "download_count"), - QLabel(str(self.release.assets_download_count))) - formLayout.addRow(QObject.tr(self, "created_at"), - QLabel(QDateTime.fromString(self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString( - "yyyy-MM-dd hh:mm:ss"))) + if self.release.assets_content_type != "": + formLayout.addRow(QObject.tr(self, "content_type"), + QLabel(self.release.assets_content_type)) + if self.release.assets_size > 0: + formLayout.addRow(QObject.tr(self, "size"), QLabel( + str(f"{self.release.assets_size / 1000000:.2f} MB"))) + if self.release.assets_download_count != "": + formLayout.addRow(QObject.tr(self, "download_count"), + QLabel(str(self.release.assets_download_count))) + if self.release.assets_created_at != "": + formLayout.addRow(QObject.tr(self, "created_at"), + QLabel(QDateTime.fromString(self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString( + "yyyy-MM-dd hh:mm:ss"))) downloadUrlLabel = QLabel() downloadUrlLabel.setText("" + QObject.tr(self, "open download links") + "") @@ -237,7 +244,7 @@ def __init__(self, github: GitHub, parent=None): self.setWindowTitle(QObject.tr(self, "CheckUpdate")) # 去掉问号 self.setWindowFlags(self.windowFlags() & ~ - Qt.WindowContextHelpButtonHint) + Qt.WindowContextHelpButtonHint) self.r_path = parent.r_path self.github: GitHub = github self.github.setParent(self) @@ -309,7 +316,7 @@ def initConnect(self): def changeSource(self, source: str): self.pingThread = PingThread( - source, self.github.sourceManager.sources[source]) + source, self.github.sourceManager.sources[source]['url']) self.sourceSpeedLabel.setText("---ms") self.pingThread.pingSignal.connect( lambda x, y: self.sourceSpeedLabel.setText(f"{int(y)}ms")) diff --git a/src/githubApi.py b/src/githubApi.py index f1b685d..23347ce 100644 --- a/src/githubApi.py +++ b/src/githubApi.py @@ -47,7 +47,7 @@ def run(self) -> None: class SourceManager(QObject): quickSource = pyqtSignal(str, float) - def __init__(self, sources: dict, current: any = None, parent=None): + def __init__(self, sources: dict, current: str = None, parent=None): super().__init__(parent) if not isinstance(sources, dict): raise TypeError @@ -55,7 +55,8 @@ def __init__(self, sources: dict, current: any = None, parent=None): raise ValueError self.sources = sources # 第一个的key - self.currentSource = list(sources.keys())[0] if current is None else current + self.currentSource = list(sources.keys())[ + 0] if current is None else current self.speedData = {} self.threads = [] @@ -70,7 +71,7 @@ def sources(self, sources: dict): if not sources: raise ValueError for key in sources: - sources[key] = sources[key].strip('/') + sources[key]['url'] = sources[key]['url'].strip('/') self.__sources = sources self.currentSource = list(sources.keys())[0] @@ -81,6 +82,17 @@ def currentSource(self): """ return self.__currentSource + @property + def token(self) -> str: + return self.__sources[self.currentSource]['token'] + + @property + def tokenUrl(self) -> str: + if self.currentSource == "gitee": + return f'?access_token={self.token}' + else: + return "" + @currentSource.setter def currentSource(self, currentSource: str): """ @@ -98,7 +110,7 @@ def currentSource(self, currentSource: str): @property def currentSourceUrl(self): - return self.sources[self.currentSource] + return self.sources[self.currentSource]['url'] def checkSourceSpeed(self): """ @@ -107,7 +119,7 @@ def checkSourceSpeed(self): self.threads.clear() self.speedData = {} for source in self.sources: - thread = PingThread(source, self.sources[source]) + thread = PingThread(source, self.sources[source]['url']) thread.pingSignal.connect(self.__pingSignal) self.threads.append(thread) thread.start() @@ -129,13 +141,13 @@ def __init__(self, dataJson: dict, versionReStr: str) -> None: self.tag_name = dataJson['tag_name'] if re.search( versionReStr, dataJson['tag_name']) else dataJson['name'] self.body = dataJson['body'] - self.html_url = dataJson['html_url'] + self.html_url = dataJson['html_url'] if 'html_url' in dataJson else "" m_assert = dataJson['assets'][0] self.assets_name = m_assert['name'] - self.assets_content_type = m_assert['content_type'] - self.assets_size = m_assert['size'] - self.assets_download_count = m_assert['download_count'] - self.assets_created_at = m_assert['created_at'] + self.assets_content_type = m_assert['content_type'] if 'content_type' in m_assert else "" + self.assets_size = m_assert['size'] if 'size' in m_assert else 0 + self.assets_download_count = m_assert['download_count'] if 'download_count' in m_assert else "" + self.assets_created_at = m_assert['created_at'] if 'created_at' in m_assert else "" self.assets_browser_download_url = m_assert['browser_download_url'] @@ -148,20 +160,16 @@ class GitHub(QObject): downloadReleaseAsyncFinishSignal = pyqtSignal(str) errorSignal = pyqtSignal(str) - def __init__(self, sourceManager: SourceManager, owner: str, repo: str, version: str, versionReStr: str, + def __init__(self, sourceManager: SourceManager, version: str, versionReStr: str, parent=None): """ :param sourceManager: 一个SourceManager实例,提供了多个github api的url - :param owner: github的owner - :param repo: github的仓库名 :param version: 当前的版本号 :param versionReStr: 一个正则表达式串,用于从github的release中,提取版本号 :param parent: 一个QObject实例,父对象 """ super().__init__(parent) self.__sourceManager = sourceManager - self.__owner = owner - self.__repo = repo self.__version = version self.__versionReStr = versionReStr self.__replyDict = {} @@ -177,22 +185,6 @@ def sourceManager(self, sourceManager: SourceManager) -> None: self.__sourceManager = sourceManager self.__sourceManager.setParent(self) - @property - def owner(self) -> str: - return self.__owner - - @owner.setter - def owner(self, owner: str) -> None: - self.__owner = owner - - @property - def repo(self) -> str: - return self.__repo - - @repo.setter - def repo(self, repo: str) -> None: - self.__repo = repo - @property def version(self) -> str: return self.__version @@ -214,14 +206,14 @@ def latestReleaseUrl(self) -> str: """ 获取最新的release的url """ - return f'{self.sourceManager.currentSourceUrl}/{self.__owner}/{self.__repo}/releases/latest' + return f'{self.sourceManager.currentSourceUrl}/releases/latest{self.sourceManager.tokenUrl}' @property def releasesUrl(self) -> str: """ 获取所有release的url """ - return f'{self.sourceManager.currentSourceUrl}/{self.__owner}/{self.__repo}/releases' + return f'{self.sourceManager.currentSourceUrl}/releases{self.sourceManager.tokenUrl}' def isNeedUpdate(self, isAsync: bool = True) -> bool | str | None: """ @@ -354,7 +346,8 @@ def downloadRelease(self, release: ReleaseInfo) -> str: :return: 一个当前请求的唯一标识 """ self.downloadReleaseAsyncStartSignal.emit(release) - nam, request = self.__createRequest(release.assets_browser_download_url) + nam, request = self.__createRequest( + release.assets_browser_download_url) reply = nam.get(request) reply.setObjectName(str(uuid.uuid1())) self.__replyDict[reply.objectName()] = reply @@ -474,14 +467,14 @@ def removeDownloadFile(self): """删除下载的文件""" GitHub.removeAllTempFile(f".*{self.__repo}.*") + if __name__ == '__main__': app = QCoreApplication([]) data = { - "Github": "https://api.github.com/repos/", - "gitee": "https://api.gitee.com/repos/", + "Github": "https://api.github.com/repos/eee555/Metasweeper", + "gitee": "https://api.gitee.com/repos/ee55/Metasweeper", } - github = GitHub(SourceManager(data), "eee555", - "Solvable-Minesweeper", "3.1.9", "(\d+\.\d+\.\d+)") + github = GitHub(SourceManager(data, "gitee"), "3.1.9", "(\d+\.\d+\.\d+)") github.releasesAsyncSignal.connect(lambda x: print(x)) github.releases() # manager = SourceManager(data) diff --git a/src/mineSweeperGUI.py b/src/mineSweeperGUI.py index 67bb848..7c4a539 100644 --- a/src/mineSweeperGUI.py +++ b/src/mineSweeperGUI.py @@ -4,7 +4,11 @@ # from PyQt5.QtWidgets import QLineEdit, QInputDialog, QShortcut from PyQt5.QtWidgets import QApplication, QFileDialog, QWidget import gameDefinedParameter -import superGUI, gameAbout, gameSettings, gameSettingShortcuts,\ +import superGUI +import gameAbout +import gameSettings +import gameSettingShortcuts +import \ captureScreen, mine_num_bar, videoControl, gameRecordPop from CheckUpdateGui import CheckUpdateGui from githubApi import GitHub, SourceManager @@ -15,23 +19,24 @@ # import time import os import ctypes -import hashlib, uuid +import hashlib +import uuid # from PyQt5.QtWidgets import QApplication from country_name import country_name import metaminesweeper_checksum from mainWindowGUI import MainWindow + class MineSweeperGUI(superGUI.Ui_MainWindow): def __init__(self, MainWindow: MainWindow, args): self.mainWindow = MainWindow self.checksum_guard = metaminesweeper_checksum.ChecksumGuard() super(MineSweeperGUI, self).__init__(MainWindow, args) - - + # MineSweeperGUI父类的init中读.ini、读图片、设置字体、局面初始化等 - self.time_10ms: int = 0 # 已毫秒为单位的游戏时间,全局统一的 + self.time_10ms: int = 0 # 已毫秒为单位的游戏时间,全局统一的 self.showTime(self.time_10ms // 100) self.timer_10ms = QTimer() self.timer_10ms.setInterval(10) # 10毫秒回调一次的定时器 @@ -48,6 +53,7 @@ def __init__(self, MainWindow: MainWindow, args): self.actiongao_ji.triggered.connect(lambda: self.predefined_Board(3)) self.actionzi_ding_yi.triggered.connect(self.action_CEvent) self.actiongao_ji.triggered.connect(lambda: self.predefined_Board(3)) + def save_evf_file_integrated(): if self.game_state != "ready" and self.game_state != "playing" and\ self.game_state != "show" and self.game_state != "study" and\ @@ -63,10 +69,14 @@ def save_evf_file_integrated(): self.actiongaun_yv.triggered.connect(self.action_AEvent) self.actionauto_update.triggered.connect(self.auto_Update) self.actionopen.triggered.connect(self.action_OpenFile) - self.english_action.triggered.connect(lambda: self.trans_language("en_US")) - self.chinese_action.triggered.connect(lambda: self.trans_language("zh_CN")) - self.polish_action.triggered.connect(lambda: self.trans_language("pl_PL")) - self.german_action.triggered.connect(lambda: self.trans_language("de_DE")) + self.english_action.triggered.connect( + lambda: self.trans_language("en_US")) + self.chinese_action.triggered.connect( + lambda: self.trans_language("zh_CN")) + self.polish_action.triggered.connect( + lambda: self.trans_language("pl_PL")) + self.german_action.triggered.connect( + lambda: self.trans_language("de_DE")) # config = configparser.ConfigParser() # config.read(self.game_setting_path, encoding='utf-8') @@ -89,7 +99,8 @@ def save_evf_file_integrated(): self.frameShortcut7.activated.connect(lambda: self.predefined_Board(6)) self.frameShortcut8.activated.connect(self.showScores) self.frameShortcut9.activated.connect(self.screenShot) - self.shortcut_hidden_score_board.activated.connect(self.hidden_score_board) + self.shortcut_hidden_score_board.activated.connect( + self.hidden_score_board) self._game_state = self.game_state = 'ready' # 用状态机控制局面状态。 @@ -102,14 +113,12 @@ def save_evf_file_integrated(): # 'fail':游戏失败,踩雷了。 # 'win':游戏成功。 - - # 相对路径 self.relative_path = args[0] # 用本软件打开录像 if len(args) == 2: self.action_OpenFile(openfile_name=args[1]) - + self.trans_language() self.score_board_manager.with_namespace({ "race_identifier": self.race_identifier, @@ -117,17 +126,17 @@ def save_evf_file_integrated(): "checksum_ok": "--", "is_official": "--", "is_fair": "--" - }) - self.score_board_manager.reshow(self.label.ms_board, index_type = 1) + }) + self.score_board_manager.reshow(self.label.ms_board, index_type=1) self.score_board_manager.visible() self.mainWindow.closeEvent_.connect(self.closeEvent_) self.mainWindow.dropFileSignal.connect(self.action_OpenFile) - + @property def pixSize(self): return self._pixSize - + @pixSize.setter def pixSize(self, pixSize): pixSize = max(5, pixSize) @@ -147,11 +156,13 @@ def pixSize(self, pixSize): else: self.predefinedBoardPara[0]['pixsize'] = pixSize - self.label.setMinimumSize(QtCore.QSize(pixSize * self.column + 8, pixSize * self.row + 8)) - self.label.setMaximumSize(QtCore.QSize(pixSize * self.column + 8, pixSize * self.row + 8)) + self.label.setMinimumSize(QtCore.QSize( + pixSize * self.column + 8, pixSize * self.row + 8)) + self.label.setMaximumSize(QtCore.QSize( + pixSize * self.column + 8, pixSize * self.row + 8)) # self.label.setFixedSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) - self.reimportLEDPic(pixSize) # 重新导入图片,无磁盘io + self.reimportLEDPic(pixSize) # 重新导入图片,无磁盘io self.label_2.reloadFace(pixSize) self.set_face(14) self.showMineNum(self.mineUnFlagedNum) @@ -180,11 +191,11 @@ def game_state(self): def game_state(self, game_state: str): # print(self._game_state, " -> " ,game_state) if self._game_state in ("playing", "show", "joking") and\ - game_state not in ("playing", "show", "joking"): + game_state not in ("playing", "show", "joking"): self.timer_10ms.stop() self.unlimit_cursor() elif self._game_state in ("display", "showdisplay") and\ - game_state not in ("display", "showdisplay"): + game_state not in ("display", "showdisplay"): self.timer_video.stop() self.ui_video_control.QWidget.close() self.label.paint_cursor = False @@ -194,14 +205,13 @@ def game_state(self, game_state: str): "is_official": "--", "is_fair": "--", "mode": self.gameMode, - }) - self.score_board_manager.show(self.label.ms_board, index_type = 1) + }) + self.score_board_manager.show(self.label.ms_board, index_type=1) elif self._game_state == 'study': self.num_bar_ui.QWidget.close() self._game_state = game_state - def layMine(self, i, j): xx = self.row yy = self.column @@ -231,7 +241,7 @@ def timeCount(self): since_time_unix_2 = QtCore.QDateTime.currentDateTime().\ toMSecsSinceEpoch() - self.start_time_unix_2 if abs(t * 1000 - since_time_unix_2) > 10 and\ - (self.game_state == "playing" or self.game_state == "joking"): + (self.game_state == "playing" or self.game_state == "joking"): # 防CE作弊 self.gameRestart() @@ -240,7 +250,7 @@ def timeCount(self): # self.score_board_manager.with_namespace({ # "rtime": self.time_ms / 1000, # }) - self.score_board_manager.show(self.label.ms_board, index_type = 1) + self.score_board_manager.show(self.label.ms_board, index_type=1) def ai(self, i, j): # 0,4, 5, 6, 7, 8, 9, 10代表:标准、win7、 @@ -251,13 +261,14 @@ def ai(self, i, j): return elif self.gameMode == 6: if self.label.ms_board.board[i][j] >= 0 and \ - not ms.is_able_to_solve(self.label.ms_board.game_board, (i, j)): + not ms.is_able_to_solve(self.label.ms_board.game_board, (i, j)): board = self.label.ms_board.board.into_vec_vec() board[i][j] = -1 self.label.ms_board.board = board return elif self.gameMode == 7: - code = ms.is_guess_while_needless(self.label.ms_board.game_board, (i, j)) + code = ms.is_guess_while_needless( + self.label.ms_board.game_board, (i, j)) if code == 3: board = self.label.ms_board.board.into_vec_vec() board[i][j] = -1 @@ -268,7 +279,8 @@ def ai(self, i, j): self.label.ms_board.board = board return elif self.gameMode == 8: - code = ms.is_guess_while_needless(self.label.ms_board.game_board, (i, j)) + code = ms.is_guess_while_needless( + self.label.ms_board.game_board, (i, j)) if code == 2: board, flag = mm.enumerateChangeBoard(self.label.ms_board.board, self.label.ms_board.game_board, [(i, j)]) @@ -280,10 +292,9 @@ def ai(self, i, j): board, flag = mm.enumerateChangeBoard(self.label.ms_board.board, self.label.ms_board.game_board, [(i, j)]) - self.label.ms_board.board = board return - + # 双击时进入,可以双击猜雷 # 此处架构可以改进,放到工具箱里 def chording_ai(self, i, j): @@ -295,16 +306,16 @@ def chording_ai(self, i, j): if self.label.ms_board.mouse_state != 5 and self.label.ms_board.mouse_state != 6: return if self.label.ms_board.game_board[i][j] >= 10 or\ - self.label.ms_board.game_board[i][j] == 0: + self.label.ms_board.game_board[i][j] == 0: return if self.gameMode == 0 or self.gameMode == 4 or self.gameMode == 5: return - not_mine_round = [] # 没有标雷,且非雷 - is_mine_round = [] # 没有标雷,且是雷 - flag_not_mine_round = [] # 标雷,且非雷 - flag_is_mine_round = [] # 标雷,且是雷 - for ii in range(max(0,i-1), min(self.row,i+2)): - for jj in range(max(0,j-1), min(self.column,j+2)): + not_mine_round = [] # 没有标雷,且非雷 + is_mine_round = [] # 没有标雷,且是雷 + flag_not_mine_round = [] # 标雷,且非雷 + flag_is_mine_round = [] # 标雷,且是雷 + for ii in range(max(0, i-1), min(self.row, i+2)): + for jj in range(max(0, j-1), min(self.column, j+2)): if (ii, jj) != (i, j): if self.label.ms_board.game_board[ii][jj] == 10: if self.label.ms_board.board[ii][jj] == -1: @@ -317,7 +328,7 @@ def chording_ai(self, i, j): else: flag_not_mine_round.append((ii, jj)) if len(flag_is_mine_round) + len(flag_not_mine_round) !=\ - self.label.ms_board.board[i][j]: + self.label.ms_board.board[i][j]: # 不满足双击条件 return board = self.label.ms_board.board.into_vec_vec() @@ -330,7 +341,8 @@ def chording_ai(self, i, j): elif self.gameMode == 7: must_guess = True for (x, y) in is_mine_round + not_mine_round: - code = ms.is_guess_while_needless(self.label.ms_board.game_board, (x, y)) + code = ms.is_guess_while_needless( + self.label.ms_board.game_board, (x, y)) if code == 3: must_guess = False break @@ -346,7 +358,8 @@ def chording_ai(self, i, j): elif self.gameMode == 8: must_guess = True for (x, y) in is_mine_round + not_mine_round: - code = ms.is_guess_while_needless(self.label.ms_board.game_board, (x, y)) + code = ms.is_guess_while_needless( + self.label.ms_board.game_board, (x, y)) if code == 3: must_guess = False break @@ -360,11 +373,10 @@ def chording_ai(self, i, j): self.label.ms_board.game_board, not_mine_round + is_mine_round) self.label.ms_board.board = board - def mineAreaLeftPressed(self, i, j): if self.game_state == 'ready' or self.game_state == 'playing' or\ - self.game_state == 'joking': + self.game_state == 'joking': self.label.ms_board.step('lc', (i, j)) self.label.update() @@ -372,7 +384,8 @@ def mineAreaLeftPressed(self, i, j): elif self.game_state == 'show': # 看概率时,所有操作都移出局面外 - self.label.ms_board.step('lc', (self.row * self.pixSize, self.column * self.pixSize)) + self.label.ms_board.step( + 'lc', (self.row * self.pixSize, self.column * self.pixSize)) self.set_face(15) def mineAreaLeftRelease(self, i, j): @@ -381,10 +394,10 @@ def mineAreaLeftRelease(self, i, j): self.label.ms_board.step('lr', (i, j)) else: if self.label.ms_board.mouse_state == 4 and\ - self.label.ms_board.game_board[i// self.pixSize][j// self.pixSize] == 10: + self.label.ms_board.game_board[i // self.pixSize][j // self.pixSize] == 10: # 正式埋雷开始 self.layMine(i // self.pixSize, j // self.pixSize) - + if self.board_constraint: self.game_state = 'joking' else: @@ -398,14 +411,13 @@ def mineAreaLeftRelease(self, i, j): if self.cursor_limit: self.limit_cursor() - # 核实用的时间,防变速齿轮 self.start_time_unix_2 = QtCore.QDateTime.currentDateTime().\ - toMSecsSinceEpoch() + toMSecsSinceEpoch() self.timer_10ms.start() # 禁用双击修改指标名称公式 self.score_board_manager.editing_row = -2 - + self.label.ms_board.step('lr', (i, j)) if self.label.ms_board.game_board_state == 3: @@ -424,11 +436,11 @@ def mineAreaLeftRelease(self, i, j): elif self.game_state == 'playing' or self.game_state == 'joking': # 如果是游戏中,且是左键抬起(不是双击),且是在10上,且在局面内,则用ai劫持、处理下 if self.pos_is_in_board(i, j): - if self.label.ms_board.game_board[i// self.pixSize][j// self.pixSize] == 10 \ - and self.label.ms_board.mouse_state == 4: + if self.label.ms_board.game_board[i // self.pixSize][j // self.pixSize] == 10 \ + and self.label.ms_board.mouse_state == 4: self.ai(i // self.pixSize, j // self.pixSize) - self.chording_ai(i// self.pixSize, j// self.pixSize) - + self.chording_ai(i // self.pixSize, j // self.pixSize) + self.label.ms_board.step('lr', (i, j)) if self.label.ms_board.game_board_state == 3: @@ -444,7 +456,8 @@ def mineAreaLeftRelease(self, i, j): elif self.game_state == 'show': # 看概率时,所有操作都移出局面外 - self.label.ms_board.step('lr', (self.row * self.pixSize, self.column * self.pixSize)) + self.label.ms_board.step( + 'lr', (self.row * self.pixSize, self.column * self.pixSize)) self.set_face(14) def mineAreaRightPressed(self, i, j): @@ -454,11 +467,11 @@ def mineAreaRightPressed(self, i, j): # 假如按下左键,再切屏(比如快捷键截图),再左键抬起,再切回来,再右键按下, # 就会导致DownUp状态下右键按下。此时不应该标雷,左上角雷数也应该不变 if self.label.ms_board.game_board[i//self.pixSize][j//self.pixSize] == 11 and\ - self.label.ms_board.mouse_state == 1: + self.label.ms_board.mouse_state == 1: self.mineUnFlagedNum += 1 self.showMineNum(self.mineUnFlagedNum) elif self.label.ms_board.game_board[i//self.pixSize][j//self.pixSize] == 10 and\ - self.label.ms_board.mouse_state == 1: + self.label.ms_board.mouse_state == 1: self.mineUnFlagedNum -= 1 self.showMineNum(self.mineUnFlagedNum) self.label.ms_board.step('rc', (i, j)) @@ -467,24 +480,24 @@ def mineAreaRightPressed(self, i, j): def mineAreaRightRelease(self, i, j): if self.game_state == 'ready' or self.game_state == 'playing' or self.game_state == 'joking': - self.chording_ai(i// self.pixSize, j// self.pixSize) + self.chording_ai(i // self.pixSize, j // self.pixSize) self.label.ms_board.step('rr', (i, j)) self.label.update() self.set_face(14) elif self.game_state == 'show': # 看概率时,所有操作都移出局面外 - self.label.ms_board.step('rr', (self.row * self.pixSize, self.column * self.pixSize)) + self.label.ms_board.step( + 'rr', (self.row * self.pixSize, self.column * self.pixSize)) self.set_face(14) def mineAreaLeftAndRightPressed(self, i, j): if self.game_state == 'ready' or self.game_state == 'playing' or\ - self.game_state == 'joking': + self.game_state == 'joking': self.label.ms_board.step('cc', (i, j)) self.label.update() self.set_face(15) - def mineMouseMove(self, i, j): # 正常情况的鼠标移动事件,与高亮的显示有关 if self.game_state == 'playing' or self.game_state == 'joking' or self.game_state == 'ready': @@ -495,23 +508,24 @@ def mineMouseMove(self, i, j): if not self.pos_is_in_board(i, j): self.label_info.setText('(是雷的概率)') else: - text4 = '{:.3f}'.format(max(0, self.label.boardPossibility[i//self.pixSize][j//self.pixSize])) + text4 = '{:.3f}'.format( + max(0, self.label.boardPossibility[i//self.pixSize][j//self.pixSize])) self.label_info.setText(text4) # 播放录像时的鼠标移动事件 elif self.game_state == 'showdisplay': if not self.pos_is_in_board(i, j): self.label_info.setText('(是雷的概率)') else: - text4 = '{:.3f}'.format(max(0, self.label.ms_board.game_board_poss[i//self.pixSize][j//self.pixSize])) + text4 = '{:.3f}'.format( + max(0, self.label.ms_board.game_board_poss[i//self.pixSize][j//self.pixSize])) self.label_info.setText(text4) - def resizeWheel(self, i, x, y): # 按住ctrl滚轮,调整局面大小 # study状态下,滚轮修改局面 # 函数名要改了 if QApplication.keyboardModifiers() == Qt.ControlModifier and\ - self.game_state == 'ready' and self.label.ms_board.game_board_state == 1: + self.game_state == 'ready' and self.label.ms_board.game_board_state == 1: # 调整局面大小需要满足:ui端是ready且状态机是ready if i > 0: self.pixSize += 1 @@ -575,8 +589,10 @@ def gameStart(self): self.label.set_rcp(self.row, self.column, self.pixSize) self.game_state = 'ready' self.label.reloadCellPic(self.pixSize) - self.label.setMinimumSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) - self.label.setMaximumSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) + self.label.setMinimumSize(QtCore.QSize( + self.pixSize*self.column + 8, self.pixSize*self.row + 8)) + self.label.setMaximumSize(QtCore.QSize( + self.pixSize*self.column + 8, self.pixSize*self.row + 8)) # self.label.setMinimumSize(QtCore.QSize(8, 8)) self.label_2.reloadFace(self.pixSize) @@ -585,21 +601,23 @@ def gameStart(self): self.minimumWindow() # 点击脸时调用,或尺寸不变时重开 - def gameRestart(self, e = None): # 画界面,但是不埋雷,改数据而不是重新生成label + def gameRestart(self, e=None): # 画界面,但是不埋雷,改数据而不是重新生成label if e: - # 点脸周围时,会传入一个e参数 + # 点脸周围时,会传入一个e参数 if not (self.MinenumTimeWigdet.width() >= e.localPos().x() >= 0 and 0 <= e.localPos().y() <= self.MinenumTimeWigdet.height()): return # 此时self.label.ms_board是mm.abstract_game_board的实例 if self.game_state == 'display' or self.game_state == 'showdisplay': # self.timer_video.stop() # self.ui_video_control.QWidget.close() - self.label.ms_board = ms.BaseVideo([[0] * self.column for _ in range(self.row)], self.pixSize) + self.label.ms_board = ms.BaseVideo( + [[0] * self.column for _ in range(self.row)], self.pixSize) self.label.ms_board.mode = self.gameMode elif self.game_state == 'study': # self.num_bar_ui.QWidget.close() self.score_board_manager.visible() - self.label.ms_board = ms.BaseVideo([[0] * self.column for _ in range(self.row)], self.pixSize) + self.label.ms_board = ms.BaseVideo( + [[0] * self.column for _ in range(self.row)], self.pixSize) self.label.ms_board.mode = self.gameMode self.label_info.setText(self.player_identifier) self.game_state = 'ready' @@ -620,8 +638,8 @@ def gameRestart(self, e = None): # 画界面,但是不埋雷,改数据而 # self.label.paint_cursor = False # self.label.setMouseTracking(False) # 鼠标未按下时,组织移动事件回调 - # 游戏结束画残局,改状态 + def gameFinished(self): if self.label.ms_board.game_board_state == 3 and self.end_then_flag: self.label.ms_board.win_then_flag_all_mine() @@ -634,12 +652,11 @@ def gameFinished(self): self.score_board_manager.with_namespace({ "is_official": self.is_official(), "is_fair": self.is_fair() - }) - self.score_board_manager.show(self.label.ms_board, index_type = 2) + }) + self.score_board_manager.show(self.label.ms_board, index_type=2) self.enable_screenshot() self.unlimit_cursor() - def gameWin(self): # 成功后改脸和状态变量,停时间 self.timer_10ms.stop() self.score_board_manager.editing_row = -1 @@ -656,10 +673,8 @@ def gameWin(self): # 成功后改脸和状态变量,停时间 self.dump_evf_file_data() self.save_evf_file() - self.gameFinished() - # 尝试弹窗,没有破纪录则不弹 if self.auto_notification and self.is_fair(): self.try_record_pop() @@ -671,28 +686,31 @@ def checksum_module_ok(self): return hashlib.sha256(bytes(metaminesweeper_checksum.get_self_key())).hexdigest() ==\ '590028493bb58a25ffc76e2e2ad490df839a1f449435c35789d3119ca69e5d4f' - # 搜集数据,生成evf文件的二进制数据,但是不保存 + def dump_evf_file_data(self): if isinstance(self.label.ms_board, ms.BaseVideo): - self.label.ms_board.use_question = False # 禁用问号是共识 + self.label.ms_board.use_question = False # 禁用问号是共识 self.label.ms_board.use_cursor_pos_lim = self.cursor_limit self.label.ms_board.use_auto_replay = self.auto_replay > 0 - + self.label.ms_board.is_fair = self.is_fair() self.label.ms_board.is_official = self.is_official() - + self.label.ms_board.software = superGUI.version self.label.ms_board.mode = self.gameMode self.label.ms_board.player_identifier = self.player_identifier self.label.ms_board.race_identifier = self.race_identifier self.label.ms_board.uniqueness_identifier = self.unique_identifier - self.label.ms_board.country = "XX" if not self.country else country_name[self.country].upper() - self.label.ms_board.device_uuid = hashlib.md5(bytes(str(uuid.getnode()).encode())).hexdigest().encode( "UTF-8" ) - + self.label.ms_board.country = "XX" if not self.country else country_name[self.country].upper( + ) + self.label.ms_board.device_uuid = hashlib.md5( + bytes(str(uuid.getnode()).encode())).hexdigest().encode("UTF-8") + self.label.ms_board.generate_evf_v4_raw_data() # 补上校验值 - checksum = self.checksum_guard.get_checksum(self.label.ms_board.raw_data[:-1]) + checksum = self.checksum_guard.get_checksum( + self.label.ms_board.raw_data[:-1]) self.label.ms_board.checksum_evf_v4 = checksum return elif isinstance(self.label.ms_board, ms.EvfVideo): @@ -707,10 +725,10 @@ def dump_evf_file_data(self): self.label.ms_board.generate_evf_v4_raw_data() return - # 将evf数据存成evf文件 # 调试的时候不会自动存录像,见checksum_module_ok # 菜单保存的回调。以及游戏结束自动保存。 + def save_evf_file(self): if not os.path.exists(self.replay_path): os.mkdir(self.replay_path) @@ -726,12 +744,12 @@ def save_evf_file(self): if self.game_state == "display" or self.game_state == "showdisplay": self.label.ms_board.current_time = 999999.9 file_name = self.replay_path + '\\' + filename_level +\ - f'{self.label.ms_board.mode}' + '_' +\ - f'{self.label.ms_board.rtime:.3f}' +\ - '_' + f'{self.label.ms_board.bbbv}' +\ - '_' + f'{self.label.ms_board.bbbv_s:.3f}' +\ - '_' + self.label.ms_board.player_identifier - + f'{self.label.ms_board.mode}' + '_' +\ + f'{self.label.ms_board.rtime:.3f}' +\ + '_' + f'{self.label.ms_board.bbbv}' +\ + '_' + f'{self.label.ms_board.bbbv_s:.3f}' +\ + '_' + self.label.ms_board.player_identifier + if not self.label.ms_board.is_completed: file_name += "_fail" if not self.label.ms_board.is_fair: @@ -740,11 +758,10 @@ def save_evf_file(self): file_name += "_trans" elif not self.checksum_module_ok(): file_name += "_fake" - - self.label.ms_board.save_to_evf_file(file_name) + self.label.ms_board.save_to_evf_file(file_name) - def gameFailed(self): # 失败后改脸和状态变量 + def gameFailed(self): # 失败后改脸和状态变量 self.timer_10ms.stop() self.score_board_manager.editing_row = -1 @@ -816,7 +833,6 @@ def try_record_pop(self): else: raise RuntimeError() - if b.rtime < self.record_setting.value(f"{record_key}/rtime", None, float): if b.rce == 0 and self.gameMode == 0: self.record_setting.set_value(f"{record_key}/rtime", b.rtime) @@ -824,7 +840,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/rtime", b.rtime) elif b.rce == 0 and self.gameMode == 0 and\ - b.rtime < self.record_setting.value(f"{LNF}/rtime", None, float): + b.rtime < self.record_setting.value(f"{LNF}/rtime", None, float): self.record_setting.set_value(f"{LNF}/rtime", b.rtime) nf_items.append(1) else: @@ -836,7 +852,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/bbbv_s", b.bbbv_s) elif b.rce == 0 and self.gameMode == 0 and\ - b.bbbv_s > self.record_setting.value(f"{LNF}/bbbv_s", None, float): + b.bbbv_s > self.record_setting.value(f"{LNF}/bbbv_s", None, float): self.record_setting.set_value(f"{LNF}/bbbv_s", b.bbbv_s) nf_items.append(3) else: @@ -848,7 +864,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/stnb", b.stnb) elif b.rce == 0 and self.gameMode == 0 and\ - b.stnb > self.record_setting.value(f"{LNF}/stnb", None, float): + b.stnb > self.record_setting.value(f"{LNF}/stnb", None, float): self.record_setting.set_value(f"{LNF}/stnb", b.stnb) nf_items.append(5) else: @@ -860,7 +876,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/ioe", b.ioe) elif b.rce == 0 and self.gameMode == 0 and\ - b.ioe > self.record_setting.value(f"{LNF}/ioe", None, float): + b.ioe > self.record_setting.value(f"{LNF}/ioe", None, float): self.record_setting.set_value(f"{LNF}/ioe", b.ioe) nf_items.append(7) else: @@ -872,7 +888,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/path", b.path) elif b.rce == 0 and self.gameMode == 0 and\ - b.path < self.record_setting.value(f"{LNF}/path", None, float): + b.path < self.record_setting.value(f"{LNF}/path", None, float): self.record_setting.set_value(f"{LNF}/path", b.path) nf_items.append(9) else: @@ -884,7 +900,7 @@ def try_record_pop(self): else: self.record_setting.set_value(f"{record_key}/rqp", b.rqp) elif b.rce == 0 and self.gameMode == 0 and\ - b.rqp < self.record_setting.value(f"{LNF}/rqp", None, float): + b.rqp < self.record_setting.value(f"{LNF}/rqp", None, float): self.record_setting.set_value(f"{LNF}/rqp", b.rqp) nf_items.append(11) else: @@ -894,13 +910,15 @@ def try_record_pop(self): if self.gameMode == 0: if b.level == 3: if b.rtime < self.record_setting.value(f"BEGINNER/{b.bbbv}", None, float): - self.record_setting.set_value(f"BEGINNER/{b.bbbv}", b.rtime) + self.record_setting.set_value( + f"BEGINNER/{b.bbbv}", b.rtime) del_items += [14, 15] else: del_items += [13, 14, 15] elif b.level == 4: if b.rtime < self.record_setting.value(f"INTERMEDIATE/{b.bbbv}", None, float): - self.record_setting.set_value(f"INTERMEDIATE/{b.bbbv}", b.rtime) + self.record_setting.set_value( + f"INTERMEDIATE/{b.bbbv}", b.rtime) del_items += [13, 15] else: del_items += [13, 14, 15] @@ -916,7 +934,8 @@ def try_record_pop(self): del_items += [13, 14, 15] if len(del_items) < 9: - ui = gameRecordPop.ui_Form(self.r_path, del_items, b.bbbv, nf_items, self.mainWindow) + ui = gameRecordPop.ui_Form( + self.r_path, del_items, b.bbbv, nf_items, self.mainWindow) ui.Dialog.setModal(True) ui.label_16.setText(mode_text) ui.Dialog.show() @@ -928,8 +947,8 @@ def showMineNum(self, n): self.mineNumShow = n if n >= 0 and n <= 999: self.label_11.setPixmap(self.pixmapLEDNum[n//100]) - self.label_12.setPixmap(self.pixmapLEDNum[n//10%10]) - self.label_13.setPixmap(self.pixmapLEDNum[n%10]) + self.label_12.setPixmap(self.pixmapLEDNum[n//10 % 10]) + self.label_13.setPixmap(self.pixmapLEDNum[n % 10]) elif n < 0: self.label_11.setPixmap(self.pixmapLEDNum[0]) self.label_12.setPixmap(self.pixmapLEDNum[0]) @@ -943,8 +962,8 @@ def showTime(self, t): # 显示剩余时间,时间数大于等于0,小于等于999秒,整数 if t >= 0 and t <= 999: self.label_31.setPixmap(self.pixmapLEDNum[t//100]) - self.label_32.setPixmap(self.pixmapLEDNum[t//10%10]) - self.label_33.setPixmap(self.pixmapLEDNum[t%10]) + self.label_32.setPixmap(self.pixmapLEDNum[t//10 % 10]) + self.label_33.setPixmap(self.pixmapLEDNum[t % 10]) return elif t >= 1000: return @@ -975,17 +994,18 @@ def predefined_Board(self, k): self.label.ms_board.reset(row, column, self.pixSize) else: # 解决播放录像时快捷键切换难度报错 - self.label.ms_board = ms.BaseVideo([[0] * column for _ in range(row)], self.pixSize) + self.label.ms_board = ms.BaseVideo( + [[0] * column for _ in range(row)], self.pixSize) self.gameMode = self.predefinedBoardPara[k]['gamemode'] self.score_board_manager.with_namespace({ "mode": self.gameMode, - }) + }) self.score_board_manager.show(self.label.ms_board, index_type=1) self.board_constraint = self.predefinedBoardPara[k]['board_constraint'] self.attempt_times_limit = self.predefinedBoardPara[k]['attempt_times_limit'] - # 菜单回放的回调 + def replay_game(self): if not isinstance(self.label.ms_board, ms.BaseVideo): return @@ -993,15 +1013,14 @@ def replay_game(self): return self.dump_evf_file_data() raw_data = bytes(self.label.ms_board.raw_data) - + video = ms.EvfVideo("virtual.evf", raw_data) self.play_video(video) - def action_CEvent(self): # 点击菜单栏的自定义后回调 self.actionChecked('C') - ui = gameDefinedParameter.ui_Form(self.r_path, self.row, self.column, + ui = gameDefinedParameter.ui_Form(self.r_path, self.row, self.column, self.mineNum, self.mainWindow) ui.Dialog.setModal(True) ui.Dialog.show() @@ -1051,7 +1070,6 @@ def setBoard_and_start(self, row, column, mineNum): else: self.gameRestart() - def action_NEvent(self): # 游戏设置 self.actionChecked('N') @@ -1060,7 +1078,7 @@ def action_NEvent(self): ui.Dialog.show() ui.Dialog.exec_() if ui.alter: - + self.pixSize = ui.pixSize self.gameStart() self.gameMode = ui.gameMode @@ -1100,11 +1118,11 @@ def action_NEvent(self): for child in self.mainWindow.findChildren(QWidget): # 设置子窗口的透明度 child.setWindowOpacity(ui.transparency / 100) - + self.score_board_manager.with_namespace({ "race_identifier": ui.race_identifier, "mode": self.gameMode, - }) + }) self.score_board_manager.show(self.label.ms_board, index_type=1) def action_QEvent(self): @@ -1125,7 +1143,6 @@ def action_mouse_setting(self): os.system("start rundll32.exe shell32.dll,Control_RunDLL main.cpl,,2") except: ... - def action_AEvent(self): # 关于 @@ -1137,11 +1154,17 @@ def action_AEvent(self): def auto_Update(self): data = { - "Github": "https://api.github.com/repos/", - "Gitee": "https://api.gitee.com/repos/", + "Github": { + "url": "https://api.github.com/repos/eee555/Metasweeper", + "token": "" + }, + "gitee": { + "url": "https://gitee.com/api/v5/repos/ee55/Metasweeper", + "token": "02d95b894b8a5ccb3731a9464b2a6f2b" + } } - update_dialog = CheckUpdateGui(GitHub(SourceManager(data), "eee555", - "Metasweeper", superGUI.version, "(\d+\.\d+\.\d+)"), parent = self) + update_dialog = CheckUpdateGui(GitHub(SourceManager( + data, "Github"), superGUI.version, "(\d+\.\d+\.\d+)"), parent=self) update_dialog.setModal(True) update_dialog.show() update_dialog.exec_() @@ -1154,8 +1177,7 @@ def screenShot(self): if self.game_state == "display" or self.game_state == "showdisplay": self.video_playing = False self.timer_video.stop() - - + self.enable_screenshot() ui = captureScreen.CaptureScreen() @@ -1167,7 +1189,8 @@ def screenShot(self): # 会报两种runtimeerror,标记阶段无解的局面、枚举阶段无解的局面 try: - ans = ms.cal_possibility_onboard(ui.board, 0.20625 if len(ui.board[0]) >= 24 else 0.15625) + ans = ms.cal_possibility_onboard( + ui.board, 0.20625 if len(ui.board[0]) >= 24 else 0.15625) except: return @@ -1175,7 +1198,6 @@ def screenShot(self): # 概率矩阵为空就是出错了 return - # 连续截屏时 # if self.game_state == 'study': # self.num_bar_ui.QWidget.close() @@ -1196,32 +1218,34 @@ def screenShot(self): self.row = len(ui.board) self.column = len(ui.board[0]) - self.num_bar_ui = mine_num_bar.ui_Form(ans[1], self.pixSize * self.row, self.mainWindow) + self.num_bar_ui = mine_num_bar.ui_Form( + ans[1], self.pixSize * self.row, self.mainWindow) self.num_bar_ui.QWidget.barSetMineNum.connect(self.showMineNum) - self.num_bar_ui.QWidget.barSetMineNumCalPoss.connect(self.render_poss_on_board) + self.num_bar_ui.QWidget.barSetMineNumCalPoss.connect( + self.render_poss_on_board) self.num_bar_ui.setSignal() # self.mainWindow.closeEvent_.connect(self.num_bar_ui.QWidget.close) self.timer_close_bar = QTimer() - self.timer_close_bar.timeout.connect(lambda:self.num_bar_ui.QWidget.show()) + self.timer_close_bar.timeout.connect( + lambda: self.num_bar_ui.QWidget.show()) self.timer_close_bar.setSingleShot(True) self.timer_close_bar.start(1) # self.num_bar_ui.QWidget.show() # self.setBoard_and_start(len(ui.board), len(ui.board[0]), ans[1][1]) self.setBoard(self.row, self.column, ans[1][1]) - + self.label.paintPossibility = True self.label.set_rcp(self.row, self.column, self.pixSize) - + self.label.ms_board.game_board = ui.board self.label.ms_board.mouse_state = 1 self.label.ms_board.game_board_state = 1 self.mineNumShow = ans[1][1] self.showMineNum(self.mineNumShow) self.label.boardPossibility = ans[0] - self.label.update() # self.label.setMouseTracking(True) @@ -1231,7 +1255,8 @@ def screenShot(self): def render_poss_on_board(self): # 雷数条拉动后、改局面后,显示雷数并展示 try: - ans = ms.cal_possibility_onboard(self.label.ms_board.game_board, self.mineNumShow) + ans = ms.cal_possibility_onboard( + self.label.ms_board.game_board, self.mineNumShow) except: try: ans = ms.cal_possibility_onboard(self.label.ms_board.game_board, @@ -1263,7 +1288,6 @@ def render_poss_on_board(self): self.showMineNum(self.mineNumShow) - def showScores(self): # 按空格 if self.game_state == 'win' or self.game_state == 'fail': @@ -1282,11 +1306,11 @@ def showScores(self): # 删去用户标的雷,因为可能标错 # game_board = list(map(lambda x: list(map(lambda y: min(y, 10), x)), # self.label.ms_board.game_board)) - ans = ms.cal_possibility_onboard(self.label.ms_board.game_board, mineNum) + ans = ms.cal_possibility_onboard( + self.label.ms_board.game_board, mineNum) self.label.boardPossibility = ans[0] self.label.update() - def mineKeyReleaseEvent(self, keyName): # 松开空格键 if keyName == 'Space': @@ -1318,10 +1342,11 @@ def refreshSettingsDefault(self): self.game_setting.sync() # 打开录像文件的回调 - def action_OpenFile(self, openfile_name = None): + def action_OpenFile(self, openfile_name=None): if not openfile_name: openfile_name = QFileDialog.\ - getOpenFileName(self.mainWindow, '打开文件','../replay','All(*.avf *.evf *.rmv *.mvf);;Arbiter video(*.avf);;Metasweeper video(*.evf);;Vienna MineSweeper video(*.rmv);;Minesweeper Clone 0.97(*.mvf)') + getOpenFileName(self.mainWindow, '打开文件', '../replay', + 'All(*.avf *.evf *.rmv *.mvf);;Arbiter video(*.avf);;Metasweeper video(*.evf);;Vienna MineSweeper video(*.rmv);;Minesweeper Clone 0.97(*.mvf)') openfile_name = openfile_name[0] # 实例化 if not openfile_name: @@ -1355,18 +1380,18 @@ def play_video(self, video): # 检查evf的checksum,其余录像没有鉴定能力 if isinstance(video, ms.EvfVideo): self.score_board_manager.with_namespace({ - "checksum_ok": self.checksum_guard.\ - valid_checksum(video.raw_data[:-33], video.checksum), - }) + "checksum_ok": self.checksum_guard. + valid_checksum(video.raw_data[:-33], video.checksum), + }) else: self.score_board_manager.with_namespace({ "checksum_ok": False, - }) + }) self.score_board_manager.with_namespace({ "is_official": video.is_official, "is_fair": video.is_fair, "mode": video.mode, - }) + }) video.analyse_for_features(["high_risk_guess", "jump_judge", "needless_guess", "mouse_trace", "vision_transfer", "survive_poss"]) @@ -1376,27 +1401,29 @@ def play_video(self, video): t = event.time comment = event.comments if comment: - comments.append((t, [i.split(': ') for i in comment.split(';')[:-1]])) + comments.append((t, [i.split(': ') + for i in comment.split(';')[:-1]])) # 调整窗口 if (video.row, video.column) != (self.row, self.column): self.setBoard(video.row, video.column, video.mine_num) self.label.paintPossibility = False self.label.set_rcp(self.row, self.column, self.pixSize) # self.label.reloadCellPic(self.pixSize) - self.label.setMinimumSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) - self.label.setMaximumSize(QtCore.QSize(self.pixSize*self.column + 8, self.pixSize*self.row + 8)) + self.label.setMinimumSize(QtCore.QSize( + self.pixSize*self.column + 8, self.pixSize*self.row + 8)) + self.label.setMaximumSize(QtCore.QSize( + self.pixSize*self.column + 8, self.pixSize*self.row + 8)) self.label_2.reloadFace(self.pixSize) self.minimumWindow() - - self.timer_video = QTimer() self.timer_video.timeout.connect(self.video_playing_step) - self.ui_video_control = videoControl.ui_Form(self.r_path, video, comments, + self.ui_video_control = videoControl.ui_Form(self.r_path, video, comments, self.game_setting, self.mainWindow) # self.mainWindow.closeEvent_.connect(self.ui_video_control.QWidget.close) self.ui_video_control.pushButton_play.clicked.connect(self.video_play) - self.ui_video_control.pushButton_replay.clicked.connect(self.video_replay) + self.ui_video_control.pushButton_replay.clicked.connect( + self.video_replay) self.ui_video_control.videoSetTime.connect(self.video_set_time) self.ui_video_control.label_speed.wEvent.connect(self.video_set_speed) for labels in self.ui_video_control.comments_labels: @@ -1405,11 +1432,11 @@ def play_video(self, video): labels[2].Release.connect(self.video_set_a_time) self.ui_video_control.QWidget.show() - self.video_time = video.video_start_time # 录像当前时间 - self.video_stop_time = video.video_end_time # 录像停止时间 - self.video_time_step = 0.01 # 录像时间的步长,定时器始终是10毫秒 + self.video_time = video.video_start_time # 录像当前时间 + self.video_stop_time = video.video_end_time # 录像停止时间 + self.video_time_step = 0.01 # 录像时间的步长,定时器始终是10毫秒 self.label.paint_cursor = True - self.video_playing = True # 录像正在播放 + self.video_playing = True # 录像正在播放 # 禁用双击修改指标名称公式 self.score_board_manager.editing_row = -2 @@ -1420,9 +1447,8 @@ def play_video(self, video): self.label_info.setText(self.label.ms_board.player_identifier) # 改成录像的国旗 self.set_country_flag(self.label.ms_board.country) - - self.timer_video.start(10) + self.timer_video.start(10) def video_playing_step(self): # 播放录像时定时器的回调 @@ -1431,14 +1457,16 @@ def video_playing_step(self): self.timer_video.stop() self.video_playing = False self.label.update() - self.score_board_manager.show(self.label.ms_board, index_type = 3) + self.score_board_manager.show(self.label.ms_board, index_type=3) self.video_time += self.video_time_step self.showTime(int(self.video_time)) self.ui_video_control.horizontalSlider_time.blockSignals(True) - self.ui_video_control.horizontalSlider_time.setValue(int(self.video_time * 1000)) + self.ui_video_control.horizontalSlider_time.setValue( + int(self.video_time * 1000)) self.ui_video_control.horizontalSlider_time.blockSignals(False) self.ui_video_control.doubleSpinBox_time.blockSignals(True) - self.ui_video_control.doubleSpinBox_time.setValue(self.label.ms_board.time) + self.ui_video_control.doubleSpinBox_time.setValue( + self.label.ms_board.time) self.ui_video_control.doubleSpinBox_time.blockSignals(False) def video_play(self): @@ -1465,7 +1493,7 @@ def video_set_time(self, t): self.video_time = t / 1000 self.label.ms_board.current_time = self.video_time self.label.update() - self.score_board_manager.show(self.label.ms_board, index_type = 3) + self.score_board_manager.show(self.label.ms_board, index_type=3) def video_set_a_time(self, t): # 把录像定位到某一段时间,默认前后一秒,自动播放。是点录像事件的回调 @@ -1499,7 +1527,7 @@ def set_face(self, face_type): pixmap = QPixmap(self.pixmapNum[face_type]) self.label_2.setPixmap(pixmap) self.label_2.setScaledContents(True) - + def hidden_score_board(self): # 按/隐藏计数器,再按显示 if self.game_state == 'study': @@ -1530,28 +1558,32 @@ class RECT(ctypes.Structure): ("right", ctypes.c_long), ("bottom", ctypes.c_long)] # 创建RECT实例 - r = RECT(rect.left() + 4, rect.top() + 4, + r = RECT(rect.left() + 4, rect.top() + 4, rect.right() - 4, rect.bottom() - 4) # 调用Windows API函数ClipCursor来限制光标 ctypes.windll.user32.ClipCursor(ctypes.byref(r)) - def closeEvent_(self): # 主窗口关闭的回调 self.unlimit_cursor() # self.score_board_manager.close() - self.game_setting.set_value("DEFAULT/mainWinTop", str(self.mainWindow.x())) - self.game_setting.set_value("DEFAULT/mainWinLeft", str(self.mainWindow.y())) + self.game_setting.set_value( + "DEFAULT/mainWinTop", str(self.mainWindow.x())) + self.game_setting.set_value( + "DEFAULT/mainWinLeft", str(self.mainWindow.y())) self.game_setting.set_value("DEFAULT/row", str(self.row)) self.game_setting.set_value("DEFAULT/column", str(self.column)) self.game_setting.set_value("DEFAULT/mineNum", str(self.mineNum)) - + if (self.row, self.column, self.mineNum) == (8, 8, 10): - self.game_setting.set_value("BEGINNER/gamemode", str(self.gameMode)) + self.game_setting.set_value( + "BEGINNER/gamemode", str(self.gameMode)) self.game_setting.set_value("BEGINNER/pixsize", str(self.pixSize)) elif (self.row, self.column, self.mineNum) == (16, 16, 40): - self.game_setting.set_value("INTERMEDIATE/gamemode", str(self.gameMode)) - self.game_setting.set_value("INTERMEDIATE/pixsize", str(self.pixSize)) + self.game_setting.set_value( + "INTERMEDIATE/gamemode", str(self.gameMode)) + self.game_setting.set_value( + "INTERMEDIATE/pixsize", str(self.pixSize)) elif (self.row, self.column, self.mineNum) == (16, 30, 99): self.game_setting.set_value("EXPERT/gamemode", str(self.gameMode)) self.game_setting.set_value("EXPERT/pixsize", str(self.pixSize)) @@ -1561,12 +1593,3 @@ def closeEvent_(self): self.game_setting.sync() self.record_setting.sync() - - - - - - - - - From a00e2f34440fd5c9e24dfa89f4e06bd7072527e9 Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 21:15:08 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E9=9A=90=E8=97=8Fgitee=E4=B8=8B=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E7=9A=84=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CheckUpdateGui.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/CheckUpdateGui.py b/src/CheckUpdateGui.py index 68226f7..171b2b2 100644 --- a/src/CheckUpdateGui.py +++ b/src/CheckUpdateGui.py @@ -102,13 +102,14 @@ def initUi(self): self.titleWidget.setLayout(row1) self.titleWidget.setContentsMargins(0, 0, 0, 0) formLayout = QFormLayout() - urlLabel = QLabel() - urlLabel.setText("" + QObject.tr(self, "open external links") + "") - urlLabel.setOpenExternalLinks(True) - dataLayout = QVBoxLayout() formLayout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) - formLayout.addRow(QObject.tr(self, "html_url"), urlLabel) + dataLayout = QVBoxLayout() + if self.release.html_url != "": + urlLabel = QLabel() + urlLabel.setText("" + QObject.tr(self, "open external links") + "") + urlLabel.setOpenExternalLinks(True) + formLayout.addRow(QObject.tr(self, "html_url"), urlLabel) formLayout.addRow(QObject.tr(self, "name"), QLabel(self.release.assets_name)) if self.release.assets_content_type != "": From 87147ba73d761dc33b0a95885b725e9ff886f99b Mon Sep 17 00:00:00 2001 From: ljzloser <1312358581@qq.com> Date: Sun, 2 Mar 2025 21:26:45 +0800 Subject: [PATCH 7/7] =?UTF-8?q?refactor(api,=20gui):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=B0=86'token'=E5=AD=97=E6=AE=B5=E6=9B=B4=E5=90=8D=E4=B8=BA't?= =?UTF-8?q?'=20=E7=BB=9F=E4=B8=80=E6=9B=B4=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=B8=AD=20GitHub=20=E5=92=8C=20Gitee=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=AD'token'=E4=B8=BA't'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/githubApi.py | 2 +- src/mineSweeperGUI.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/githubApi.py b/src/githubApi.py index 23347ce..75d4e34 100644 --- a/src/githubApi.py +++ b/src/githubApi.py @@ -84,7 +84,7 @@ def currentSource(self): @property def token(self) -> str: - return self.__sources[self.currentSource]['token'] + return self.__sources[self.currentSource]['t'] @property def tokenUrl(self) -> str: diff --git a/src/mineSweeperGUI.py b/src/mineSweeperGUI.py index 7c4a539..5ed0a8e 100644 --- a/src/mineSweeperGUI.py +++ b/src/mineSweeperGUI.py @@ -1156,11 +1156,11 @@ def auto_Update(self): data = { "Github": { "url": "https://api.github.com/repos/eee555/Metasweeper", - "token": "" + "t": "" }, "gitee": { "url": "https://gitee.com/api/v5/repos/ee55/Metasweeper", - "token": "02d95b894b8a5ccb3731a9464b2a6f2b" + "t": "02d95b894b8a5ccb3731a9464b2a6f2b" } } update_dialog = CheckUpdateGui(GitHub(SourceManager(