使用 Pygame 创建五子棋游戏

一、准备工作

1.环境搭建

参考 pygame 实现 flappybird 并打包成 exe 运行文件

2.媒体文件准备

音乐媒体文件 作用
victory.wav 玩家胜利音乐
bg_music.mp3 游戏背景音乐
click.wav 按钮点击声
forbid.wav 禁止点击声
put_down.wav 棋子落子声
图片媒体文件 作用
chessboard.png 棋盘
src.jpg exe图标
logo.png 任务栏图标

媒体文件

媒体文件

二、模块说明

1.函数文件

文件 作用
black.py 黑子
cursors.py 鼠标格式
main.py 主程序
white.py 白子

2.简易逻辑说明

创建三个按钮start(开始),back(悔棋),again(再来一次),点击“开始”才能落子,创建一个矩阵来对应棋盘上的落子,在矩阵中用零表示空,用1表示黑,10 表示白(黑 1 白 10 ),每落一子则用一个 5 * 5 大小的矩阵遍历棋盘,判断矩阵横竖斜的和是否为 5 或 10,每点击一次“悔棋”则抹除棋盘上的子,并在矩阵相应位置设置为 0,点击“再来”按钮则刷新游戏

逻辑注意

1.黑白的对应数字尽量大避免有交集(如若白 1 黑 2,则 5 白与 2 黑 1 白等同,造误判)
2.游戏遵循白先黑后,标志位设定可以看出来
3.在悔棋处有一个逻辑缺陷,由于时间冲突(刚写完就被其他事占领)未更改,就是单悔棋一子会造成黑白交换,悔棋二子才不会,您可以自行更改
4.一定要分清并对应棋盘坐标与矩阵坐标

3.程序梳理

cursors.py

thickarrow_strings 
text_no 
text_arrow

主要包括三个鼠标形态,箭头,禁止,无柄箭头
由 pygame 所提供的源代码修改而来

E:\Anaconda3\envs\tensorflow\Lib\site-packages\pygame\examples\cursors.py

black.pywhite.py
主要给出黑子与白子的参数

class Black(object):
    def __init__(self):
        self.color = 0, 0, 0
        self.radius = 10
        self.width = 10

main.py
导入必要的包与模块

import pygame
import numpy as np
import sys
from white import White
from black import Black
import cursors

落子的更新

def map_update(w_flag, s_flag, pos):
    if w_flag and s_flag:
        sound.play()
        pygame.draw.circle(screen, White.color, pos, White.radius, White.width)
    if not w_flag and s_flag:
        sound.play()
        pygame.draw.circle(screen, Black.color, pos, Black.radius, Black.width)

棋局结果的判断

def result(matrix, num):
    over = False
    bg_color = (72, 61, 139)
    text_color = (255, 255, 255)
    result_font = pygame.font.SysFont('Arial', 40)
    white_rectangle = result_font.render("White is winner", True, text_color, bg_color)
    white_rectangle.set_alpha(200)
    black_rectangle = result_font.render("Black is winner", True, text_color, bg_color)
    black_rectangle.set_alpha(200)
    if num >= 9:
        for row in range(0, 11):
            for i in range(0, 11):
                calculate_area = matrix[row:row + 5, i:i + 5]
                if np.sum(calculate_area) >= 5:
                    if (np.trace(calculate_area) == 5 or np.trace(
                            np.fliplr(calculate_area)) == 5) or (
                            5 in sum(calculate_area[:, ]) or 5 in sum(calculate_area.T[:, ])):
                        matrix = np.zeros([15, 15], dtype=int)
                        over = True
                        white_win()
                        victory.play()
                    elif (np.trace(calculate_area) == 50 or np.trace(
                            np.fliplr(calculate_area)) == 50) or (
                            50 in sum(calculate_area[:, ]) or 50 in sum(calculate_area.T[:, ])):
                        matrix = np.zeros([15, 15], dtype=int)
                        over = True
                        black_win()
                        victory.play()
                    else:
                        over = False
    pygame.display.flip()
    return over

白色赢后的显示效果

def white_win():
    button_color = (0, 0, 255)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 30)
    img_button = start_font.render("White is WINNER", True, text_color, button_color)
    img_button.set_alpha(170)
    screen.blit(img_button, [screen.get_width() / 2 - img_button.get_width() / 2,
                             screen.get_height()/2 - img_button.get_height() / 2 - 30])
    pygame.display.flip()

黑色赢后的显示效果

def start_button():
    button_color = (0, 0, 255)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 30)
    img_button = start_font.render("Start", True, text_color, button_color)
    screen.blit(img_button, [screen.get_width() / 5 - img_button.get_width() / 2,
                             screen.get_height() - 40])
    pygame.display.flip()

悔棋按钮

