• 【Dison夏令营 Day 18】如何用 Python 中的 Pygame 制作国际象棋游戏


    对于 Python 中级程序员来说,国际象棋游戏是一个很酷的项目创意。在熟练使用类的同时,它也是制作图形用户界面应用程序的良好练习。在本教程中,您将学习到

    使用 pygame 的基础知识。
    学习如何使用 Python 类编码一个国际象棋游戏。

    安装和设置

    在开始编码之前,让我们先在终端中安装 pygame 模块:

    $ pip install pygame
    

    安装好 pygame 后,我们就可以开始设置环境了,按照以下顺序创建我们要使用的 py 文件和文件夹:

    > python-chess
        > data
            > classes
                > pieces
                    /* Bishop.py
                    /* King.py
                    /* Knight.py
                    /* Pawn.py
                    /* Queen.py
                    /* Rook.py
                /* Board.py
                /* Piece.py
                /* Square.py
            > imgs
        /* main.py
    

    将要用到的国际象棋图标图像移到 python/data/imgs/ 目录中。确保图像文件命名为[颜色的第一个字母]_[棋子名称].png,就像这样:
    在这里插入图片描述
    如果您没有国际象棋图标,可以在这里使用我的图标

    游戏编码

    现在我们完成了设置,可以开始编码了。我们的国际象棋游戏有两个主要代码部分:创建棋盘和创建棋子。棋盘主要侧重于方位和游戏规则,而棋子则侧重于所代表的棋子及其走法。

    制作棋盘

    让我们从制作 Square 类开始。Square类将在我们的游戏窗口中创建、着色、定位和绘制每块棋子:

    # /* Square.py
    import pygame
    
    # Tile creator
    class Square:
        def __init__(self, x, y, width, height):
            self.x = x
            self.y = y
            self.width = width
            self.height = height
            self.abs_x = x * width
            self.abs_y = y * height
            self.abs_pos = (self.abs_x, self.abs_y)
            self.pos = (x, y)
            self.color = 'light' if (x + y) % 2 == 0 else 'dark'
            self.draw_color = (220, 208, 194) if self.color == 'light' else (53, 53, 53)
            self.highlight_color = (100, 249, 83) if self.color == 'light' else (0, 228, 10)
            self.occupying_piece = None
            self.coord = self.get_coord()
            self.highlight = False
            self.rect = pygame.Rect(
                self.abs_x,
                self.abs_y,
                self.width,
                self.height
            )
    
        # get the formal notation of the tile
        def get_coord(self):
            columns = 'abcdefgh'
            return columns[self.x] + str(self.y + 1)
    
        def draw(self, display):
            # configures if tile should be light or dark or highlighted tile
            if self.highlight:
                pygame.draw.rect(display, self.highlight_color, self.rect)
            else:
                pygame.draw.rect(display, self.draw_color, self.rect)
            # adds the chess piece icons
            if self.occupying_piece != None:
                centering_rect = self.occupying_piece.img.get_rect()
                centering_rect.center = self.rect.center
                display.blit(self.occupying_piece.img, centering_rect.topleft)
    

    我们要做的第一件事是创建一个用于制作国际象棋Square的类。首先,我们要添加 __init__() 函数来获取正方形的宽、高、行 x 和列 y。

    有了这些基本信息,我们就可以使用它们来实现其他变量。如上图所示,我们有 self.xself.y,同时还有 self.abs_xself.abs_yself.abs_xself.abs_y 决定了国际象棋方块在窗口内的绘制位置,我们将它们编译到 self.abs_pos 中。

    self.color告诉我们,如果方片能被 2 整除,则应为浅色;如果不能被 2 整除,则应为深色;而 self.draw_color 则告诉我们浅色和深色的颜色配置。我们还有 self.highlight_color,用来突出显示被选中的棋子可能移动的位置。self.rect 配置了正方形或瓷砖的宽、高和__cpLocation(使用 self.abs_xself.abs_y)。

    get_coord()会根据实际棋盘上的 x 和 y 返回平铺的名称。字母表示行,数字表示列。如 “a1”,它是国际象棋棋盘中最左下方的棋子。

    draw()语句将执行我们所做的配置,在画布上绘制出指定颜色的棋子。第二个 if 语句告诉我们,如果该方格在这个位置上有棋子,则应访问其图标并将其放入瓦片中。

    现在我们有了一个制作正方形的类。让我们再创建一个类来处理瓦片和整个棋盘。

    # /* Board.py
    
    import pygame
    from data.classes.Square import Square
    from data.classes.pieces.Rook import Rook
    from data.classes.pieces.Bishop import Bishop
    from data.classes.pieces.Knight import Knight
    from data.classes.pieces.Queen import Queen
    from data.classes.pieces.King import King
    from data.classes.pieces.Pawn import Pawn
    
    # Game state checker
    class Board:
        def __init__(self, width, height):
            self.width = width
            self.height = height
            self.tile_width = width // 8
            self.tile_height = height // 8
            self.selected_piece = None
            self.turn = 'white'
            self.config = [
                ['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
                ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'],
                ['','','','','','','',''],
                ['','','','','','','',''],
                ['','','','','','','',''],
                ['','','','','','','',''],
                ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'],
                ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
            ]
            self.squares = self.generate_squares()
            self.setup_board()
    
        def generate_squares(self):
            output = []
            for y in range(8):
                for x in range(8):
                    output.append(
                        Square(x,  y, self.tile_width, self.tile_height)
                    )
            return output
    
        def get_square_from_pos(self, pos):
            for square in self.squares:
                if (square.x, square.y) == (pos[0], pos[1]):
                    return square
    
        def get_piece_from_pos(self, pos):
            return self.get_square_from_pos(pos).occupying_piece
    

    在制作整个国际象棋棋盘时,首先要知道游戏窗口的宽度和高度,这样我们才能将其分为 8 行 8 列,以确定我们的棋子的精确尺寸。

    self.config代表棋盘配置,它是一个二维列表,包含了棋子的默认位置。在 self.config 下方,我们配置了 self.squares,该值调用了 self.generate_squares(),用于制作国际象棋棋子并将它们全部放入列表中。

    现在我们来创建棋盘的其他部分,包括上面调用的 self.setup_board()

        def setup_board(self):
            for y, row in enumerate(self.config):
                for x, piece in enumerate(row):
                    if piece != '':
                        square = self.get_square_from_pos((x, y))
                        # looking inside contents, what piece does it have
                        if piece[1] == 'R':
                            square.occupying_piece = Rook(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
                        # as you notice above, we put `self` as argument, or means our class Board
                        elif piece[1] == 'N':
                            square.occupying_piece = Knight(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
                        elif piece[1] == 'B':
                            square.occupying_piece = Bishop(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
                        elif piece[1] == 'Q':
                            square.occupying_piece = Queen(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
                        elif piece[1] == 'K':
                            square.occupying_piece = King(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
                        elif piece[1] == 'P':
                            square.occupying_piece = Pawn(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
                            )
    

    setup_board() 会创建每个棋子,并通过 self.config 与整个棋盘的映射将它们放到各自的位置。如果 self.config 中棋子的当前值是空字符串或'',那么该棋子必须是空的,如果不是,它将通过 xy 的当前值访问各自的棋子位置。

    如果是 "N",那么它是骑士;如果是 "P",那么它是。如果是 "R",则是"B" 代表主教,以此类推。配置好字母后,我们将用棋子类的值覆盖当前的 square.occupying_piece,颜色取决于棋子字符串的第一个值。正如您在此处和其他语句中注意到的那样:

                        if piece[1] == 'R':
                            square.occupying_piece = Rook(
                                (x, y), 'white' if piece[0] == 'w' else 'black', self
    

    我们将 self 作为 Rook 类的参数。这意味着我们将当前的类 Board 作为参数。

    我们需要一个函数来检测游戏中的每次点击。因此,让我们在 Board 类中创建 handle_click() 函数:

        def handle_click(self, mx, my):
            x = mx // self.tile_width
            y = my // self.tile_height
            clicked_square = self.get_square_from_pos((x, y))
            if self.selected_piece is None:
                if clicked_square.occupying_piece is not None:
                    if clicked_square.occupying_piece.color == self.turn:
                        self.selected_piece = clicked_square.occupying_piece
            elif self.selected_piece.move(self, clicked_square):
                self.turn = 'white' if self.turn == 'black' else 'black'
            elif clicked_square.occupying_piece is not None:
                if clicked_square.occupying_piece.color == self.turn:
                    self.selected_piece = clicked_square.occupying_piece
    

    handle_click()函数接受游戏窗口内点击位置的 x(mx)和 y(my)坐标作为参数。该函数中的 xy 变量会计算出您点击的行和列,然后我们将其结果传递给 clicked_square,以获得正方形或平铺。

    现在,这个配置可以接收我们在游戏窗口中的每次点击。下面的 if/else 语句会处理我们的点击,如果我们正在移动或只是四处点击的话。

    一旦你点击了游戏窗口内的某处,所有操作都会生效,因此我们假设你正在使用一个白色棋子进行游戏,并且已经点击了棋子。如果我们还没有选择任何棋子,那么看起来就像是你点击的那张牌上有棋子,如果轮到你的颜色,如果是,它就是你的 self.select_piece。

    在其他类的帮助下,您的棋子可能走的棋步会在游戏中突出显示。选中棋子后,它会将 self.turn 转换成下一位棋手的棋子颜色。

    现在我们选中了棋子并选择了走法,它就会开始走棋。稍后我会在我们为棋子创建类时解释其他的移动。

    让我们为棋盘类添加另一个功能;我们将添加检查棋手是否处于将死或将死状态的函数。

        # check state checker
        def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
            output = False
            king_pos = None
            changing_piece = None
            old_square = None
            new_square = None
            new_square_old_piece = None
            if board_change is not None:
                for square in self.squares:
                    if square.pos == board_change[0]:
                        changing_piece = square.occupying_piece
                        old_square = square
                        old_square.occupying_piece = None
                for square in self.squares:
                    if square.pos == board_change[1]:
                        new_square = square
                        new_square_old_piece = new_square.occupying_piece
                        new_square.occupying_piece = changing_piece
            pieces = [
                i.occupying_piece for i in self.squares if i.occupying_piece is not None
            ]
            if changing_piece is not None:
                if changing_piece.notation == 'K':
                    king_pos = new_square.pos
            if king_pos == None:
                for piece in pieces:
                    if piece.notation == 'K' and piece.color == color:
                            king_pos = piece.pos
            for piece in pieces:
                if piece.color != color:
                    for square in piece.attacking_squares(self):
                        if square.pos == king_pos:
                            output = True
            if board_change is not None:
                old_square.occupying_piece = changing_piece
                new_square.occupying_piece = new_square_old_piece
            return output
    

    每走一步棋,只要board_change 不为空,就会调用 is_in_check() 函数。

    在第一次迭代中,它会定位旧棋子的位置,将当前棋子传入 changing_piece,并清空该棋子;而在第二次迭代中,它会捕捉新棋子的位置,将当前棋子传入 new_square_old_piece,并从 changing_piece 给它一个新棋子。

    一旦 changing_piece 不是空的,它就会通过获取 self.notation 来识别它是否是国王。如果是,它将覆盖 king_pos,并赋予它 new_square.pos 的值。

    self.notation 是棋子类中的一个变量,作为包含字母符号的标识。

    接下来,我们将尝试识别敌方棋子是如何对我方的国王进行检查的,我们将从 if piece.color != color 开始检查。

            for piece in pieces:
                if piece.color != color:
                    for square in piece.attacking_squares(self):
                        if square.pos == king_pos:
                            output = True
    

    通过上面的代码,我们可以遍历敌方棋子并检查它们的攻击方位(attacking_squares),从而获得棋子的所有可能走法。如果棋子在 attacking_squares 中的位置与 king_pos 的值相同,这意味着其中一名棋手已被检查,因此我们将输出设置为 True。输出结果会告诉我们国王是否受制,因此我们必须返回它。

    现在我们来制作 is_in_checkmate() 函数,用于识别是否有赢家:

        # checkmate state checker
        def is_in_checkmate(self, color):
            output = False
            for piece in [i.occupying_piece for i in self.squares]:
                if piece != None:
                    if piece.notation == 'K' and piece.color == color:
                        king = piece
            if king.get_valid_moves(self) == []:
                if self.is_in_check(color):
                    output = True
            return output
    

    一旦国王的颜色与我们传递的参数相同,它就会尝试查看是否还有剩余的棋步。如果没有,它就会检查棋手是否处于受制状态。如果是这样,那么它将返回输出值 True,这意味着我们传入的颜色的一方是将死。

    现在我们有了所有的棋盘配置;是时候为棋盘类添加最后一个函数了,那就是 draw() 函数:

        def draw(self, display):
            if self.selected_piece is not None:
                self.get_square_from_pos(self.selected_piece.pos).highlight = True
                for square in self.selected_piece.get_valid_moves(self):
                    square.highlight = True
            for square in self.squares:
                square.draw(display)
    

    制作棋子

    现在我们完成了棋盘类,让我们在 Piece.py 中制作另一个棋子类。

    首先,我们要添加一个函数来获取所有可用的棋步,并在下一位棋手被上一位棋手选中时添加一个校验器:

    # /* Piece.py
    
    import pygame
    
    class Piece:
        def __init__(self, pos, color, board):
            self.pos = pos
            self.x = pos[0]
            self.y = pos[1]
            self.color = color
            self.has_moved = False
    
        def get_moves(self, board):
            output = []
            for direction in self.get_possible_moves(board):
                for square in direction:
                    if square.occupying_piece is not None:
                        if square.occupying_piece.color == self.color:
                            break
                        else:
                            output.append(square)
                            break
                    else:
                        output.append(square)
            return output
    

    get_moves() 会获取当前玩家的所有可用棋步,包括攻击敌方棋子。如果敌方棋子在某棋子的移动范围内,该棋子可以吃掉它,其范围将通过 output.append(square) 限制在敌方棋子的位置上,然后断开,除非该棋子是骑士,可以 "L "形移动。

        def get_valid_moves(self, board):
            output = []
            for square in self.get_moves(board):
                if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
                    output.append(square)
            return output
    

    在我们当前的棋手继续下棋之前,get_valid_moves() 会首先检查上一个棋手下的棋是否与我们当前的棋手一致。如果没有,它将返回可用的棋步。

    为了让棋子正常工作,我们将添加一个 move() 函数来处理我们在棋盘上的每一步棋:

        def move(self, board, square, force=False):
            for i in board.squares:
                i.highlight = False
            if square in self.get_valid_moves(board) or force:
                prev_square = board.get_square_from_pos(self.pos)
                self.pos, self.x, self.y = square.pos, square.x, square.y
                prev_square.occupying_piece = None
                square.occupying_piece = self
                board.selected_piece = None
                self.has_moved = True
                # Pawn promotion
                if self.notation == ' ':
                    if self.y == 0 or self.y == 7:
                        from data.classes.pieces.Queen import Queen
                        square.occupying_piece = Queen(
                            (self.x, self.y),
                            self.color,
                            board
                        )
                # Move rook if king castles
                if self.notation == 'K':
                    if prev_square.x - self.x == 2:
                        rook = board.get_piece_from_pos((0, self.y))
                        rook.move(board, board.get_square_from_pos((3, self.y)), force=True)
                    elif prev_square.x - self.x == -2:
                        rook = board.get_piece_from_pos((7, self.y))
                        rook.move(board, board.get_square_from_pos((5, self.y)), force=True)
                return True
            else:
                board.selected_piece = None
                return False
    
        # True for all pieces except pawn
        def attacking_squares(self, board):
            return self.get_moves(board)
    

    它以棋盘和方格为参数。如果我们选择移动所选棋子的棋子在 self.get_valid_moves() 中,那么移动就是有效的。为了实现这一点,move() 函数将使用 board.get_square_from_pos(self.pos) 获得当前的方格,并将其保存在 prev_square 中,同时获得其位置 square.possquare.xsquare.y,并将其保存在 self.posself.xself.y 中,以备进一步使用。

    然后,函数将清空 prev_square,棋子(self - 当前棋子类)将被移动到 square.occupying_piece 中。

    国际象棋有一些很酷的特性,其中包括投子和提兵,这就是我们接下来要做的。

    如果我们刚刚移入的棋子的记号是 ' ',也就是卒,并且到达第 0 行(白棋)或第 7 行(黑棋),那么该卒将被另一个相同颜色的后取代。

    如果棋子的标记是 “K”,然后向左或向右移动 2 格,则表示棋手的移动是投子。

    为每个棋子创建一个类

    现在我们已经完成了方块类、棋盘类和棋子类,是时候为每种棋子类型创建不同的类了。每个棋子都将以主棋子类作为父类:

    # /* Pawn.py
    
    import pygame
    
    from data.classes.Piece import Piece
    
    class Pawn(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_pawn.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 35, board.tile_height - 35))
            self.notation = ' '
    
        def get_possible_moves(self, board):
            output = []
            moves = []
            # move forward
            if self.color == 'white':
                moves.append((0, -1))
                if not self.has_moved:
                    moves.append((0, -2))
            elif self.color == 'black':
                moves.append((0, 1))
                if not self.has_moved:
                    moves.append((0, 2))
            for move in moves:
                new_pos = (self.x, self.y + move[1])
                if new_pos[1] < 8 and new_pos[1] >= 0:
                    output.append(
                        board.get_square_from_pos(new_pos)
                    )
            return output
    
        def get_moves(self, board):
            output = []
            for square in self.get_possible_moves(board):
                if square.occupying_piece != None:
                    break
                else:
                    output.append(square)
            if self.color == 'white':
                if self.x + 1 < 8 and self.y - 1 >= 0:
                    square = board.get_square_from_pos(
                        (self.x + 1, self.y - 1)
                    )
                    if square.occupying_piece != None:
                        if square.occupying_piece.color != self.color:
                            output.append(square)
                if self.x - 1 >= 0 and self.y - 1 >= 0:
                    square = board.get_square_from_pos(
                        (self.x - 1, self.y - 1)
                    )
                    if square.occupying_piece != None:
                        if square.occupying_piece.color != self.color:
                            output.append(square)
            elif self.color == 'black':
                if self.x + 1 < 8 and self.y + 1 < 8:
                    square = board.get_square_from_pos(
                        (self.x + 1, self.y + 1)
                    )
                    if square.occupying_piece != None:
                        if square.occupying_piece.color != self.color:
                            output.append(square)
                if self.x - 1 >= 0 and self.y + 1 < 8:
                    square = board.get_square_from_pos(
                        (self.x - 1, self.y + 1)
                    )
                    if square.occupying_piece != None:
                        if square.occupying_piece.color != self.color:
                            output.append(square)
            return output
    
        def attacking_squares(self, board):
            moves = self.get_moves(board)
            # return the diagonal moves 
            return [i for i in moves if i.x != self.x]
    

    下面是棋子的代码,无论是黑棋还是白棋。正如您所注意到的,我们在 Pawn 类中使用了 get_moves()attacking_square() 函数,就像在 Piece 类中使用的函数一样,只是使用了不同的脚本。这是因为小卒棋子基本上每次只能从其队伍位置移动一步。小卒也有 3 种可能的移动方式:小卒只能从其起始位置最多移动 2 个棋子,每次可以向前移动 1 步,每次可以捕捉对角线上 1 步的棋子。

    正如我们所注意到的,我们还有另一个函数 get_possible_moves()。正如它的名字一样,它会根据棋盘的当前状态获取棋子的所有可能走法。

    现在让我们开始为其他棋子编写代码。

    Knight.py:

    # /* Kinght.py
    
    import pygame
    from data.classes.Piece import Piece
    
    class Knight(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_knight.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
            self.notation = 'N'
    
        def get_possible_moves(self, board):
            output = []
            moves = [
                (1, -2),
                (2, -1),
                (2, 1),
                (1, 2),
                (-1, 2),
                (-2, 1),
                (-2, -1),
                (-1, -2)
            ]
            for move in moves:
                new_pos = (self.x + move[0], self.y + move[1])
                if (
                    new_pos[0] < 8 and
                    new_pos[0] >= 0 and 
                    new_pos[1] < 8 and 
                    new_pos[1] >= 0
                ):
                    output.append([
                        board.get_square_from_pos(
                            new_pos
                        )
                    ])
            return output
    

    Bishop.py:

    # /* Bishop.py
    
    import pygame
    from data.classes.Piece import Piece
    
    class Bishop(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_bishop.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
            self.notation = 'B'
    
        def get_possible_moves(self, board):
            output = []
            moves_ne = []
            for i in range(1, 8):
                if self.x + i > 7 or self.y - i < 0:
                    break
                moves_ne.append(board.get_square_from_pos(
                    (self.x + i, self.y - i)
                ))
            output.append(moves_ne)
            moves_se = []
            for i in range(1, 8):
                if self.x + i > 7 or self.y + i > 7:
                    break
                moves_se.append(board.get_square_from_pos(
                    (self.x + i, self.y + i)
                ))
            output.append(moves_se)
            moves_sw = []
            for i in range(1, 8):
                if self.x - i < 0 or self.y + i > 7:
                    break
                moves_sw.append(board.get_square_from_pos(
                    (self.x - i, self.y + i)
                ))
            output.append(moves_sw)
            moves_nw = []
            for i in range(1, 8):
                if self.x - i < 0 or self.y - i < 0:
                    break
                moves_nw.append(board.get_square_from_pos(
                    (self.x - i, self.y - i)
                ))
            output.append(moves_nw)
            return output
    

    Rook.py:

    # /* Rook.py
    
    import pygame
    
    from data.classes.Piece import Piece
    
    class Rook(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_rook.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
            self.notation = 'R'
    
        def get_possible_moves(self, board):
            output = []
            moves_north = []
            for y in range(self.y)[::-1]:
                moves_north.append(board.get_square_from_pos(
                    (self.x, y)
                ))
            output.append(moves_north)
            moves_east = []
            for x in range(self.x + 1, 8):
                moves_east.append(board.get_square_from_pos(
                    (x, self.y)
                ))
            output.append(moves_east)
            moves_south = []
            for y in range(self.y + 1, 8):
                moves_south.append(board.get_square_from_pos(
                    (self.x, y)
                ))
            output.append(moves_south)
            moves_west = []
            for x in range(self.x)[::-1]:
                moves_west.append(board.get_square_from_pos(
                    (x, self.y)
                ))
            output.append(moves_west)
            return output
    

    Queen.py:

    # /* Queen.py
    
    import pygame
    from data.classes.Piece import Piece
    
    class Queen(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_queen.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
            self.notation = 'Q'
    
        def get_possible_moves(self, board):
            output = []
            moves_north = []
            for y in range(self.y)[::-1]:
                moves_north.append(board.get_square_from_pos(
                    (self.x, y)
                ))
            output.append(moves_north)
            moves_ne = []
            for i in range(1, 8):
                if self.x + i > 7 or self.y - i < 0:
                    break
                moves_ne.append(board.get_square_from_pos(
                    (self.x + i, self.y - i)
                ))
            output.append(moves_ne)
            moves_east = []
            for x in range(self.x + 1, 8):
                moves_east.append(board.get_square_from_pos(
                    (x, self.y)
                ))
            output.append(moves_east)
            moves_se = []
            for i in range(1, 8):
                if self.x + i > 7 or self.y + i > 7:
                    break
                moves_se.append(board.get_square_from_pos(
                    (self.x + i, self.y + i)
                ))
            output.append(moves_se)
            moves_south = []
            for y in range(self.y + 1, 8):
                moves_south.append(board.get_square_from_pos(
                    (self.x, y)
                ))
            output.append(moves_south)
            moves_sw = []
            for i in range(1, 8):
                if self.x - i < 0 or self.y + i > 7:
                    break
                moves_sw.append(board.get_square_from_pos(
                    (self.x - i, self.y + i)
                ))
            output.append(moves_sw)
            moves_west = []
            for x in range(self.x)[::-1]:
                moves_west.append(board.get_square_from_pos(
                    (x, self.y)
                ))
            output.append(moves_west)
            moves_nw = []
            for i in range(1, 8):
                if self.x - i < 0 or self.y - i < 0:
                    break
                moves_nw.append(board.get_square_from_pos(
                    (self.x - i, self.y - i)
                ))
            output.append(moves_nw)
            return output
    

    King.py:

    # /* King.py
    
    import pygame
    from data.classes.Piece import Piece
    
    class King(Piece):
        def __init__(self, pos, color, board):
            super().__init__(pos, color, board)
            img_path = 'data/imgs/' + color[0] + '_king.png'
            self.img = pygame.image.load(img_path)
            self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
            self.notation = 'K'
    
        def get_possible_moves(self, board):
            output = []
            moves = [
                (0,-1), # north
                (1, -1), # ne
                (1, 0), # east
                (1, 1), # se
                (0, 1), # south
                (-1, 1), # sw
                (-1, 0), # west
                (-1, -1), # nw
            ]
            for move in moves:
                new_pos = (self.x + move[0], self.y + move[1])
                if (
                    new_pos[0] < 8 and
                    new_pos[0] >= 0 and 
                    new_pos[1] < 8 and 
                    new_pos[1] >= 0
                ):
                    output.append([
                        board.get_square_from_pos(
                            new_pos
                        )
                    ])
            return output
    
        def can_castle(self, board):
            if not self.has_moved:
                if self.color == 'white':
                    queenside_rook = board.get_piece_from_pos((0, 7))
                    kingside_rook = board.get_piece_from_pos((7, 7))
                    if queenside_rook != None:
                        if not queenside_rook.has_moved:
                            if [
                                board.get_piece_from_pos((i, 7)) for i in range(1, 4)
                            ] == [None, None, None]:
                                return 'queenside'
                    if kingside_rook != None:
                        if not kingside_rook.has_moved:
                            if [
                                board.get_piece_from_pos((i, 7)) for i in range(5, 7)
                            ] == [None, None]:
                                return 'kingside'
                elif self.color == 'black':
                    queenside_rook = board.get_piece_from_pos((0, 0))
                    kingside_rook = board.get_piece_from_pos((7, 0))
                    if queenside_rook != None:
                        if not queenside_rook.has_moved:
                            if [
                                board.get_piece_from_pos((i, 0)) for i in range(1, 4)
                            ] == [None, None, None]:
                                return 'queenside'
                    if kingside_rook != None:
                        if not kingside_rook.has_moved:
                            if [
                                board.get_piece_from_pos((i, 0)) for i in range(5, 7)
                            ] == [None, None]:
                                return 'kingside'
    
        def get_valid_moves(self, board):
            output = []
            for square in self.get_moves(board):
                if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
                    output.append(square)
            if self.can_castle(board) == 'queenside':
                output.append(
                    board.get_square_from_pos((self.x - 2, self.y))
                )
            if self.can_castle(board) == 'kingside':
                output.append(
                    board.get_square_from_pos((self.x + 2, self.y))
                )
            return output
    

    让我们在 main.py 中添加运行整个游戏的代码,完成游戏:

    import pygame
    
    from data.classes.Board import Board
    
    pygame.init()
    
    WINDOW_SIZE = (600, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE)
    
    board = Board(WINDOW_SIZE[0], WINDOW_SIZE[1])
    
    def draw(display):
    	display.fill('white')
    	board.draw(display)
    	pygame.display.update()
    
    
    if __name__ == '__main__':
    	running = True
    	while running:
    		mx, my = pygame.mouse.get_pos()
    		for event in pygame.event.get():
    			# Quit the game if the user presses the close button
    			if event.type == pygame.QUIT:
    				running = False
    			elif event.type == pygame.MOUSEBUTTONDOWN: 
           			# If the mouse is clicked
    				if event.button == 1:
    					board.handle_click(mx, my)
    		if board.is_in_checkmate('black'): # If black is in checkmate
    			print('White wins!')
    			running = False
    		elif board.is_in_checkmate('white'): # If white is in checkmate
    			print('Black wins!')
    			running = False
    		# Draw the board
    		draw(screen)
    

    如上图所示,我们有 screenboard 变量,它们的参数非常相似,但又不尽相同。

    screen 用于在屏幕上渲染棋盘,这样我们就能看到棋盘上发生的事情。代码 pygame.display.set_mode(WINDOW_SIZE) 创建了游戏窗口。

    我们使用棋盘来制作和处理棋子、棋子位置以及棋格中的棋子。正如你所记得的,在 Board 类代码中,我们给了它两个参数:游戏窗口的长度和宽度。

    为了保持游戏运行,我们给了它一个 while 循环,只要 running 的值为 True,循环就会运行。

    只要鼠标位于游戏窗口内,mx, my = pygame.mouse.get_pos() 就会定位鼠标的当前位置。如果在这段代码下面添加 print(mx,my),你就会看到鼠标的当前位置,而且每次将鼠标悬停在窗口内时,它的值都会发生变化。

    event.type == pygame.MOUSEBUTTONDOWN 会捕捉你的每次点击。为了识别玩家是否在移动,每次捕捉到玩家点击时,我们从 pygame.mouse.get_pos() 中得到的鼠标当前位置都会发送到 Board.handle_click(),并在那里处理你的点击。

    好了,现在让我们试试这个游戏。如果运行正常,请在终端中移动到保存 Main.py 文件的目录,然后运行 Main.py。运行文件后,游戏将立即开始:

    在这里插入图片描述
    点击可以移动的棋子,就能看到可用的棋步:

    在这里插入图片描述

    在这里插入图片描述

    结论

    简单来说,请记住国际象棋游戏有两个主要部分:棋盘和棋子。

    棋盘负责每个棋子的名称、位置和游戏规则,而棋子类则负责每个棋子的移动和攻击。

    要制作棋盘,你应该有一个 Square 类来创建处理国际象棋的棋子,同时也要注意它所包含的棋子,还有一个叫做 Board 的类,它包含游戏规则。我们还需要为从 PawnKing 的每个棋子创建类。这就是用 Python 制作国际象棋游戏的方法,只需使用类和 pygame

    您可以在此处查看完整代码

  • 相关阅读:
    leetcode做题笔记150. 逆波兰表达式求值
    【JavaEE】JUC(java.util.concurrent)的常见类以及线程安全的集合类
    vue实现el-tree操作后默认展开该节点和同级节点拖拽并进行前后端排序
    Leetcode周赛368补题(3 / 3)
    C语言的break、continue、switch、goto语句
    经典查找算法
    第三方软件测试机构提供哪些测试服务?软件测试报告收费标准
    Qt扩展-QCustomPlot绘图基础概述
    第三十二章 使用 CSP 进行基于标签的开发 - 服务器端方法
    ElasticSearch查询
  • 原文地址:https://blog.csdn.net/weixin_41446370/article/details/140405444