Pygame制作五子棋小游戏

部署运行你感兴趣的模型镜像

使用的Python 版本为3.10,IDE是Pycharm2024。

其中Pygame版本为2.6.1,numpy版本为2.1.3。

制作不易,感觉不赖的话可以点点赞,感谢!

一、结果展示

二、源码展示

import pygame
import numpy as np

# 初始化
pygame.init()

# 常量
WIDTH, HEIGHT = 800, 800
LINE_COUNT = 15
GRID_SIZE = WIDTH // (LINE_COUNT + 1)
BOARD_SIZE = GRID_SIZE * (LINE_COUNT - 1)

# 颜色表
BACKGROUND_COLOR = (220, 179, 92)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

# 创建游戏窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("五子棋游戏")
font = pygame.font.SysFont(None, 36)


class GomokuGame:
    def __init__(self):
        self.reset_game()
        self.offset = GRID_SIZE

    def reset_game(self):
        self.board = np.zeros((LINE_COUNT, LINE_COUNT), dtype=int)
        self.current_player = 1
        self.game_over = False
        self.winner = None
        self.last_move = None
        self.winning_line = None

    def draw_board(self):
        screen.fill(BACKGROUND_COLOR)

        for i in range(LINE_COUNT):
            pygame.draw.line(screen, BLACK,
                             (self.offset, self.offset + i * GRID_SIZE),
                             (self.offset + BOARD_SIZE, self.offset + i * GRID_SIZE), 2)
            pygame.draw.line(screen, BLACK,
                             (self.offset + i * GRID_SIZE, self.offset),
                             (self.offset + i * GRID_SIZE, self.offset + BOARD_SIZE), 2)

        # 绘制棋盘上的五个点
        points = [(3, 3), (3, 11), (7, 7), (11, 3), (11, 11)]
        for point in points:
            x, y = point
            pygame.draw.circle(screen, BLACK,
                               (self.offset + x * GRID_SIZE, self.offset + y * GRID_SIZE), 5)

    def draw_pieces(self):
        for row in range(LINE_COUNT):
            for col in range(LINE_COUNT):
                if self.board[row][col] == 1:
                    pygame.draw.circle(screen, BLACK,
                                       (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                                       GRID_SIZE // 2 - 2)
                elif self.board[row][col] == 2:
                    pygame.draw.circle(screen, WHITE,
                                       (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                                       GRID_SIZE // 2 - 2)

        # 标记最后一步棋
        if self.last_move:
            row, col = self.last_move
            pygame.draw.circle(screen, RED,
                               (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                               5)

        # 绘制胜利线
        if self.game_over and self.winning_line:
            start_row, start_col, end_row, end_col = self.winning_line
            start_x = self.offset + start_col * GRID_SIZE
            start_y = self.offset + start_row * GRID_SIZE
            end_x = self.offset + end_col * GRID_SIZE
            end_y = self.offset + end_row * GRID_SIZE
            pygame.draw.line(screen, RED, (start_x, start_y), (end_x, end_y), 3)

    def place_piece(self, row, col):
        if self.game_over:
            return False

        # 检查是否已有棋子
        if self.board[row][col] != 0:
            return False

        # 放置棋子
        self.board[row][col] = self.current_player
        self.last_move = (row, col)

        # 检查是否获胜
        win_info = self.check_win(row, col)
        if win_info[0]:  # 如果获胜
            self.game_over = True
            self.winner = self.current_player
            self.winning_line = win_info[1]
        elif np.count_nonzero(self.board) == LINE_COUNT * LINE_COUNT:
            self.game_over = True
            self.winner = 0
        else:
            # 切换玩家
            self.current_player = 3 - self.current_player

        return True

    def check_win(self, row, col):
        player = self.board[row][col]

        # 检查方向
        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]

        for dr, dc in directions:
            count = 1  # 当前位置已经有一个棋子
            start_row, start_col = row, col
            end_row, end_col = row, col

            r, c = row + dr, col + dc
            while 0 <= r < LINE_COUNT and 0 <= c < LINE_COUNT and self.board[r][c] == player:
                count += 1
                end_row, end_col = r, c
                r += dr
                c += dc

            # 反向检查
            r, c = row - dr, col - dc
            while 0 <= r < LINE_COUNT and 0 <= c < LINE_COUNT and self.board[r][c] == player:
                count += 1
                start_row, start_col = r, c
                r -= dr
                c -= dc

            if count >= 5:
                return True, (start_row, start_col, end_row, end_col)

        return False, None

    def draw_game_info(self):
        # 显示当前玩家
        player_text = "Now: " + ("Black" if self.current_player == 1 else "White")
        player_color = BLACK if self.current_player == 1 else WHITE
        text_surface = font.render(player_text, True, player_color)
        screen.blit(text_surface, (20, 20))

        # 获胜信息
        if self.game_over:
            if self.winner == 0:
                message = "Round Draw!"
            else:
                message = f"{'Black' if self.winner == 1 else 'White'} Win!"

            # 半透明背景
            s = pygame.Surface((WIDTH, 100), pygame.SRCALPHA)
            s.fill((0, 0, 0, 180))
            screen.blit(s, (0, HEIGHT - 420))

            # 显示消息
            text_surface = font.render(message, True, RED)
            screen.blit(text_surface, (WIDTH // 2 - text_surface.get_width() // 2, HEIGHT - 400))

            # 显示重新开始提示
            restart_text = "Press R to restart~"
            restart_surface = font.render(restart_text, True, WHITE)
            screen.blit(restart_surface, (WIDTH // 2 - restart_surface.get_width() // 2, HEIGHT - 360))

    def draw_mouse_hover(self, pos):
        if self.game_over:
            return

        # 将鼠标位置转换为棋盘坐标
        x, y = pos
        col = round((x - self.offset) / GRID_SIZE)
        row = round((y - self.offset) / GRID_SIZE)

        # 检查坐标是否在棋盘范围内
        if 0 <= row < LINE_COUNT and 0 <= col < LINE_COUNT:
            # 如果该位置为空,绘制半透明的棋子预览
            if self.board[row][col] == 0:
                color = BLACK if self.current_player == 1 else WHITE
                s = pygame.Surface((GRID_SIZE, GRID_SIZE), pygame.SRCALPHA)
                pygame.draw.circle(s, (*color, 150), (GRID_SIZE // 2, GRID_SIZE // 2), GRID_SIZE // 2 - 2)
                screen.blit(s, (self.offset + col * GRID_SIZE - GRID_SIZE // 2,
                                self.offset + row * GRID_SIZE - GRID_SIZE // 2))

    def run(self):
        # 游戏主循环
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()

                # 鼠标点击事件
                if event.type == pygame.MOUSEBUTTONDOWN and not self.game_over:
                    if event.button == 1:
                        x, y = event.pos
                        col = round((x - self.offset) / GRID_SIZE)
                        row = round((y - self.offset) / GRID_SIZE)

                        if 0 <= row < LINE_COUNT and 0 <= col < LINE_COUNT:
                            self.place_piece(row, col)

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:
                        self.reset_game()

            self.draw_board()
            self.draw_pieces()
            self.draw_mouse_hover(pygame.mouse.get_pos())
            self.draw_game_info()

            pygame.display.flip()


if __name__ == "__main__":
    game = GomokuGame()
    game.run()

三、代码解释

想要制作一个五子棋游戏,那我们首先就得了解五子棋的规则:(内容来自百度)

  • 棋盘布局:五子棋棋盘为15×15的方形格子。

  • 棋子标记:通常用黑白两种颜色的棋子,黑棋先手。

  • 落子顺序:黑方先下,轮流落子,每次只能下一个棋子。

  • 落子位置:棋子放在棋盘的任意交叉点上。

  • 胜利条件:先在棋盘上形成五个相同颜色的棋子连成一条线(横、竖、斜线皆可)的一方获胜。

了解完五子棋游戏以后,初步思路是利用pygame的可视化界面以及对鼠标键盘的事件响应来实现棋盘展现、棋子的放置等等。然后通过numpy库的矩阵计算来实现五子棋的胜利逻辑。


1.全局变量

初始设置一些需要用到的常量,例如窗口的长宽,棋盘的宽度以及颜色表,方便后续编写时调用。

# 常量
WIDTH, HEIGHT = 800, 800  # 窗口尺寸
LINE_COUNT = 15  # 棋盘的宽度,一般为15*15
GRID_SIZE = WIDTH // (LINE_COUNT + 1)  # 棋盘的网格尺寸
BOARD_SIZE = GRID_SIZE * (LINE_COUNT - 1)  # 棋盘大小

# 颜色表
BACKGROUND_COLOR = (220, 179, 92)  # 棋盘配色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

# 创建游戏窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))  # 创建800*800的窗口
pygame.display.set_caption("五子棋游戏")  # 设置窗口标题
font = pygame.font.SysFont(None, 36)  # 字体设置,None表示默认字体

2.类的编写

__init__()和reset_game()

此处部分较多且为代码的核心部分,所以下方我逐个函数进行分析:

以下代码我创建一个GomokuGame类来实现五子棋的功能和性质。作为五子棋,每次调用为实例时应该呈现的是一个15*15的空棋盘。所以在初始化方法__init__()中写入重置棋盘的函数,并且设置棋盘的距离窗口的边距,让棋盘有一个居中的视觉效果。

在reset_game函数中:

  1. 利用np中的零矩阵来模拟空的棋盘。
  2. 设定黑为1,白为2。根据规则,黑棋先行,current_player = 1
  3. 其余的就是将游戏结束信号、赢家、最后一步棋的位置以及胜利连线全部初始化为空。

这样就算是完全重新开了一局新的游戏。

# 五子棋类
class GomokuGame:
    def __init__(self):
        self.reset_game()  # 重置游戏
        self.offset = GRID_SIZE  # 棋盘的边距

    def reset_game(self):
        self.board = np.zeros((LINE_COUNT, LINE_COUNT), dtype=int)  # 空棋盘
        self.current_player = 1  # 黑棋为1,先行
        self.game_over = False  # 游戏结束信号
        self.winner = None  # 赢家
        self.last_move = None  # 最后一步棋位置记录
        self.winning_line = None  # 胜利连线

 draw_board()和draw_pieces()

此处为绘制图像,也就是最终呈现的棋盘部分,其中我通过for循环 + pygame中的draw模块的line函数来绘制棋盘,循环次数为之前的常量LINE_COUNT。其中以绘制横线那部分为例来详细解释一下各个参数表示的意思:

  1. screen:绘制的目标,即我之前创建的窗口
  2. BLACK:线条颜色
  3. (self.offset, self.offset + i * GRID_SIZE):  起点坐标(x = 边距, y = 边距 + 循环每格)
  4. (self.offset + BOARD_SIZE, self.offset + i * GRID_SIZE):  终点坐标(x = 边距 + 棋盘总长, y = 边距 + 循环每格)
  5. 最后一个2表示线条的宽度。

然后是绘制棋子的部分,绘制棋子的部分比较简单,通过两个for循环嵌套来遍历整个棋盘board,也就是初始化时设置的零矩阵。如果该处为1,那么表示此处为黑子,此时绘制黑棋。如果为2,此时绘制白棋。绘制利用circle()函数,其参数与上述的差不多。

除了绘制棋子之外,还用红点标记最后一步棋子的位置,以便玩家做出相应的对策。同时,当胜利的时候,还要记录胜利的五子位置,所以还应记录胜利五子的首尾棋子的位置,便于绘制连接线。

    def draw_board(self):
        screen.fill(BACKGROUND_COLOR)  # 背景颜色填充

        # 绘制网格线
        for i in range(LINE_COUNT):
            # 横线
            pygame.draw.line(screen, BLACK,
                             (self.offset, self.offset + i * GRID_SIZE),
                             (self.offset + BOARD_SIZE, self.offset + i * GRID_SIZE), 2)
            # 竖线
            pygame.draw.line(screen, BLACK,
                             (self.offset + i * GRID_SIZE, self.offset),
                             (self.offset + i * GRID_SIZE, self.offset + BOARD_SIZE), 2)

        # 绘制棋盘上的天元和星(棋盘都有的五个点,严谨加上)
        points = [(3, 3), (3, 11), (7, 7), (11, 3), (11, 11)]
        for point in points:
            x, y = point
            pygame.draw.circle(screen, BLACK,
                               (self.offset + x * GRID_SIZE, self.offset + y * GRID_SIZE), 5)

    def draw_pieces(self):
        for row in range(LINE_COUNT):
            for col in range(LINE_COUNT):
                if self.board[row][col] == 1:  # 绘制黑棋
                    pygame.draw.circle(screen, BLACK,
                                       (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                                       GRID_SIZE // 2 - 2)
                elif self.board[row][col] == 2:  # 绘制白棋
                    pygame.draw.circle(screen, WHITE,
                                       (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                                       GRID_SIZE // 2 - 2)

        # 标记最后一步棋
        if self.last_move:
            row, col = self.last_move
            pygame.draw.circle(screen, RED,
                               (self.offset + col * GRID_SIZE, self.offset + row * GRID_SIZE),
                               5)

        # 绘制胜利线
        if self.game_over and self.winning_line:
            start_row, start_col, end_row, end_col = self.winning_line
            start_x = self.offset + start_col * GRID_SIZE
            start_y = self.offset + start_row * GRID_SIZE
            end_x = self.offset + end_col * GRID_SIZE
            end_y = self.offset + end_row * GRID_SIZE
            pygame.draw.line(screen, RED, (start_x, start_y), (end_x, end_y), 3)

place_piece()和 check_win()

此处为放置棋子和判断胜利的逻辑,后续会跟鼠标的点击事件联系。也是整个程序中最重要的部分,五子相连的逻辑判断。主要时对于check_win()程序进行讲解:

check_win函数需要的参数是当前棋子的位置,即行和列(row,col)。player表示当前是白棋还是黑棋。设置directions列表,里面包括四个方向向量:水平、垂直、主对角线方向、副对角线方向。

其中使用for循环对每个方向进行检查,dr、dc分别表示行方向的变化量和列方向上的变化量。初始化count = 1,start和end。因为当前已经有了一个棋子,所以数量记为1,其中起点和终点都是该棋子。

以水平方向检查为例,r和c为当前方向的延伸,同时在while循环中来确保棋子仍在棋盘内而且属于该player的棋子。在该棋子正方向上延伸,如果有棋子count就加1,并且更新终点位置end,同时继续向正方向延伸直到没有同类棋子。当正向检查结束之后,开始反向检查,基本同理,就是r和c向左方延伸,然后逐个查找是否为同类棋子,直至不满足条件。

如果水平方向上没有满足胜利条件,那么就会继续垂直、主对角线、副对角线这三个方向上进行查找。如果有满足count>=5的,那么即为胜利,返回胜利五子的起点坐标和终点坐标。

place_piece()函数主要是检查落子的合法性以及执行落子操作,接收的是当前要落子的行坐标和列坐标。在self.board上更新为当前的棋子,黑棋就是1,白棋就是2。并且更新last_move为当前的棋子位置。

通过调用check_win()来检查是否达成胜利,胜利了就更新game_over信号、并且将当前棋子定为赢家,同时将起点终点的坐标载入到winning_line。如果棋盘被填满(非零元素为零个)且无人获胜,则视为平局。否则就通过数学简便写法 3 - current_player 来切换黑子和白子。

    def place_piece(self, row, col):
        if self.game_over:  # 游戏结束时不能下棋
            return False

        # 检查是否已有棋子
        if self.board[row][col] != 0:
            return False

        # 放置棋子
        self.board[row][col] = self.current_player
        self.last_move = (row, col)

        # 检查是否获胜
        win_info = self.check_win(row, col)
        if win_info[0]:  # 如果获胜
            self.game_over = True
            self.winner = self.current_player
            self.winning_line = win_info[1]
        elif np.count_nonzero(self.board) == LINE_COUNT * LINE_COUNT:  # 平局
            self.game_over = True
            self.winner = 0
        else:
            # 切换玩家
            self.current_player = 3 - self.current_player

        return True

    def check_win(self, row, col):
        player = self.board[row][col]

        # 检查方向
        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]

        for dr, dc in directions:
            count = 1  # 当前位置已经有一个棋子
            start_row, start_col = row, col  # 五子首
            end_row, end_col = row, col  # 五子尾

            # 正向检查
            r, c = row + dr, col + dc
            while 0 <= r < LINE_COUNT and 0 <= c < LINE_COUNT and self.board[r][c] == player:
                count += 1
                end_row, end_col = r, c
                r += dr
                c += dc

            # 反向检查
            r, c = row - dr, col - dc
            while 0 <= r < LINE_COUNT and 0 <= c < LINE_COUNT and self.board[r][c] == player:
                count += 1
                start_row, start_col = r, c
                r -= dr
                c -= dc
            
            # 如果个数大于等于5,视作胜利
            if count >= 5:
                return True, (start_row, start_col, end_row, end_col)

        return False, None

draw_game_info()和draw_mouse_hover()

draw_game_info()这个方法是五子棋游戏的信息显示函数,负责在屏幕上绘制当前游戏状态和结果信息。例如在屏幕左上角(20, 20)位置显示当前轮到哪位玩家下棋、在游戏结束时显示半透明背景显示赢家或者平局,同时提示按R去重启对局。

draw_game_info()这个方法用于显示鼠标悬停时的预览效果。通过读取鼠标的屏幕像素坐标(x, y),移去棋盘偏移量self.offset,除以格子的像素大小四舍五入可以得到最近的整数行列索引。同时确保再棋盘内,而且该位置没有棋子才可显示预览。透明效果需要用到pygame.SRCALPHA来启用透明通道,创建透明的Surface。

    def draw_game_info(self):
        # 显示当前玩家
        player_text = "Now: " + ("Black" if self.current_player == 1 else "White")
        player_color = BLACK if self.current_player == 1 else WHITE
        text_surface = font.render(player_text, True, player_color)
        screen.blit(text_surface, (20, 20))

        # 获胜信息
        if self.game_over:
            if self.winner == 0:
                message = "Round Draw!"
            else:
                message = f"{'Black' if self.winner == 1 else 'White'} Win!"

            # 半透明背景
            s = pygame.Surface((WIDTH, 100), pygame.SRCALPHA)
            s.fill((0, 0, 0, 180))
            screen.blit(s, (0, HEIGHT - 420))

            # 显示消息
            text_surface = font.render(message, True, RED)
            screen.blit(text_surface, (WIDTH // 2 - text_surface.get_width() // 2, HEIGHT - 400))

            # 显示重新开始提示
            restart_text = "Press R to restart~"
            restart_surface = font.render(restart_text, True, WHITE)
            screen.blit(restart_surface, (WIDTH // 2 - restart_surface.get_width() // 2, HEIGHT - 360))

    def draw_mouse_hover(self, pos):
        if self.game_over:
            return

        # 将鼠标位置转换为棋盘坐标
        x, y = pos
        col = round((x - self.offset) / GRID_SIZE)
        row = round((y - self.offset) / GRID_SIZE)

        # 检查坐标是否在棋盘范围内
        if 0 <= row < LINE_COUNT and 0 <= col < LINE_COUNT:
            # 如果该位置为空,绘制半透明的棋子预览
            if self.board[row][col] == 0:
                color = BLACK if self.current_player == 1 else WHITE
                s = pygame.Surface((GRID_SIZE, GRID_SIZE), pygame.SRCALPHA)
                pygame.draw.circle(s, (*color, 150), (GRID_SIZE // 2, GRID_SIZE // 2), GRID_SIZE // 2 - 2)
                screen.blit(s, (self.offset + col * GRID_SIZE - GRID_SIZE // 2,
                                self.offset + row * GRID_SIZE - GRID_SIZE // 2))

run()

该方法是五子棋游戏的核心控制循环,负责处理游戏的所有事件和渲染。主循环中不断获取待处理的事件,更新整个显示界面,例如窗口的关闭、左键点击时放置棋子、R键重新开始游戏。再循环中绘制棋盘和背景,绘制所有棋子以及游戏信息状态和鼠标悬停预览。

    def run(self):
        # 游戏主循环
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:  # 退出游戏
                    pygame.quit()

                # 鼠标点击事件
                if event.type == pygame.MOUSEBUTTONDOWN and not self.game_over:
                    if event.button == 1:  # 左键点击
                        # 转换坐标并尝试放置棋子
                        x, y = event.pos
                        col = round((x - self.offset) / GRID_SIZE)
                        row = round((y - self.offset) / GRID_SIZE)

                        if 0 <= row < LINE_COUNT and 0 <= col < LINE_COUNT:
                            self.place_piece(row, col)

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:  # R键重新开始
                        self.reset_game()

            # 游戏绘制
            self.draw_board()
            self.draw_pieces()
            self.draw_mouse_hover(pygame.mouse.get_pos())
            self.draw_game_info()

            pygame.display.flip()

您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值