【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 = xself.y = yself.width = widthself.height = heightself.abs_x = x * widthself.abs_y = y * heightself.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 = Noneself.coord = self.get_coord()self.highlight = Falseself.rect = pygame.Rect(self.abs_x,self.abs_y,self.width,self.height)# get the formal notation of the tiledef 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 tileif 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 iconsif self.occupying_piece != None:centering_rect = self.occupying_piece.img.get_rect()centering_rect.center = self.rect.centerdisplay.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.pyimport 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 = widthself.height = heightself.tile_width = width // 8self.tile_height = height // 8self.selected_piece = Noneself.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 outputdef get_square_from_pos(self, pos):for square in self.squares:if (square.x, square.y) == (pos[0], pos[1]):return squaredef 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 haveif 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 Boardelif 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_widthy = my // self.tile_heightclicked_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_pieceelif 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 checkerdef is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]output = Falseking_pos = Nonechanging_piece = Noneold_square = Nonenew_square = Nonenew_square_old_piece = Noneif board_change is not None:for square in self.squares:if square.pos == board_change[0]:changing_piece = square.occupying_pieceold_square = squareold_square.occupying_piece = Nonefor square in self.squares:if square.pos == board_change[1]:new_square = squarenew_square_old_piece = new_square.occupying_piecenew_square.occupying_piece = changing_piecepieces = [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.posif king_pos == None:for piece in pieces:if piece.notation == 'K' and piece.color == color:king_pos = piece.posfor piece in pieces:if piece.color != color:for square in piece.attacking_squares(self):if square.pos == king_pos:output = Trueif board_change is not None:old_square.occupying_piece = changing_piecenew_square.occupying_piece = new_square_old_piecereturn 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 checkerdef is_in_checkmate(self, color):output = Falsefor piece in [i.occupying_piece for i in self.squares]:if piece != None:if piece.notation == 'K' and piece.color == color:king = pieceif king.get_valid_moves(self) == []:if self.is_in_check(color):output = Truereturn output

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

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

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

制作棋子

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

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

# /* Piece.pyimport pygameclass Piece:def __init__(self, pos, color, board):self.pos = posself.x = pos[0]self.y = pos[1]self.color = colorself.has_moved = Falsedef 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:breakelse:output.append(square)breakelse: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 = Falseif 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.yprev_square.occupying_piece = Nonesquare.occupying_piece = selfboard.selected_piece = Noneself.has_moved = True# Pawn promotionif self.notation == ' ':if self.y == 0 or self.y == 7:from data.classes.pieces.Queen import Queensquare.occupying_piece = Queen((self.x, self.y),self.color,board)# Move rook if king castlesif 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 Trueelse:board.selected_piece = Nonereturn False# True for all pieces except pawndef 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.pyimport pygamefrom data.classes.Piece import Piececlass 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 forwardif 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 outputdef get_moves(self, board):output = []for square in self.get_possible_moves(board):if square.occupying_piece != None:breakelse: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 outputdef 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.pyimport pygame
from data.classes.Piece import Piececlass 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 andnew_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.pyimport pygame
from data.classes.Piece import Piececlass 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:breakmoves_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:breakmoves_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:breakmoves_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:breakmoves_nw.append(board.get_square_from_pos((self.x - i, self.y - i)))output.append(moves_nw)return output

Rook.py:

# /* Rook.pyimport pygamefrom data.classes.Piece import Piececlass 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.pyimport pygame
from data.classes.Piece import Piececlass 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:breakmoves_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:breakmoves_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:breakmoves_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:breakmoves_nw.append(board.get_square_from_pos((self.x - i, self.y - i)))output.append(moves_nw)return output

King.py:

# /* King.pyimport pygame
from data.classes.Piece import Piececlass 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 andnew_pos[0] >= 0 and new_pos[1] < 8 and new_pos[1] >= 0):output.append([board.get_square_from_pos(new_pos)])return outputdef 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 pygamefrom data.classes.Board import Boardpygame.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 = Truewhile running:mx, my = pygame.mouse.get_pos()for event in pygame.event.get():# Quit the game if the user presses the close buttonif event.type == pygame.QUIT:running = Falseelif event.type == pygame.MOUSEBUTTONDOWN: # If the mouse is clickedif event.button == 1:board.handle_click(mx, my)if board.is_in_checkmate('black'): # If black is in checkmateprint('White wins!')running = Falseelif board.is_in_checkmate('white'): # If white is in checkmateprint('Black wins!')running = False# Draw the boarddraw(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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/46032.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

