笑看天下无敌手 发表于 4 天前

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

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

首先绘制一下棋盘,看看样子:
https://i-blog.csdnimg.cn/direct/e4d3232046684da7ae899a92337465ec.png
黑白经典款
绘制棋盘:

class Board(QLabel):

    '''
    棋盘坐标与屏幕坐标类似,左上角为 (0, 0),右下角为 (8, 9)
    '''

    BOARD = str(dirpath / u"images/board.png")
    MARK = str(dirpath / u"images/mark.png")
    CHECK = str(dirpath / u"images/check.png")
    FAVICON = str(dirpath / u"images/black_bishop.png")

    IMAGES = {
      Chess.R: str(dirpath / 'images/red_rook.png'),
      Chess.N: str(dirpath / 'images/red_knight.png'),
      Chess.B: str(dirpath / 'images/red_bishop.png'),
      Chess.A: str(dirpath / 'images/red_advisor.png'),
      Chess.K: str(dirpath / 'images/red_king.png'),
      Chess.C: str(dirpath / 'images/red_cannon.png'),
      Chess.P: str(dirpath / 'images/red_pawn.png'),
      Chess.r: str(dirpath / 'images/black_rook.png'),
      Chess.n: str(dirpath / 'images/black_knight.png'),
      Chess.b: str(dirpath / 'images/black_bishop.png'),
      Chess.a: str(dirpath / 'images/black_advisor.png'),
      Chess.k: str(dirpath / 'images/black_king.png'),
      Chess.c: str(dirpath / 'images/black_cannon.png'),
      Chess.p: str(dirpath / 'images/black_pawn.png'),
    }

    ANIMATION_DURATION = 280

    flags = QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint

    signal_class = BoardSignal

    def __init__(self, parent=None, callback=None):
      super().__init__(parent=parent)

      self.csize = 60
      self.board_image = QtGui.QPixmap(self.BOARD)

      if parent is None:
            self.setWindowIcon(QtGui.QIcon(self.FAVICON))
            self.setWindowTitle(u"Chinese Chess")
            # self.setWindowFlags(self.flags)

      # https://www.mfitzp.com/tutorials/packaging-pyqt5-pyside2-applications-windows-pyinstaller/

      app = QtWidgets.QApplication.instance()
      if app:
            app.setWindowIcon(QtGui.QIcon(self.FAVICON))

      if os.name == 'nt':
            logger.info("set model id")
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                f'StevenBaby.Chess.{VERSION}'
            )

      self.setObjectName(u"Board")
      self.setScaledContents(True)

      self.animate = QtCore.QPropertyAnimation(self, b'geometry', self)

      self.resize(self.csize * Chess.W, self.csize * Chess.H)

      for chess, path in self.IMAGES.items():
            self.IMAGES = QPixmap(path)

      mark = QPixmap(self.MARK)

      # 棋盘起始标记
      self.mark1 = QLabel(self)
      self.mark1.setPixmap(mark)
      self.mark1.setScaledContents(True)
      self.mark1.setVisible(False)

      # 棋盘落子标记
      self.mark2 = QLabel(self)
      self.mark2.setPixmap(mark)
      self.mark2.setScaledContents(True)
      self.mark2.setVisible(False)

      check = QPixmap(self.CHECK)
      # 将军棋子标记
      self.mark3 = QLabel(self)
      self.mark3.setPixmap(check)
      self.mark3.setScaledContents(True)
      self.mark3.setVisible(False)

      self.signal = self.signal_class()
      self.signal.refresh.connect(self.refresh)

      self.labels = mat(zeros((Chess.W, Chess.H,)), dtype=QtWidgets.QLabel)
      self.board = mat(zeros((Chess.W, Chess.H,)), dtype=int)

      self.fpos = None
      self.tpos = None
      self.check = None
      self.reverse = False

      self.update()

      self.callback = callback

    def setBoard(self, board, fpos=None, tpos=None):
      # 设置 棋盘 board,以及该步,的棋子从哪儿 fpos,到哪儿 tpos
      # 由于 该函数可能在多个线程中调用,所以下面触发 signal.refresh
      # QT 会自动将刷新棋盘的重任放到主线程去做
      # 如果直接在非主线程调用 refresh 函数,程序可能莫名其妙的死掉。

      self.board = board
      self.fpos = fpos
      self.tpos = tpos
      self.signal.refresh.emit()
