Python游戏开发之《人机大战象棋》-附完备源码-python教程 ...

打印 上一主题 下一主题

主题 719|帖子 719|积分 2157

本日给各人带来的是人机大战的象棋
中国象棋

首先绘制一下棋盘,看看样子:

黑白经典款
绘制棋盘:
  1. class Board(QLabel):
  2.     '''
  3.     棋盘坐标与屏幕坐标类似,左上角为 (0, 0),右下角为 (8, 9)
  4.     '''
  5.     BOARD = str(dirpath / u"images/board.png")
  6.     MARK = str(dirpath / u"images/mark.png")
  7.     CHECK = str(dirpath / u"images/check.png")
  8.     FAVICON = str(dirpath / u"images/black_bishop.png")
  9.     IMAGES = {
  10.         Chess.R: str(dirpath / 'images/red_rook.png'),
  11.         Chess.N: str(dirpath / 'images/red_knight.png'),
  12.         Chess.B: str(dirpath / 'images/red_bishop.png'),
  13.         Chess.A: str(dirpath / 'images/red_advisor.png'),
  14.         Chess.K: str(dirpath / 'images/red_king.png'),
  15.         Chess.C: str(dirpath / 'images/red_cannon.png'),
  16.         Chess.P: str(dirpath / 'images/red_pawn.png'),
  17.         Chess.r: str(dirpath / 'images/black_rook.png'),
  18.         Chess.n: str(dirpath / 'images/black_knight.png'),
  19.         Chess.b: str(dirpath / 'images/black_bishop.png'),
  20.         Chess.a: str(dirpath / 'images/black_advisor.png'),
  21.         Chess.k: str(dirpath / 'images/black_king.png'),
  22.         Chess.c: str(dirpath / 'images/black_cannon.png'),
  23.         Chess.p: str(dirpath / 'images/black_pawn.png'),
  24.     }
  25.     ANIMATION_DURATION = 280
  26.     flags = QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint
  27.     signal_class = BoardSignal
  28.     def __init__(self, parent=None, callback=None):
  29.         super().__init__(parent=parent)
  30.         self.csize = 60
  31.         self.board_image = QtGui.QPixmap(self.BOARD)
  32.         if parent is None:
  33.             self.setWindowIcon(QtGui.QIcon(self.FAVICON))
  34.             self.setWindowTitle(u"Chinese Chess")
  35.             # self.setWindowFlags(self.flags)
  36.         # https://www.mfitzp.com/tutorials/packaging-pyqt5-pyside2-applications-windows-pyinstaller/
  37.         app = QtWidgets.QApplication.instance()
  38.         if app:
  39.             app.setWindowIcon(QtGui.QIcon(self.FAVICON))
  40.         if os.name == 'nt':
  41.             logger.info("set model id")
  42.             ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
  43.                 f'StevenBaby.Chess.{VERSION}'
  44.             )
  45.         self.setObjectName(u"Board")
  46.         self.setScaledContents(True)
  47.         self.animate = QtCore.QPropertyAnimation(self, b'geometry', self)
  48.         self.resize(self.csize * Chess.W, self.csize * Chess.H)
  49.         for chess, path in self.IMAGES.items():
  50.             self.IMAGES[chess] = QPixmap(path)
  51.         mark = QPixmap(self.MARK)
  52.         # 棋盘起始标记
  53.         self.mark1 = QLabel(self)
  54.         self.mark1.setPixmap(mark)
  55.         self.mark1.setScaledContents(True)
  56.         self.mark1.setVisible(False)
  57.         # 棋盘落子标记
  58.         self.mark2 = QLabel(self)
  59.         self.mark2.setPixmap(mark)
  60.         self.mark2.setScaledContents(True)
  61.         self.mark2.setVisible(False)
  62.         check = QPixmap(self.CHECK)
  63.         # 将军棋子标记
  64.         self.mark3 = QLabel(self)
  65.         self.mark3.setPixmap(check)
  66.         self.mark3.setScaledContents(True)
  67.         self.mark3.setVisible(False)
  68.         self.signal = self.signal_class()
  69.         self.signal.refresh.connect(self.refresh)
  70.         self.labels = mat(zeros((Chess.W, Chess.H,)), dtype=QtWidgets.QLabel)
  71.         self.board = mat(zeros((Chess.W, Chess.H,)), dtype=int)
  72.         self.fpos = None
  73.         self.tpos = None
  74.         self.check = None
  75.         self.reverse = False
  76.         self.update()
  77.         self.callback = callback
  78.     def setBoard(self, board, fpos=None, tpos=None):
  79.         # 设置 棋盘 board,以及该步,的棋子从哪儿 fpos,到哪儿 tpos
  80.         # 由于 该函数可能在多个线程中调用,所以下面触发 signal.refresh
  81.         # QT 会自动将刷新棋盘的重任放到主线程去做
  82.         # 如果直接在非主线程调用 refresh 函数,程序可能莫名其妙的死掉。
  83.         self.board = board
  84.         self.fpos = fpos
  85.         self.tpos = tpos
  86.         self.signal.refresh.emit()