33.异步FIFO IP核的配置、调用与仿真

&#xff08;1&#xff09;异步FIFO的配置过程&#xff1a; ps&#xff1a;异步fifo相比较同步fifo少一个实际深度 &#xff08;2&#xff09;异步FIFO的调用: module dcfifo (input wr_clk ,input rd_clk ,input [7:0] …

2024-07-13 Unity AI状态机2 —— 项目介绍

文章目录 1 项目介绍2 模块介绍2.1 BaseState2.2 ...State2.2.1 PatrolState2.2.2 ChaseState / AttackState / BackState 2.3 StateMachine2.4 Monster 3 其他功能4 类图 项目借鉴 B 站唐老狮 2023年直播内容。 点击前往唐老狮 B 站主页。 1 项目介绍 ​ 本项目使用 Unity 2…

防火墙NAT和智能选路实验详解(华为)

目录 实验概述实验拓扑实验要求要求一要求二要求三要求四要求五 实验概述 从我上面一个博客能够了解到NAT和防火墙选路原理 ——>防火墙nat和智能选路&#xff0c;这一章我通过实验来详解防火墙关于nat和智能选路从而能熟练使用和配置防火墙&#xff0c;这里使用的是华为US…

《Cross-Image Pixel Contrasting for Semantic Segmentation》论文解读

期刊&#xff1a;TPAMI 年份&#xff1a;2024 摘要 研究图像语义分割问题。目前的方法主要集中在通过专门设计的上下文聚合模块(如空洞卷积、神经注意力)或结构感知的优化目标(如iou样损失)挖掘"局部"上下文&#xff0c;即单个图像中像素之间的依赖关系。然而&…

Dify中高质量索引模式时,通过线程池处理chunk过程

本文主要介绍了Dify中高质量索引模式时,如何通过线程池执行器来处理chunk的过程。源码位置:dify\api\core\indexing_runner.py\IndexingRunner._load。核心思想:假设一个数据集中有一个文档,该文档可以拆分为12个段(segment)。如果chunk_size=10,那么分为2批提交给线程池…

Mojo语言的运用

1.Mojo语言概述 1.1什么是Mojo语言&#xff1f; Mojo语言是一种现代的动态编程语言&#xff0c;主要用于Web开发。它结合了多种语言的优点&#xff0c;如JavaScript、Perl和Lua&#xff0c;旨在为开发者提供&#xff1a; 简洁的语法&#xff1a;易于学习和使用&#xff0c;适…

Azcopy Sync同步Azure文件共享

Azcopy Sync同步Azure文件共享 一、工作原理二、安装 AzCopy在 Windows 上在 Linux 上 三、资源准备1. 创建源和目标 Azure 存储账户2. 创建源和目标文件共享3. 确定路径4. 生成源和目的存储账户的共享访问签名&#xff08;SAS&#xff09;令牌配置权限示例生成的 URL 四、Azco…

【鸿蒙学习笔记】尺寸设置・width・height・size・margin・padding・

官方文档&#xff1a;尺寸设置 目录标题 width&#xff1a;设置组件自身的宽度height&#xff1a;设置组件自身的高度size&#xff1a;设置高宽尺寸margin&#xff1a;设置组件的外边距padding&#xff1a;设置组件的内边距 width&#xff1a;设置组件自身的宽度 参数为Length…

【Linux杂货铺】3.程序地址空间