这个主要是绘制棋盘和棋盘中棋子移动产生的回调。
绘制棋子:

# coding=utf-8
import numpy as np

from logger import logger


class Chess(object):

    NONE = 0

    RMASK = 16# 第五位表示红方 比如红車 0b01_0_001
    # 判断棋子是不是黑方只需要 按位与 0b0100000 就可以了

    BMASK = 32# 第六位表示黑方 比如黑炮 0b10_0_110
    # 判断棋子是不是黑方只需要 按位与 0b100000 就可以了

    RED = RMASK
    BLACK = BMASK
    TMASK = 0b110000# 棋子颜色掩码,可能的取值为 16 和 32
    CMASK = 0b111# 棋子掩码

    # 棋子使用 1-7 来表示,二进制就是 0b001 - 0b111

    # INDEX = {
    #   '帥': (0, 0), '仕': (1, 0), '相': (2, 0), '傌': (3, 0), '俥': (4, 0), '炮': (5, 0), '兵': (6, 0),
    #   '將': (0, 1), '士': (1, 1), '象': (2, 1), '馬': (3, 1), '車': (4, 1), '砲': (5, 1), '卒': (6, 1),
    # }

    KING = 1
    ADVISOR = 2
    BISHOP = 3
    KNIGHT = 4
    ROOK = 5
    CANNON = 6
    PAWN = 7

    P = PAWN | RMASK
    R = ROOK | RMASK
    N = KNIGHT | RMASK
    B = BISHOP | RMASK
    A = ADVISOR | RMASK
    C = CANNON | RMASK
    K = KING | RMASK

    p = PAWN | BMASK
    r = ROOK | BMASK
    n = KNIGHT | BMASK
    b = BISHOP | BMASK
    a = ADVISOR | BMASK
    c = CANNON | BMASK
    k = KING | BMASK

    WIDTH = 9
    HEIGHT = 10
    W = WIDTH
    H = HEIGHT

    NAMES = {
      0: ' ',
      P: 'P',
      R: 'R',
      N: 'N',
      B: 'B',
      A: 'A',
      C: 'C',
      K: 'K',
      p: 'p',
      r: 'r',
      n: 'n',
      b: 'b',
      a: 'a',
      c: 'c',
      k: 'k',
    }

    CHESSES = {P, R, N, B, A, C, K, p, r, n, b, a, c, k, }

    @staticmethod
    def invert(chess):
      # 转换棋子颜色
      return (chess & 7) | ((~chess) & 0b110000)

    @staticmethod
    def is_red(chess):
      return chess & Chess.RED

    @staticmethod
    def is_black(chess):
      return chess & Chess.BLACK

    @staticmethod
    def chess(c):
      return c & Chess.CMASK

    @staticmethod
    def color(c):
      return c & Chess.TMASK

    ORIGIN = np.mat(
      np.array(
            [
                ,
                ,
                ,
                ,
                ,
                ,
                ,
                ,
               
            ]
      )
    )

    MOVE = 1
    CAPTURE = 2
    DRAW = 3
    CHECK = 4
    INVALID = 5
    CHECKMATE = 6
    RESIGN = 7
    NEWGAME = 8

    # 以下为引擎专用
    INFO = 9
    POPHASH = 10
    NOBESTMOVE = 11
还有其他文件,这里就不一一列举了,
https://i-blog.csdnimg.cn/direct/d843fdc0bf03476194989fc4adcef8f1.png
主步调:

# coding=utf-8

import sys
import time
from functools import partial
import threading

import numpy as np

import keyboard

from PySide6 import QtWidgets
from PySide6 import QtCore
from PySide6 import QtGui

from board import BoardFrame

from engine import Engine
from engine import UCCIEngine
from engine import Chess
from engine import dirpath
from engine import logger
from engines import UCCI_ENGINES
from situation import Situation

import audio
import engines
import system
from version import VERSION

from dialogs.settings import SettingsDialog
from dialogs.method import MethodDialog
from toast import Toast

from context import BaseContextMenu
from context import BaseContextMenuMixin

from arrange import ArrangeBoard
from manual import Manual
import qqchess