复制代码
这个主要是绘制棋盘和棋盘中棋子移动产生的回调。
绘制棋子:
  1. # coding=utf-8
  2. import numpy as np
  3. from logger import logger
  4. class Chess(object):
  5.     NONE = 0
  6.     RMASK = 16  # 第五位表示红方 比如红車 0b01_0_001
  7.     # 判断棋子是不是黑方只需要 按位与 0b0100000 就可以了
  8.     BMASK = 32  # 第六位表示黑方 比如黑炮 0b10_0_110
  9.     # 判断棋子是不是黑方只需要 按位与 0b100000 就可以了
  10.     RED = RMASK
  11.     BLACK = BMASK
  12.     TMASK = 0b110000  # 棋子颜色掩码,可能的取值为 16 和 32
  13.     CMASK = 0b111  # 棋子掩码
  14.     # 棋子使用 1-7 来表示,二进制就是 0b001 - 0b111
  15.     # INDEX = {
  16.     #     '帥': (0, 0), '仕': (1, 0), '相': (2, 0), '傌': (3, 0), '俥': (4, 0), '炮': (5, 0), '兵': (6, 0),
  17.     #     '將': (0, 1), '士': (1, 1), '象': (2, 1), '馬': (3, 1), '車': (4, 1), '砲': (5, 1), '卒': (6, 1),
  18.     # }
  19.     KING = 1
  20.     ADVISOR = 2
  21.     BISHOP = 3
  22.     KNIGHT = 4
  23.     ROOK = 5
  24.     CANNON = 6
  25.     PAWN = 7
  26.     P = PAWN | RMASK
  27.     R = ROOK | RMASK
  28.     N = KNIGHT | RMASK
  29.     B = BISHOP | RMASK
  30.     A = ADVISOR | RMASK
  31.     C = CANNON | RMASK
  32.     K = KING | RMASK
  33.     p = PAWN | BMASK
  34.     r = ROOK | BMASK
  35.     n = KNIGHT | BMASK
  36.     b = BISHOP | BMASK
  37.     a = ADVISOR | BMASK
  38.     c = CANNON | BMASK
  39.     k = KING | BMASK
  40.     WIDTH = 9
  41.     HEIGHT = 10
  42.     W = WIDTH
  43.     H = HEIGHT
  44.     NAMES = {
  45.         0: ' ',
  46.         P: 'P',
  47.         R: 'R',
  48.         N: 'N',
  49.         B: 'B',
  50.         A: 'A',
  51.         C: 'C',
  52.         K: 'K',
  53.         p: 'p',
  54.         r: 'r',
  55.         n: 'n',
  56.         b: 'b',
  57.         a: 'a',
  58.         c: 'c',
  59.         k: 'k',
  60.     }
  61.     CHESSES = {P, R, N, B, A, C, K, p, r, n, b, a, c, k, }
  62.     @staticmethod
  63.     def invert(chess):
  64.         # 转换棋子颜色
  65.         return (chess & 7) | ((~chess) & 0b110000)
  66.     @staticmethod
  67.     def is_red(chess):
  68.         return chess & Chess.RED
  69.     @staticmethod
  70.     def is_black(chess):
  71.         return chess & Chess.BLACK
  72.     @staticmethod
  73.     def chess(c):
  74.         return c & Chess.CMASK
  75.     @staticmethod
  76.     def color(c):
  77.         return c & Chess.TMASK
  78.     ORIGIN = np.mat(
  79.         np.array(
  80.             [
  81.                 [r, 0, 0, p, 0, 0, P, 0, 0, R],
  82.                 [n, 0, c, 0, 0, 0, 0, C, 0, N],
  83.                 [b, 0, 0, p, 0, 0, P, 0, 0, B],
  84.                 [a, 0, 0, 0, 0, 0, 0, 0, 0, A],
  85.                 [k, 0, 0, p, 0, 0, P, 0, 0, K],
  86.                 [a, 0, 0, 0, 0, 0, 0, 0, 0, A],
  87.                 [b, 0, 0, p, 0, 0, P, 0, 0, B],
  88.                 [n, 0, c, 0, 0, 0, 0, C, 0, N],
  89.                 [r, 0, 0, p, 0, 0, P, 0, 0, R]
  90.             ]
  91.         )
  92.     )
  93.     MOVE = 1
  94.     CAPTURE = 2
  95.     DRAW = 3
  96.     CHECK = 4
  97.     INVALID = 5
  98.     CHECKMATE = 6
  99.     RESIGN = 7
  100.     NEWGAME = 8
  101.     # 以下为引擎专用
  102.     INFO = 9
  103.     POPHASH = 10
  104.     NOBESTMOVE = 11