def back_button():
    button_color = (0, 0, 255)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 30)
    img_button = start_font.render("Back", True, text_color, button_color)
    screen.blit(img_button, [screen.get_width() / 2 - img_button.get_width() / 2,
                             screen.get_height() - 40])
    pygame.display.flip()

再来一次按钮

def again_button():
    button_color = (0, 0, 255)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 30)
    img_button = start_font.render("Again", True, text_color, button_color)
    screen.blit(img_button, [4 * screen.get_width() / 5 - img_button.get_width() / 2,
                             screen.get_height() - 40])
    pygame.display.flip()

提前开辟变量,用于存储落子的顺序,分别为实际棋盘与矩阵棋盘,方便悔棋时清理棋盘

chess_order = []
board_order = []

主程序入口与函数初始化,参数设定

if __name__ == '__main__':
    pygame.init()
    pygame.mixer.init()
    pygame.font.init()
    font = pygame.font.SysFont('Arial', 50)
    size = width, height = 535, 586
    screen = pygame.display.set_mode(size)
    screen.fill((255, 0, 0))
    background = pygame.image.load('picture/chessboard.png')
    game_icon = pygame.image.load('picture/logo.png')
    pygame.display.set_icon(game_icon)
    White = White()
    Black = Black()

初始标志位,分别为开始,悔棋,再来,白棋落子,游戏结束标志位

    start_flag = False
    back_flag = False
    again_flag = False
    white_flag = True
    chess_over = False

背景显示,创建棋盘矩阵,数量与位置初始化

    screen.blit(background, (0, 0))
    chess_board = np.zeros([15, 15], dtype=int)
    chess_num = 0
    white_num = 0
    position = (0, 0)

函数初始化,音效载入

    clock = pygame.time.Clock()
    pygame.mixer.music.load('music/bg_music.mp3')
    pygame.mixer.music.play(-1, 0.0)
    sound = pygame.mixer.Sound('music/put_down.wav')
    click_sound = pygame.mixer.Sound('music/click.wav')
    forbidden = pygame.mixer.Sound('music/forbid.wav')
    victory = pygame.mixer.Sound('music/victory.wav')

循环开始,pygame标准开始格式

   while True:
        clock.tick(60)
        again_button()
        start_button()
        back_button()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

获取鼠标坐标,限定鼠标范围,通过标志位设置鼠标形态

            x, y = pygame.mouse.get_pos()
            click = pygame.mouse.get_pressed()
            if x in range(0, 536) and y in range(0, 537) and not start_flag:
                curs, mask = cursors.compile(cursors.text_no, 'X', '.')
                pygame.mouse.set_cursor((24, 24), (0, 0), curs, mask)
            elif start_flag and x in range(0, 536) and y in range(0, 537):
                curs, mask = cursors.compile(cursors.thickarrow_strings, 'X', '.')
                pygame.mouse.set_cursor((24, 24), (0, 0), curs, mask)
            else:
                curs, mask = cursors.compile(cursors.text_arrow, 'X', '.')
                pygame.mouse.set_cursor((24, 24), (0, 0), curs, mask)

标志位判断与载入游戏音效

            if not start_flag and event.type == pygame.MOUSEBUTTONDOWN and x in range(0, 536) and y in range(0, 537):
                forbidden.play()
            if chess_over and x in range(0, 536) and y in range(0, 537)and event.type == pygame.MOUSEBUTTONDOWN:
                forbidden.play()
            if chess_over and x in range(0, 536) and y in range(0, 537):
                curs, mask = cursors.compile(cursors.text_no, 'X', '.')
                pygame.mouse.set_cursor((24, 24), (0, 0), curs, mask)
            if click[0] == 1 and (x in range(81, 133) and y in range(546, 582)):
                click_sound.play()
                start_flag = True
            if click[0] == 1 and (x in range(240, 294) and y in range(546, 582)) and not start_flag:
                click_sound.play()
            if click[0] == 1 and (x in range(396, 459) and y in range(546, 582))and not start_flag:
                click_sound.play()
            if click[0] == 1 and (x in range(240, 294) and y in range(546, 582)) and start_flag and np.sum(chess_board) and not chess_over:
                click_sound.play()
                back_flag = True
            if click[0] == 1 and (x in range(240, 294) and y in range(546, 582)):
                click_sound.play()
            if click[0] == 1 and (x in range(396, 459) and y in range(546, 582))and start_flag:
                click_sound.play()
                again_flag = True