class GameSignal(QtCore.QObject):

    hint = QtCore.Signal(None)
    undo = QtCore.Signal(None)
    redo = QtCore.Signal(None)
    reset = QtCore.Signal(None)
    debug = QtCore.Signal(None)

    load = QtCore.Signal(None)
    save = QtCore.Signal(None)
    paste = QtCore.Signal(None)
    capture = QtCore.Signal(None)
    connecting = QtCore.Signal(None)

    move = QtCore.Signal(int)
    draw = QtCore.Signal(None)
    resign = QtCore.Signal(None)
    checkmate = QtCore.Signal(None)
    nobestmove = QtCore.Signal(None)

    animate = QtCore.Signal(tuple, tuple)
    settings = QtCore.Signal(None)
    method = QtCore.Signal(None)
    arrange = QtCore.Signal(None)
    thinking = QtCore.Signal(bool)
    reverse = QtCore.Signal(None)


class GameContextMenu(BaseContextMenu):

    items = [
      ('提示', 'Ctrl+H', lambda self: self.signal.hint.emit(), True),
      ('悔棋', 'Ctrl+Z', lambda self: self.signal.undo.emit(), True),
      ('重走', 'Ctrl+Shift+Z', lambda self: self.signal.redo.emit(), True),
      'separator',
      ('重置', 'Ctrl+N', lambda self: self.signal.reset.emit(), False),
      ('布局', 'Ctrl+A', lambda self: self.signal.arrange.emit(), True),
      ('着法', 'Ctrl+M', lambda self: self.signal.method.emit(), False),
      ('反转', 'Ctrl+I', lambda self: self.signal.reverse.emit(), False),
      'separator',
      ('粘贴', 'Ctrl+V', lambda self: self.signal.paste.emit(), True),
      ('载入', 'Ctrl+O', lambda self: self.signal.load.emit(), True),
      ('保存', 'Ctrl+S', lambda self: self.signal.save.emit(), True),
      ('截屏', 'Ctrl+K', lambda self: self.signal.capture.emit(), True),
      ('连线', 'Ctrl+L', lambda self: self.signal.connecting.emit(), True),
      'separator',
      ('设置', 'Ctrl+,', lambda self: self.signal.settings.emit(), False),
    ]

    if system.DEBUG:
      items.extend(
            [
                ('调试', 'Ctrl+D', lambda self: self.signal.debug.emit(), False),
                'separator',
            ]
      )