复制代码
还有其他文件,这里就不一一列举了,

主步调:
  1. # coding=utf-8
  2. import sys
  3. import time
  4. from functools import partial
  5. import threading
  6. import numpy as np
  7. import keyboard
  8. from PySide6 import QtWidgets
  9. from PySide6 import QtCore
  10. from PySide6 import QtGui
  11. from board import BoardFrame
  12. from engine import Engine
  13. from engine import UCCIEngine
  14. from engine import Chess
  15. from engine import dirpath
  16. from engine import logger
  17. from engines import UCCI_ENGINES
  18. from situation import Situation
  19. import audio
  20. import engines
  21. import system
  22. from version import VERSION
  23. from dialogs.settings import SettingsDialog
  24. from dialogs.method import MethodDialog
  25. from toast import Toast
  26. from context import BaseContextMenu
  27. from context import BaseContextMenuMixin
  28. from arrange import ArrangeBoard
  29. from manual import Manual
  30. import qqchess
  31. class GameSignal(QtCore.QObject):
  32.     hint = QtCore.Signal(None)
  33.     undo = QtCore.Signal(None)
  34.     redo = QtCore.Signal(None)
  35.     reset = QtCore.Signal(None)
  36.     debug = QtCore.Signal(None)
  37.     load = QtCore.Signal(None)
  38.     save = QtCore.Signal(None)
  39.     paste = QtCore.Signal(None)
  40.     capture = QtCore.Signal(None)
  41.     connecting = QtCore.Signal(None)
  42.     move = QtCore.Signal(int)
  43.     draw = QtCore.Signal(None)
  44.     resign = QtCore.Signal(None)
  45.     checkmate = QtCore.Signal(None)
  46.     nobestmove = QtCore.Signal(None)
  47.     animate = QtCore.Signal(tuple, tuple)
  48.     settings = QtCore.Signal(None)
  49.     method = QtCore.Signal(None)
  50.     arrange = QtCore.Signal(None)
  51.     thinking = QtCore.Signal(bool)
  52.     reverse = QtCore.Signal(None)
  53. class GameContextMenu(BaseContextMenu):
  54.     items = [
  55.         ('提示', 'Ctrl+H', lambda self: self.signal.hint.emit(), True),
  56.         ('悔棋', 'Ctrl+Z', lambda self: self.signal.undo.emit(), True),
  57.         ('重走', 'Ctrl+Shift+Z', lambda self: self.signal.redo.emit(), True),
  58.         'separator',
  59.         ('重置', 'Ctrl+N', lambda self: self.signal.reset.emit(), False),
  60.         ('布局', 'Ctrl+A', lambda self: self.signal.arrange.emit(), True),
  61.         ('着法', 'Ctrl+M', lambda self: self.signal.method.emit(), False),
  62.         ('反转', 'Ctrl+I', lambda self: self.signal.reverse.emit(), False),
  63.         'separator',
  64.         ('粘贴', 'Ctrl+V', lambda self: self.signal.paste.emit(), True),
  65.         ('载入', 'Ctrl+O', lambda self: self.signal.load.emit(), True),
  66.         ('保存', 'Ctrl+S', lambda self: self.signal.save.emit(), True),
  67.         ('截屏', 'Ctrl+K', lambda self: self.signal.capture.emit(), True),
  68.         ('连线', 'Ctrl+L', lambda self: self.signal.connecting.emit(), True),
  69.         'separator',
  70.         ('设置', 'Ctrl+,', lambda self: self.signal.settings.emit(), False),
  71.     ]
  72.     if system.DEBUG:
  73.         items.extend(
  74.             [
  75.                 ('调试', 'Ctrl+D', lambda self: self.signal.debug.emit(), False),
  76.                 'separator',
  77.             ]
  78.         )
  79. class Game(BoardFrame, BaseContextMenuMixin):
  80.     def __init__(self, parent=None):
  81.         super().__init__(parent, board_class=ArrangeBoard)
  82.         self.setWindowTitle(f"中国象棋 v{VERSION}")
  83.         self.setupContextMenu()
  84.         audio.init()
  85.         self.image = None
  86.         self.engine = None
  87.         self.engines = {
  88.             Chess.RED: None,
  89.             Chess.BLACK: None,
  90.         }
  91.         self.engine_side = [Chess.BLACK]
  92.         self.human_side = [Chess.RED]
  93.         self.board.csize = 80
  94.         self.board.callback = self.board_callback
  95.         self.resize(self.board.csize * Chess.W, self.board.csize * Chess.H)
  96.         self.game_signal = GameSignal()
  97.         self.method = MethodDialog(self)
  98.         self.method.setWindowIcon(QtGui.QIcon(self.board.FAVICON))
  99.         self.settings = SettingsDialog(self)
  100.         self.settings.setWindowIcon(QtGui.QIcon(self.board.FAVICON))
  101.         self.game_menu = GameContextMenu(self, self.game_signal)
  102.         self.toast = Toast(self)
  103.         # 以下初始化信号
  104.         keyboard.add_hotkey(
  105.             'ctrl+alt+z', lambda: self.game_signal.capture.emit())
  106.         keyboard.add_hotkey(
  107.             'ctrl+alt+x', lambda: self.game_signal.hint.emit())
  108.         keyboard.add_hotkey(
  109.             'ctrl+alt+m', lambda: self.game_signal.method.emit())
  110.         keyboard.add_hotkey(
  111.             'ctrl+alt+l', lambda: self.game_signal.connecting.emit())
  112.         self.game_signal.reverse.connect(self.reverse)
  113.         self.game_signal.thinking.connect(self.set_thinking)
  114.         self.game_signal.settings.connect(self.settings.show)
  115.         self.game_signal.hint.connect(self.hint)
  116.         self.game_signal.undo.connect(self.undo)
  117.         self.game_signal.redo.connect(self.redo)
  118.         self.game_signal.reset.connect(self.reset)
  119.         self.game_signal.debug.connect(self.debug)
  120.         self.game_signal.load.connect(self.load)
  121.         self.game_signal.save.connect(self.save)
  122.         self.game_signal.paste.connect(self.paste)
  123.         self.game_signal.capture.connect(self.capture)
  124.         self.game_signal.move.connect(self.play)
  125.         self.game_signal.checkmate.connect(self.checkmateMessage)
  126.         self.game_signal.checkmate.connect(lambda: self.set_thinking(False))
  127.         self.game_signal.nobestmove.connect(self.nobestmove)
  128.         self.game_signal.draw.connect(lambda: self.toast.message('和棋!!!'))
  129.         self.game_signal.resign.connect(lambda: self.toast.message('认输了!!!'))
  130.         self.game_signal.animate.connect(self.animate)
  131.         self.settings.transprancy.valueChanged.connect(
  132.             lambda e: self.setWindowOpacity((100 - e) / 100)
  133.         )
  134.         self.settings.reverse.stateChanged.connect(
  135.             lambda e: self.board.setReverse(self.settings.reverse.isChecked())
  136.         )
  137.         self.settings.audio.stateChanged.connect(
  138.             lambda e: audio.play(Chess.MOVE) if e else None
  139.         )
  140.         self.settings.standard_method.stateChanged.connect(
  141.             lambda e: self.method.set_standard(e)
  142.         )
  143.         self.settings.ontop.clicked.connect(self.set_on_top)
  144.         self.settings.ok.clicked.connect(self.accepted)
  145.         self.settings.loads()
  146.         self.game_signal.arrange.connect(self.arrange)
  147.         self.board.signal.finish.connect(self.finish_arrange)
  148.         self.game_signal.method.connect(
  149.             lambda: self.method.setVisible(
  150.                 not self.method.isVisible()))
  151.         self.method.list.currentItemChanged.connect(self.method_changed)
  152.         self.game_signal.connecting.connect(self.connecting)
  153.         # self.qqboard = qqchess.Capturer(self)
  154.         # logger.info("set qqboard %s", self.settings.qqboard)
  155.         # self.qqboard.setGeometry(*self.settings.qqboard)
  156.         # self.qqboard.signal.capture.connect(self.capture_image)
  157.         self.reset()
  158.         self.accepted()
  159.         self.check_openfile()
  160.     def set_on_top(self):
  161.         logger.info("set on top")
  162.         # self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
  163.     def check_openfile(self):
  164.         # 直接打开棋谱文件
  165.         if len(sys.argv) > 1:
  166.             with open(sys.argv[1], encoding='utf8') as file:
  167.                 content = file.read()
  168.             self.pasre_content(content)
  169.     def show_context_menu(self, point):
  170.         if self.board.arranging:
  171.             return self.board.arrange_menu.exec_(self.mapToGlobal(point))
  172.         self.game_menu.exec(self.mapToGlobal(point))
  173.     def update_action_state(self):
  174.         if len(self.engine_side) == 2 and not self.engine.checkmate:
  175.             self.game_menu.setAllMenuEnabled(False)
  176.             self.game_menu.setAllShortcutEnabled(False)
  177.         elif self.thinking:
  178.             self.game_menu.setAllMenuEnabled(False)
  179.             self.game_menu.setAllShortcutEnabled(False)
  180.         else:
  181.             self.game_menu.setAllMenuEnabled(True)
  182.             self.game_menu.setAllShortcutEnabled(True)
  183.     def set_thinking(self, thinking):
  184.         self.thinking = thinking
  185.         logger.debug("set thinking %s", self.thinking)
  186.         if len(self.engine_side) == 2 and not self.engine.checkmate:
  187.             QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor)
  188.         elif self.thinking:
  189.             QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor)
  190.         else:
  191.             QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
  192.         self.update_action_state()
  193.     def arrange(self):
  194.         self.board.arranging = True
  195.         self.board.setBoard(self.board.board)
  196.         self.board.setCheck(None)
  197.         self.game_menu.setAllShortcutEnabled(False)
  198.     def finish_arrange(self, finished):
  199.         if not finished:
  200.             return
  201.         logger.debug('finish arrange')
  202.         self.engine.close()
  203.         self.engine = Engine()
  204.         self.engine.sit.board = self.board.board
  205.         self.engine.sit.turn = self.board.first_side
  206.         self.engine.sit.fen = self.engine.sit.format_current_fen()
  207.         self.try_engine_move()
  208.         self.game_menu.setAllShortcutEnabled(True)
  209.     def method_changed(self, item: QtWidgets.QListWidgetItem):
  210.         index = self.method.list.indexFromItem(item).row()
  211.         self.engine.set_index(index)
  212.         if self.board.animate.state() == QtCore.QAbstractAnimation.State.Running:
  213.             return
  214.         self.updateBoard()
  215.     def accepted(self):
  216.         logger.info("setting accepted....")
  217.         # 设置棋手或AI
  218.         self.engine_side = []
  219.         self.human_side = []
  220.         if self.settings.redside.currentIndex() == 0:
  221.             self.human_side.append(Chess.RED)
  222.         else:
  223.             self.engine_side.append(Chess.RED)
  224.         if self.settings.blackside.currentIndex() == 0:
  225.             self.engine_side.append(Chess.BLACK)
  226.         else:
  227.             self.human_side.append(Chess.BLACK)
  228.         idx = self.settings.red_engine.currentIndex()
  229.         engine = self.engines[Chess.RED]
  230.         if not isinstance(engine, UCCI_ENGINES[idx]):
  231.             self.init_engines(Chess.RED)
  232.         idx = self.settings.black_engine.currentIndex()
  233.         engine = self.engines[Chess.BLACK]
  234.         if not isinstance(engine, UCCI_ENGINES[idx]):
  235.             self.init_engines(Chess.BLACK)
  236.         if len(self.engine_side) == 2:
  237.             self.method.list.setEnabled(False)
  238.         else:
  239.             self.method.list.setEnabled(True)
  240.         self.update_action_state()
  241.         self.try_engine_move()
  242.     def try_engine_move(self):
  243.         if self.engine.sit.turn not in self.engine_side:
  244.             return
  245.         self.go()
  246.     def go(self):
  247.         if self.engine.checkmate:
  248.             self.game_signal.checkmate.emit()
  249.             logger.debug('engine is checkmated hint ignored...')
  250.             return
  251.         self.game_signal.thinking.emit(True)
  252.         engine = self.current_engine()
  253.         engine.position(self.engine.sit.format_fen())
  254.         params = self.settings.get_params(self.engine.sit.turn)
  255.         engine.go(**params)
  256.     def nobestmove(self):
  257.         if self.engine.sit.turn == Chess.RED:
  258.             side = '黑方'
  259.         else:
  260.             side = '红方'
  261.         logger.debug('nobestmove')
  262.         self.toast.message(f"{side}行棋违例!")
  263.     @QtCore.Slot(int)
  264.     def play(self, audio_type):
  265.         if not self.settings.audio.isChecked():
  266.             return
  267.         audio.play(audio_type)
  268.     def init_engines(self, turn=None):
  269.         turns = []
  270.         if turn == Chess.RED:
  271.             turns = [Chess.RED]
  272.         elif turn == Chess.BLACK:
  273.             turns = [Chess.BLACK]
  274.         else:
  275.             turns = [Chess.RED, Chess.BLACK]
  276.         for turn in turns:
  277.             old = self.engines[turn]
  278.             if old:
  279.                 old.close()
  280.             idx = self.settings.get_engine_box(turn).currentIndex()
  281.             new = UCCI_ENGINES[idx](callback=self.engine_callback)
  282.             new.start()
  283.             self.engines[turn] = new
  284.     def current_engine(self) -> Engine:
  285.         return self.engines[self.engine.sit.turn]
  286.     def reverse(self):
  287.         logger.debug("reverse")
  288.         check = self.settings.reverse.isChecked()
  289.         self.settings.reverse.setChecked(not check)
  290.         self.settings.save()
  291.     def reset(self):
  292.         self.init_engines()
  293.         self.engine = Engine()
  294.         self.connected = False
  295.         self.connect_inited = False
  296.         self.fpos = None
  297.         self.board.arranging = False
  298.         self.thinking = False
  299.         self.game_signal.move.emit(Chess.NEWGAME)
  300.         self.updateBoard()
  301.         self.board.setCheck(None)
  302.         self.try_engine_move()
  303.         self.method.refresh(self.engine)
  304.     @QtCore.Slot(None)
  305.     def undo(self):
  306.         for _ in range(2):
  307.             self.engine.undo()
  308.             if self.engine.sit.turn in self.human_side:
  309.                 break
  310.         self.updateBoard()
  311.     @QtCore.Slot(None)
  312.     def redo(self):
  313.         for _ in range(2):
  314.             self.engine.redo()
  315.             logger.debug('engine redo result %d', self.engine.sit.result)
  316.             self.game_signal.move.emit(self.engine.sit.result)
  317.             if self.engine.checkmate:
  318.                 self.game_signal.checkmate.emit()
  319.             if self.engine.sit.turn in self.human_side:
  320.                 break
  321.         self.updateBoard()
  322.     @QtCore.Slot(bool)
  323.     def hint(self):
  324.         if self.thinking:
  325.             logger.debug('engine is thinking hint ignored...')
  326.             return
  327.         self.go()
  328.     @QtCore.Slot(None)
  329.     def debug(self):
  330.         logger.debug("debug slot.....")
  331.         qqchess.show(self.image)
  332.         # logger.debug(self.engine.sit.format_fen())
  333.     @QtCore.Slot(None)
  334.     def save(self):
  335.         dialog = QtWidgets.QFileDialog(self)
  336.         dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
  337.         filename = dialog.getSaveFileName(
  338.             self, "保存中国象棋文件 Fen", ".", "文件 (*.fen)")[0]
  339.         if not filename:
  340.             return
  341.         fen = self.engine.sit.format_fen()
  342.         with open(filename, 'w', encoding='utf8') as file:
  343.             file.write('fen ')
  344.             file.write(fen)
  345.         logger.info("save file %s - fen %s", filename, fen)
  346.     def pasre_content(self, content: str):
  347.         if not content.startswith('fen '):
  348.             manual = Manual()
  349.             try:
  350.                 manual.callback = lambda fpos, tpos: self.board.setBoard(
  351.                     manual.sit.board,
  352.                     manual.sit.fpos,
  353.                     manual.sit.tpos
  354.                 )
  355.                 manual.parse(content)
  356.             except Exception as e:
  357.                 self.toast.message(str(e))
  358.                 return
  359.             fen = manual.sit.format_fen()
  360.         else:
  361.             fen = content[4:]
  362.         self.engine.index = 0
  363.         self.engine.stack = self.engine.stack[:1]
  364.         self.engine.sit = self.engine.stack[0]
  365.         self.updateBoard()
  366.         self.board.setCheck(None)
  367.         if self.engine.sit.parse_fen(fen, load=True):
  368.             moves = self.engine.sit.moves
  369.             self.engine.sit.moves = []
  370.             for fpos, tpos in moves:
  371.                 result = self.engine.move(fpos, tpos)
  372.                 if result == Chess.CHECKMATE:
  373.                     self.game_signal.checkmate.emit()
  374.                     break
  375.             self.updateBoard()
  376.         else:
  377.             self.toast.message("加载棋谱失败")
  378.     @QtCore.Slot(None)
  379.     def load(self):
  380.         dialog = QtWidgets.QFileDialog(self)
  381.         dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
  382.         filename = dialog.getOpenFileName(
  383.             self, "打开中国象棋文件 Fen", ".", "fen 文件 (*.fen);;txt 文件 (*.txt)")[0]
  384.         if not filename:
  385.             return
  386.         with open(filename, 'r', encoding='utf8') as file:
  387.             content = file.read()
  388.         self.pasre_content(content)
  389.     def paste(self):
  390.         content = QtWidgets.QApplication.clipboard().text()
  391.         logger.debug('Clipboard text %s', content)
  392.         if content:
  393.             self.pasre_content(content)
  394.             return
  395.         image = qqchess.ImageGrab.grabclipboard()
  396.         if isinstance(image, qqchess.Image.Image):
  397.             self.paste_image(image)
  398.     def capture(self):
  399.         logger.debug("capture...")
  400.         colors = {0: Chess.RMASK, 1: Chess.BMASK}
  401.         board = np.zeros((9, 10), dtype=np.int8)
  402.         while True:
  403.             pred = qqchess.classifier.get_board()
  404.             board0 = board
  405.             board = np.zeros((9, 10), dtype=np.int8)
  406.             for loc, idx in pred.items():
  407.                 board[loc] = (idx[0] + 1) | colors[idx[1]]
  408.             if np.all(board0 == board):
  409.                 break
  410.         # 验证数量
  411.         C = Chess
  412.         wheres = np.argwhere((board == C.K) | (board == C.k))
  413.         if len(wheres) != 2:
  414.             logger.warning("bishop count error %s...", len(wheres))
  415.             return None
  416.         for where in wheres:
  417.             if where[0] < 3 or where[0] > 5:
  418.                 logger.warning("king location error %s...", where)
  419.                 return None
  420.             if 2 < where[1] < 7:
  421.                 logger.warning("king location error %s...", where)
  422.                 return None
  423.         counts = {
  424.             C.P: 5,
  425.             C.R: 2,
  426.             C.N: 2,
  427.             C.B: 2,
  428.             C.A: 2,
  429.             C.C: 2,
  430.             C.p: 5,
  431.             C.r: 2,
  432.             C.n: 2,
  433.             C.b: 2,
  434.             C.a: 2,
  435.             C.c: 2,
  436.         }
  437.         for key, count in counts.items():
  438.             wheres = np.argwhere(board == key)
  439.             if len(wheres) > count:
  440.                 logger.warning("chess count error %s > %s...", len(wheres), count)
  441.                 return None
  442.         wheres = np.argwhere((board == C.B) | (board == C.B))
  443.         for where in wheres:
  444.             if tuple(where) not in {
  445.                 (2, 0), (6, 0),
  446.                 (0, 2), (4, 2), (8, 2),
  447.                 (2, 4), (6, 4),
  448.                 (2, 5), (6, 5),
  449.                 (0, 7), (4, 7), (8, 7),
  450.                 (2, 9), (6, 9),
  451.             }:
  452.                 logger.warning("bishop location error %s...", where)
  453.                 return None
  454.         wheres = np.argwhere((board == C.A) | (board == C.a))
  455.         for where in wheres:
  456.             if tuple(where) not in {
  457.                 (3, 0), (5, 0),
  458.                 (4, 1),
  459.                 (3, 2), (5, 2),
  460.                 (3, 7), (5, 7),
  461.                 (4, 8),
  462.                 (3, 9), (5, 9),
  463.             }:
  464.                 logger.warning("advisor location error %s...", where)
  465.                 return None
  466.         wheres = np.argwhere(board == Chess.K)
  467.         turn = Chess.RED
  468.         self.settings.reverse.setChecked(False)
  469.         if wheres[0][1] < 3:
  470.             logger.debug("reversed")
  471.             board = board[::-1, ::-1]
  472.             self.settings.reverse.setChecked(True)
  473.             turn = Chess.BLACK
  474.         if turn == Chess.RED and self.settings.redside.currentIndex() != 1:
  475.             self.settings.redside.setCurrentIndex(1)
  476.             self.settings.blackside.setCurrentIndex(1)
  477.             self.accepted()
  478.         if turn == Chess.BLACK and self.settings.redside.currentIndex() != 0:
  479.             self.settings.redside.setCurrentIndex(0)
  480.             self.settings.blackside.setCurrentIndex(0)
  481.             self.accepted()
  482.         wheres = np.argwhere((board - self.engine.sit.board) != 0)
  483.         # logger.debug("wheres %s", wheres)
  484.         if len(wheres) == 0:
  485.             return
  486.         if len(wheres) == 2:
  487.             pos1 = tuple(wheres[0])
  488.             pos2 = tuple(wheres[1])
  489.             if board[pos1] != 0 and board[pos2] != 0:
  490.                 return
  491.             assert (board[pos1] == 0 or board[pos2] == 0)
  492.             assert (board[pos1] != 0 or board[pos2] != 0)
  493.             if board[pos1] == 0:
  494.                 fpos = pos1
  495.                 tpos = pos2
  496.             else:
  497.                 fpos = pos2
  498.                 tpos = pos1
  499.             if Chess.color(board[tpos]) != turn:
  500.                 self.move(fpos, tpos)
  501.         elif not self.connect_inited:
  502.             logger.info("reset engine situation")
  503.             # self.engine.close()
  504.             # self.engine = Engine()
  505.             self.engine.index = 0
  506.             self.engine.sit = Situation(board, turn=turn)
  507.             self.engine.stack = [self.engine.sit]
  508.             self.fpos = None
  509.             self.updateBoard()
  510.             self.try_engine_move()
  511.             self.connect_inited = True
  512.     @QtCore.Slot(tuple, tuple)
  513.     def animate(self, fpos, tpos):
  514.         self.board.move(
  515.             self.engine.sit.board, fpos, tpos,
  516.             self.updateBoard,
  517.             self.settings.ui.animate.isChecked(),
  518.         )
  519.     def move(self, fpos, tpos):
  520.         self.game_signal.thinking.emit(False)
  521.         if self.engine.checkmate:
  522.             self.game_signal.checkmate.emit()
  523.             return
  524.         result = self.engine.move(fpos, tpos)
  525.         logger.debug('move result %s', result)
  526.         if not result:
  527.             return
  528.         if result != Chess.INVALID:
  529.             self.game_signal.animate.emit(fpos, tpos)
  530.         else:
  531.             return
  532.         if self.engine.sit.check:
  533.             self.board.setCheck(self.engine.sit.check)
  534.             logger.debug('check ... %s', self.engine.sit.check)
  535.         else:
  536.             self.board.setCheck(None)
  537.         self.game_signal.move.emit(result)
  538.         if result == Chess.CHECKMATE:
  539.             logger.debug("emit checkmate")
  540.             self.game_signal.checkmate.emit()
  541.             return
  542.         self.try_engine_move()
  543.     @QtCore.Slot(int)
  544.     def change_difficulty(self, diff):
  545.         self.depth_computer = diff
  546.     def updateBoard(self):
  547.         if hasattr(self, 'method'):
  548.             self.method.signal.refresh.emit()
  549.         self.board.setBoard(
  550.             self.engine.sit.board,
  551.             self.engine.sit.fpos,
  552.             self.engine.sit.tpos
  553.         )
  554.     @QtCore.Slot(None)
  555.     def checkmateMessage(self):
  556.         if not self.engine.checkmate:
  557.             return
  558.         self.connected = False
  559.         self.connect_inited = False
  560.         if self.engine.sit.turn == Chess.RED:
  561.             self.toast.message("黑方胜!!!")
  562.             # QtWidgets.QMessageBox(self).warning(self, '信息', '')
  563.         else:
  564.             self.toast.message("红方胜!!!")
  565.             # QtWidgets.QMessageBox(self).information(self, '信息', '红方胜!!!')
  566.     def engine_callback(self, type, data):
  567.         if type == Chess.MOVE:
  568.             time.sleep(self.settings.delay.value() / 1000)
  569.             self.move(data[0], data[1])
  570.         elif type == Chess.INFO:
  571.             logger.debug(data)
  572.         elif type == Chess.POPHASH:
  573.             logger.debug(data)
  574.         elif type == Chess.DRAW:
  575.             self.game_signal.draw.emit()
  576.         elif type == Chess.RESIGN:
  577.             self.game_signal.resign.emit()
  578.         elif type == Chess.CHECKMATE:
  579.             pass
  580.             # self.game_signal.checkmate.emit()
  581.         elif type == Chess.NOBESTMOVE:
  582.             self.game_signal.nobestmove.emit()
  583.     def board_callback(self, pos):
  584.         if self.engine.sit.where_turn(pos) == self.engine.sit.turn:
  585.             self.fpos = pos
  586.             self.board.setBoard(self.engine.sit.board, self.fpos)
  587.             return
  588.         if not self.fpos:
  589.             return
  590.         self.move(self.fpos, pos)
  591.     def closeEvent(self, event):
  592.         self.engine.close()
  593.         return super().closeEvent(event)
  594.     def connecting(self):
  595.         self.connected = not self.connected
  596.         if not self.connected:
  597.             if hasattr(self, 'connect_task'):
  598.                 self.connect_task.join()
  599.             return
  600.         def task():
  601.             while self.connected:
  602.                 self.game_signal.capture.emit()
  603.                 logger.debug('connecting... task')
  604.                 time.sleep(2)
  605.         self.connect_task = threading.Thread(target=task, daemon=True)
  606.         self.connect_task.start()
  607. def main():
  608.     app = QtWidgets.QApplication(sys.argv)
  609.     # import qt_material as material
  610.     # extra = {
  611.     #     'font_family': "dengxian SumHei"
  612.     # }
  613.     # material.apply_stylesheet(app, theme='light_blue.xml', invert_secondary=True, extra=extra)
  614.     window = Game()
  615.     window.show()
  616.     sys.exit(app.exec())
  617. if __name__ == '__main__':
  618.     main()
复制代码
运行后:

必要游戏素材,和完备代码,wx扫描下方二维码获取,长期有效大概点击公众号获取。

本文源码转自 https://mp.weixin.qq.com/s/8TY8w6Pd13EI9fPdF8ncAw,如有侵权,请接洽删除。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表