1.程序地址空间的引入 fork(&#xff09;函数在调用的时候子如果是子进程则返回0&#xff0c;如果是父进程则返回子进程的pid&#xff0c;在代码中我们分别在子进程和父进程读取全局变量g_val的时候居然出现了俩个不同的值。如下&#xff1a; #include<stdio.h> #includ…

【Linux】软件管理工具 yum

文章目录 概念搜索&#xff1a;yum list安装&#xff1a;yum install卸载&#xff1a;yum remove 概念 在Linux下安装软件&#xff0c;可以下载到程序的源代码&#xff0c;进行编译得到可执行程序&#xff0c;另外这些软件还有依赖其它工具的问题&#xff0c;还得下载编译这些依…

底软驱动 | Linux字符设备驱动开发基础

文章目录 知识整理--Linux字符设备驱动开发基础字符设备基础1从一个最简单的模块源码说起字符设备驱动工作原理字符设备驱动代码实践--给空模块添加驱动壳子应用程序如何调用驱动 字符设备基础2添加读写接口&#xff08;应用和驱动之间的数据交换&#xff09;驱动中如何操控硬件…

WIFI连接阿里云

目录 1 实现功能 2 器件 3 AT指令 4 阿里云配置 4.1 打开阿里云 4.2 创建产品 4.3 添加设备 5 STM32配置 5.1 基础参数 5.2 功能定义 6 STM32代码 本文主要是记述一下&#xff0c;如何使用阿里云物联网平台&#xff0c;创建一个简单的远程控制小灯示例。 1 实现功能…

整洁架构SOLID-里氏替换原则(LSP)

文章目录 定义LSP继承实践正例反例 LSP软件架构实践反例 小结 定义 1988年&#xff0c;Barbara Liskov在描述如何定义子类型时写下了这样一段话&#xff1a; 这里需要的是一种可替换性&#xff1a;如果对于每个类型是S的对象o1都存在一个类型为T的对象o2&#xff0c;能使操作T…

Meta MobileLLM

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

鸿蒙开发—基础组件

目录 安装介绍 1.Text 2.Image 3.Button 4.Slider 安装介绍 该文章介绍鸿蒙开发中的部分基础组件&#xff0c;适用于鸿蒙开发的初学者。 1.软件下载地址&#xff1a;DevEco Studio-HarmonyOS Next Beta版-华为开发者联盟 (huawei.com) 2.安装DevEco Studio&#xff1a;…

最新版智能修图-中文luminar ai 1.55(13797) 和 neo1.20,支持m芯片和intel芯片(绝对可用)

Luminar AI for macOS 完整版本 这个程序是第一个完全由人工智能驱动的图像编辑器。有了它&#xff0c;创建引人注目的照片是有趣的&#xff0c;令人惊讶的容易。它是一个独立的照片编辑器和macOS插件。 Luminar AI for macOS 轻轻地塑造和完善一个肖像打造富有表现力的眼睛…

增加内容曝光、获得更多粉丝 - 「评论发红包」功能

目录 博客发放以及领取红包规则 1. 发布博客评论社区红包规则&#xff1a; 2. 博客评论红包领取规则 如何发红包评论&#xff1f; 发布红包评论益处 不知道大家有没有注意到&#xff0c;我们的「评论发红包」功能已经上线啦&#xff5e; 现在几乎所有的内容 -- 博客&…

TCP连接的三次握手和断开的四次挥手

TCP连接的建立过程通过三次握手完成&#xff0c;‌而连接的关闭过程则通过四次挥手完成。‌ 三次握手&#xff1a;‌这是TCP连接建立的过程&#xff0c;‌主要目的是确保双方都准备好进行数据传输。‌具体步骤如下&#xff1a;‌ 客户端向服务器发送一个SYN报文&#xff0c;‌请…

独立开发者系列(24)——使用redis

&#xff08;一&#xff09;REdis的使用原理 在早期的网站的时候&#xff0c;如果系统本身功能不是很复杂&#xff0c;比如就是内部的几个用户使用&#xff0c;而且基本就是汇报一点简单的设备维护信息&#xff0c;还有日常公告。完全可以不使用数据库&#xff0c;直接使用jso…

IoTDB 集群高效管理:一键启停功能介绍

如何快速启动、停止 IoTDB 集群节点的功能详解&#xff01; 在部署 IoTDB 集群时&#xff0c;对于基础的单机模式&#xff0c;启动过程相对简单&#xff0c;仅需执行 start-standalone 脚本来启动 1 个 ConfigNode 节点和 1 个 DataNode 节点。然而&#xff0c;对于更高级的分布…