class Game(BoardFrame, BaseContextMenuMixin):

    def __init__(self, parent=None):
      super().__init__(parent, board_class=ArrangeBoard)
      self.setWindowTitle(f"中国象棋 v{VERSION}")
      self.setupContextMenu()

      audio.init()

      self.image = None

      self.engine = None
      self.engines = {
            Chess.RED: None,
            Chess.BLACK: None,
      }

      self.engine_side =
      self.human_side =

      self.board.csize = 80
      self.board.callback = self.board_callback
      self.resize(self.board.csize * Chess.W, self.board.csize * Chess.H)

      self.game_signal = GameSignal()

      self.method = MethodDialog(self)
      self.method.setWindowIcon(QtGui.QIcon(self.board.FAVICON))

      self.settings = SettingsDialog(self)
      self.settings.setWindowIcon(QtGui.QIcon(self.board.FAVICON))

      self.game_menu = GameContextMenu(self, self.game_signal)

      self.toast = Toast(self)

      # 以下初始化信号

      keyboard.add_hotkey(
            'ctrl+alt+z', lambda: self.game_signal.capture.emit())
      keyboard.add_hotkey(
            'ctrl+alt+x', lambda: self.game_signal.hint.emit())
      keyboard.add_hotkey(
            'ctrl+alt+m', lambda: self.game_signal.method.emit())
      keyboard.add_hotkey(
            'ctrl+alt+l', lambda: self.game_signal.connecting.emit())

      self.game_signal.reverse.connect(self.reverse)
      self.game_signal.thinking.connect(self.set_thinking)
      self.game_signal.settings.connect(self.settings.show)
      self.game_signal.hint.connect(self.hint)
      self.game_signal.undo.connect(self.undo)
      self.game_signal.redo.connect(self.redo)
      self.game_signal.reset.connect(self.reset)
      self.game_signal.debug.connect(self.debug)

      self.game_signal.load.connect(self.load)
      self.game_signal.save.connect(self.save)
      self.game_signal.paste.connect(self.paste)
      self.game_signal.capture.connect(self.capture)

      self.game_signal.move.connect(self.play)

      self.game_signal.checkmate.connect(self.checkmateMessage)
      self.game_signal.checkmate.connect(lambda: self.set_thinking(False))

      self.game_signal.nobestmove.connect(self.nobestmove)

      self.game_signal.draw.connect(lambda: self.toast.message('和棋!!!'))
      self.game_signal.resign.connect(lambda: self.toast.message('认输了!!!'))

      self.game_signal.animate.connect(self.animate)

      self.settings.transprancy.valueChanged.connect(
            lambda e: self.setWindowOpacity((100 - e) / 100)
      )

      self.settings.reverse.stateChanged.connect(
            lambda e: self.board.setReverse(self.settings.reverse.isChecked())
      )

      self.settings.audio.stateChanged.connect(
            lambda e: audio.play(Chess.MOVE) if e else None
      )

      self.settings.standard_method.stateChanged.connect(
            lambda e: self.method.set_standard(e)
      )

      self.settings.ontop.clicked.connect(self.set_on_top)

      self.settings.ok.clicked.connect(self.accepted)
      self.settings.loads()

      self.game_signal.arrange.connect(self.arrange)
      self.board.signal.finish.connect(self.finish_arrange)

      self.game_signal.method.connect(
            lambda: self.method.setVisible(
                not self.method.isVisible()))
      self.method.list.currentItemChanged.connect(self.method_changed)

      self.game_signal.connecting.connect(self.connecting)
      # self.qqboard = qqchess.Capturer(self)
      # logger.info("set qqboard %s", self.settings.qqboard)
      # self.qqboard.setGeometry(*self.settings.qqboard)
      # self.qqboard.signal.capture.connect(self.capture_image)

      self.reset()
      self.accepted()
      self.check_openfile()

    def set_on_top(self):
      logger.info("set on top")
      # self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)

    def check_openfile(self):
      # 直接打开棋谱文件
      if len(sys.argv) > 1:
            with open(sys.argv, encoding='utf8') as file:
                content = file.read()
            self.pasre_content(content)

    def show_context_menu(self, point):
      if self.board.arranging:
            return self.board.arrange_menu.exec_(self.mapToGlobal(point))
      self.game_menu.exec(self.mapToGlobal(point))

    def update_action_state(self):
      if len(self.engine_side) == 2 and not self.engine.checkmate:
            self.game_menu.setAllMenuEnabled(False)
            self.game_menu.setAllShortcutEnabled(False)
      elif self.thinking:
            self.game_menu.setAllMenuEnabled(False)
            self.game_menu.setAllShortcutEnabled(False)
      else:
            self.game_menu.setAllMenuEnabled(True)
            self.game_menu.setAllShortcutEnabled(True)

    def set_thinking(self, thinking):
      self.thinking = thinking
      logger.debug("set thinking %s", self.thinking)
      if len(self.engine_side) == 2 and not self.engine.checkmate:
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor)
      elif self.thinking:
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor)
      else:
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
      self.update_action_state()

    def arrange(self):
      self.board.arranging = True
      self.board.setBoard(self.board.board)
      self.board.setCheck(None)
      self.game_menu.setAllShortcutEnabled(False)

    def finish_arrange(self, finished):
      if not finished:
            return
      logger.debug('finish arrange')
      self.engine.close()
      self.engine = Engine()

      self.engine.sit.board = self.board.board
      self.engine.sit.turn = self.board.first_side
      self.engine.sit.fen = self.engine.sit.format_current_fen()
      self.try_engine_move()
      self.game_menu.setAllShortcutEnabled(True)

    def method_changed(self, item: QtWidgets.QListWidgetItem):
      index = self.method.list.indexFromItem(item).row()
      self.engine.set_index(index)
      if self.board.animate.state() == QtCore.QAbstractAnimation.State.Running:
            return
      self.updateBoard()

    def accepted(self):
      logger.info("setting accepted....")

      # 设置棋手或AI
      self.engine_side = []
      self.human_side = []

      if self.settings.redside.currentIndex() == 0:
            self.human_side.append(Chess.RED)
      else:
            self.engine_side.append(Chess.RED)

      if self.settings.blackside.currentIndex() == 0:
            self.engine_side.append(Chess.BLACK)
      else:
            self.human_side.append(Chess.BLACK)

      idx = self.settings.red_engine.currentIndex()
      engine = self.engines
      if not isinstance(engine, UCCI_ENGINES):
            self.init_engines(Chess.RED)

      idx = self.settings.black_engine.currentIndex()
      engine = self.engines
      if not isinstance(engine, UCCI_ENGINES):
            self.init_engines(Chess.BLACK)

      if len(self.engine_side) == 2:
            self.method.list.setEnabled(False)
      else:
            self.method.list.setEnabled(True)
      self.update_action_state()

      self.try_engine_move()

    def try_engine_move(self):
      if self.engine.sit.turn not in self.engine_side:
            return
      self.go()

    def go(self):
      if self.engine.checkmate:
            self.game_signal.checkmate.emit()
            logger.debug('engine is checkmated hint ignored...')
            return
      self.game_signal.thinking.emit(True)
      engine = self.current_engine()
      engine.position(self.engine.sit.format_fen())

      params = self.settings.get_params(self.engine.sit.turn)
      engine.go(**params)

    def nobestmove(self):
      if self.engine.sit.turn == Chess.RED:
            side = '黑方'
      else:
            side = '红方'
      logger.debug('nobestmove')
      self.toast.message(f"{side}行棋违例!")

    @QtCore.Slot(int)
    def play(self, audio_type):
      if not self.settings.audio.isChecked():
            return
      audio.play(audio_type)

    def init_engines(self, turn=None):
      turns = []
      if turn == Chess.RED:
            turns =
      elif turn == Chess.BLACK:
            turns =
      else:
            turns =
      for turn in turns:
            old = self.engines
            if old:
                old.close()
            idx = self.settings.get_engine_box(turn).currentIndex()
            new = UCCI_ENGINES(callback=self.engine_callback)
            new.start()
            self.engines = new

    def current_engine(self) -> Engine:
      return self.engines

    def reverse(self):
      logger.debug("reverse")
      check = self.settings.reverse.isChecked()
      self.settings.reverse.setChecked(not check)
      self.settings.save()

    def reset(self):
      self.init_engines()

      self.engine = Engine()

      self.connected = False
      self.connect_inited = False

      self.fpos = None
      self.board.arranging = False
      self.thinking = False
      self.game_signal.move.emit(Chess.NEWGAME)

      self.updateBoard()
      self.board.setCheck(None)
      self.try_engine_move()

      self.method.refresh(self.engine)

    @QtCore.Slot(None)
    def undo(self):
      for _ in range(2):
            self.engine.undo()
            if self.engine.sit.turn in self.human_side:
                break

      self.updateBoard()

    @QtCore.Slot(None)
    def redo(self):
      for _ in range(2):
            self.engine.redo()
            logger.debug('engine redo result %d', self.engine.sit.result)
            self.game_signal.move.emit(self.engine.sit.result)
            if self.engine.checkmate:
                self.game_signal.checkmate.emit()
            if self.engine.sit.turn in self.human_side:
                break

      self.updateBoard()

    @QtCore.Slot(bool)
    def hint(self):
      if self.thinking:
            logger.debug('engine is thinking hint ignored...')
            return
      self.go()

    @QtCore.Slot(None)
    def debug(self):
      logger.debug("debug slot.....")
      qqchess.show(self.image)
      # logger.debug(self.engine.sit.format_fen())

    @QtCore.Slot(None)
    def save(self):
      dialog = QtWidgets.QFileDialog(self)
      dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
      filename = dialog.getSaveFileName(
            self, "保存中国象棋文件 Fen", ".", "文件 (*.fen)")
      if not filename:
            return
      fen = self.engine.sit.format_fen()
      with open(filename, 'w', encoding='utf8') as file:
            file.write('fen ')
            file.write(fen)
      logger.info("save file %s - fen %s", filename, fen)

    def pasre_content(self, content: str):
      if not content.startswith('fen '):
            manual = Manual()
            try:
                manual.callback = lambda fpos, tpos: self.board.setBoard(
                  manual.sit.board,
                  manual.sit.fpos,
                  manual.sit.tpos
                )
                manual.parse(content)
            except Exception as e:
                self.toast.message(str(e))
                return
            fen = manual.sit.format_fen()
      else:
            fen = content

      self.engine.index = 0
      self.engine.stack = self.engine.stack[:1]
      self.engine.sit = self.engine.stack
      self.updateBoard()
      self.board.setCheck(None)

      if self.engine.sit.parse_fen(fen, load=True):
            moves = self.engine.sit.moves
            self.engine.sit.moves = []
            for fpos, tpos in moves:
                result = self.engine.move(fpos, tpos)
                if result == Chess.CHECKMATE:
                  self.game_signal.checkmate.emit()
                  break
            self.updateBoard()
      else:
            self.toast.message("加载棋谱失败")

    @QtCore.Slot(None)
    def load(self):
      dialog = QtWidgets.QFileDialog(self)
      dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
      filename = dialog.getOpenFileName(
            self, "打开中国象棋文件 Fen", ".", "fen 文件 (*.fen);;txt 文件 (*.txt)")
      if not filename:
            return
      with open(filename, 'r', encoding='utf8') as file:
            content = file.read()
      self.pasre_content(content)

    def paste(self):
      content = QtWidgets.QApplication.clipboard().text()
      logger.debug('Clipboard text %s', content)
      if content:
            self.pasre_content(content)
            return

      image = qqchess.ImageGrab.grabclipboard()
      if isinstance(image, qqchess.Image.Image):
            self.paste_image(image)

    def capture(self):
      logger.debug("capture...")
      colors = {0: Chess.RMASK, 1: Chess.BMASK}
      board = np.zeros((9, 10), dtype=np.int8)
      while True:
            pred = qqchess.classifier.get_board()
            board0 = board
            board = np.zeros((9, 10), dtype=np.int8)
            for loc, idx in pred.items():
                board = (idx + 1) | colors]
            if np.all(board0 == board):
                break

      # 验证数量
      C = Chess
      wheres = np.argwhere((board == C.K) | (board == C.k))
      if len(wheres) != 2:
            logger.warning("bishop count error %s...", len(wheres))
            return None

      for where in wheres:
            if where < 3 or where > 5:
                logger.warning("king location error %s...", where)
                return None
            if 2 < where < 7:
                logger.warning("king location error %s...", where)
                return None

      counts = {
            C.P: 5,
            C.R: 2,
            C.N: 2,
            C.B: 2,
            C.A: 2,
            C.C: 2,
            C.p: 5,
            C.r: 2,
            C.n: 2,
            C.b: 2,
            C.a: 2,
            C.c: 2,
      }

      for key, count in counts.items():
            wheres = np.argwhere(board == key)
            if len(wheres) > count:
                logger.warning("chess count error %s > %s...", len(wheres), count)
                return None

      wheres = np.argwhere((board == C.B) | (board == C.B))
      for where in wheres:
            if tuple(where) not in {
                (2, 0), (6, 0),
                (0, 2), (4, 2), (8, 2),
                (2, 4), (6, 4),
                (2, 5), (6, 5),
                (0, 7), (4, 7), (8, 7),
                (2, 9), (6, 9),
            }:
                logger.warning("bishop location error %s...", where)
                return None

      wheres = np.argwhere((board == C.A) | (board == C.a))
      for where in wheres:
            if tuple(where) not in {
                (3, 0), (5, 0),
                (4, 1),
                (3, 2), (5, 2),
                (3, 7), (5, 7),
                (4, 8),
                (3, 9), (5, 9),
            }:
                logger.warning("advisor location error %s...", where)
                return None

      wheres = np.argwhere(board == Chess.K)

      turn = Chess.RED
      self.settings.reverse.setChecked(False)
      if wheres < 3:
            logger.debug("reversed")
            board = board[::-1, ::-1]
            self.settings.reverse.setChecked(True)
            turn = Chess.BLACK

      if turn == Chess.RED and self.settings.redside.currentIndex() != 1:
            self.settings.redside.setCurrentIndex(1)
            self.settings.blackside.setCurrentIndex(1)
            self.accepted()
      if turn == Chess.BLACK and self.settings.redside.currentIndex() != 0:
            self.settings.redside.setCurrentIndex(0)
            self.settings.blackside.setCurrentIndex(0)
            self.accepted()

      wheres = np.argwhere((board - self.engine.sit.board) != 0)
      # logger.debug("wheres %s", wheres)
      if len(wheres) == 0:
            return

      if len(wheres) == 2:
            pos1 = tuple(wheres)
            pos2 = tuple(wheres)
            if board != 0 and board != 0:
                return

            assert (board == 0 or board == 0)
            assert (board != 0 or board != 0)
            if board == 0:
                fpos = pos1
                tpos = pos2
            else:
                fpos = pos2
                tpos = pos1
            if Chess.color(board) != turn:
                self.move(fpos, tpos)
      elif not self.connect_inited:
            logger.info("reset engine situation")
            # self.engine.close()
            # self.engine = Engine()
            self.engine.index = 0
            self.engine.sit = Situation(board, turn=turn)
            self.engine.stack =
            self.fpos = None

            self.updateBoard()
            self.try_engine_move()
            self.connect_inited = True

    @QtCore.Slot(tuple, tuple)
    def animate(self, fpos, tpos):
      self.board.move(
            self.engine.sit.board, fpos, tpos,
            self.updateBoard,
            self.settings.ui.animate.isChecked(),
      )

    def move(self, fpos, tpos):
      self.game_signal.thinking.emit(False)
      if self.engine.checkmate:
            self.game_signal.checkmate.emit()
            return

      result = self.engine.move(fpos, tpos)
      logger.debug('move result %s', result)
      if not result:
            return

      if result != Chess.INVALID:
            self.game_signal.animate.emit(fpos, tpos)
      else:
            return

      if self.engine.sit.check:
            self.board.setCheck(self.engine.sit.check)
            logger.debug('check ... %s', self.engine.sit.check)
      else:
            self.board.setCheck(None)

      self.game_signal.move.emit(result)

      if result == Chess.CHECKMATE:
            logger.debug("emit checkmate")
            self.game_signal.checkmate.emit()
            return

      self.try_engine_move()

    @QtCore.Slot(int)
    def change_difficulty(self, diff):
      self.depth_computer = diff

    def updateBoard(self):
      if hasattr(self, 'method'):
            self.method.signal.refresh.emit()
      self.board.setBoard(
            self.engine.sit.board,
            self.engine.sit.fpos,
            self.engine.sit.tpos
      )

    @QtCore.Slot(None)
    def checkmateMessage(self):
      if not self.engine.checkmate:
            return
      self.connected = False
      self.connect_inited = False

      if self.engine.sit.turn == Chess.RED:
            self.toast.message("黑方胜!!!")
            # QtWidgets.QMessageBox(self).warning(self, '信息', '')
      else:
            self.toast.message("红方胜!!!")
            # QtWidgets.QMessageBox(self).information(self, '信息', '红方胜!!!')

    def engine_callback(self, type, data):
      if type == Chess.MOVE:
            time.sleep(self.settings.delay.value() / 1000)
            self.move(data, data)
      elif type == Chess.INFO:
            logger.debug(data)
      elif type == Chess.POPHASH:
            logger.debug(data)
      elif type == Chess.DRAW:
            self.game_signal.draw.emit()
      elif type == Chess.RESIGN:
            self.game_signal.resign.emit()
      elif type == Chess.CHECKMATE:
            pass
            # self.game_signal.checkmate.emit()
      elif type == Chess.NOBESTMOVE:
            self.game_signal.nobestmove.emit()

    def board_callback(self, pos):
      if self.engine.sit.where_turn(pos) == self.engine.sit.turn:
            self.fpos = pos
            self.board.setBoard(self.engine.sit.board, self.fpos)
            return

      if not self.fpos:
            return

      self.move(self.fpos, pos)

    def closeEvent(self, event):
      self.engine.close()
      return super().closeEvent(event)

    def connecting(self):
      self.connected = not self.connected
      if not self.connected:
            if hasattr(self, 'connect_task'):
                self.connect_task.join()
            return

      def task():
            while self.connected:
                self.game_signal.capture.emit()
                logger.debug('connecting... task')
                time.sleep(2)

      self.connect_task = threading.Thread(target=task, daemon=True)
      self.connect_task.start()


def main():
    app = QtWidgets.QApplication(sys.argv)

    # import qt_material as material
    # extra = {
    #   'font_family': "dengxian SumHei"
    # }
    # material.apply_stylesheet(app, theme='light_blue.xml', invert_secondary=True, extra=extra)

    window = Game()
    window.show()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

运行后:
https://i-blog.csdnimg.cn/direct/f8193746a342447285d5677140b8258d.png
必要游戏素材,和完备代码,wx扫描下方二维码获取,长期有效大概点击公众号获取。
https://i-blog.csdnimg.cn/direct/43f90830182848f39e1a91bcea53ea6f.png#pic_center
本文源码转自 https://mp.weixin.qq.com/s/8TY8w6Pd13EI9fPdF8ncAw,如有侵权,请接洽删除。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Python游戏开发之《人机大战象棋》-附完备源码-python教程