标志位判断游戏状态

            if (x in range(0, 536) and y in range(0, 537)) and start_flag and not back_flag and not again_flag:
                x_chess = round((x - 23) / 35) * 35 + 23
                y_chess = round((y - 23) / 35) * 35 + 23
                x_board = round((x - 23) / 35)
                y_board = round((y - 23) / 35)
                position = (x_board, y_board)
                pos_chess = (x_chess, y_chess)
                if not chess_over:
                    if white_flag and position[0] in range(0,15) and position[1] in range(0,15):
                        if event.type == pygame.MOUSEBUTTONDOWN and not chess_board[position]:
                            map_update(white_flag, start_flag, pos_chess)
                            chess_order.append(pos_chess)
                            board_order.append(position)
                            chess_board[position] = 1
                            white_flag = bool(1 - white_flag)
                            chess_num += 1
                            chess_over = result(chess_board, chess_num)
                    else:
                        if position[0] in range(0,15) and position[1] in range(0,15):
                            if event.type == pygame.MOUSEBUTTONDOWN and not chess_board[position]:
                                map_update(white_flag, start_flag, pos_chess)
                                chess_order.append(pos_chess)
                                board_order.append(position)
                                chess_board[position] = 10
                                white_flag = bool(1 - white_flag)
                                chess_num += 1
                                chess_over = result(chess_board, chess_num)
            if start_flag and not back_flag and again_flag:
                chess_board = np.zeros([15, 15], dtype=int)
                chess_order.clear()
                board_order.clear()
                screen.blit(background, (0, 0))
                again_flag = False
                chess_over = False
            if start_flag and back_flag and not again_flag and not chess_over:
                if chess_order:
                    chess_board[board_order[-1]] = 0
                    pos_point = chess_order[-1]
                    back_bg1 = pygame.transform.chop(background, (0, 0, pos_point[0] - 11, pos_point[1] - 11))
                    back_bg2 = pygame.transform.rotate(back_bg1, 180)
                    back_bg3 = pygame.transform.chop(back_bg2,
                                                     (0, 0, back_bg2.get_width() - 22, back_bg2.get_height() - 22))
                    final_bg = pygame.transform.rotate(back_bg3, 180)
                    screen.blit(final_bg, (pos_point[0] - 11, pos_point[1] - 11))
                    chess_order.pop()
                    board_order.pop()
                    back_flag = False
        screen.blit(screen, (0, 0))
        pygame.display.update()

程序标志位分析概要:

1.标志位包括白棋的标志位,通过反转形成黑白交替落子
2.通过开始标志位判断游戏是否开始,悔棋标志位判断是否悔棋,再来标志位判断是否刷新游戏
3.为了增强程序的鲁棒性,标志位赢联合使用而不是单一使用,减少 bug 的产生

4.程序运行

(base) C:\Users\HaoHao>activate tensorflow
(tensorflow) C:\Users\HaoHao>cd F:\Gobang
(tensorflow) C:\Users\HaoHao>F:
(tensorflow) F:\Gobang>python main.py

运行

三、Pyinstaller 打包(附一般调试方法)

1.一般打包方法

由于Pygame具有窗口所以一般直接打包方法为

pyinstaller -F -w -i logo.ico main.py

但会报 failed to execute script 错误(请手动排除媒体文件不在同级目录的原因)

2.增加调试打包

pyinstaller -F -w -c -i logo.ico main.py

打包完成后,打开cmd,将 exe 文件直接拖入,会看到报错信息
报错

C:\Users\HaoHao>E:\Desktop\Gobang\dist\main.exe
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "main.py", line 2, in <module>
  File "e:\anaconda3\envs\tensorflow\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\numpy\__init__.py", line 150, in <module>
  File "e:\anaconda3\envs\tensorflow\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\numpy\random\__init__.py", line 180, in <module>
  File "mtrand.pyx", line 1, in init numpy.random.mtrand
ModuleNotFoundError: No module named 'numpy.random.common'
[5356] Failed to execute script main

这是缺少模块的原因
命令增加隐藏模块导入部分 --hidden-import

pyinstaller -F -w -c -i logo.ico main.py --hidden-import numpy.random.common

完成后再拖入 cmd,发现缺少模块,再输入 --hidden-import,如此反复,直到成功,最终命令如下

pyinstaller -F -w -c -i logo.ico main.py --hidden-import numpy.random.common --hidden-import numpy.random.bounded_integers --hidden-import numpy.random.entropy

3.最终命令

只需要将上面的 -c 调试模式取消就行

pyinstaller -F -w -i logo.ico main.py --hidden-import numpy.random.common --hidden-import numpy.random.bounded_integers --hidden-import numpy.random.entropy

后述

Pyinstaller 与编译器对程序的要求是具有差异的,有可能编译器能运行,但程序打包后出现各种各样的情况,甚至无法打包成功,本次程序有如下几点注意

1.font 的参数不能出现 None(编译器可以运行,Pyinstaller 打包无法正常运行)
2.需要的媒体文件需要拷贝至可执行文件处(Failed to execute script main)
3.ico 图标文件不能将图片直接修改后缀得到来使用,必须转换才能使用(报缓存错误)
4.包含所有的隐藏模块

最终代码已上传至我的 github
github

猜你喜欢

转载自blog.csdn.net/qq_39567427/article/details/104538053