第七章进阶:Pygame实战小项目-AI贪吃蛇、Python五子棋、植物大战僵尸

一、项目概述

    本次将带大家深入探索三个经典的Pygame实战项目:AI贪吃蛇、Python五子棋以及植物大战僵尸。通过这三个项目,我们将从基础到进阶,全面掌握Pygame在游戏开发中的应用,同时领略其在人工智能与游戏设计结合方面的魅力。资源绑定含有全部项目文件,请感兴趣的读者自行下载。

二、AI贪吃蛇

项目结构与思路

  • 环境搭建与初始化 :导入必要的库,如pygame、random、sys等,设置游戏窗口的尺寸、颜色等基础参数,初始化pygame环境。

  • 蛇与食物的表示 :用一维数组表示蛇的身体,通过索引操作实现蛇的移动、生长等操作;食物的位置随机生成,并确保不会与蛇的身体重合。

  • 游戏逻辑实现 :包括蛇的移动方向控制、碰撞检测(与墙壁、自身碰撞)、食物的生成与吃掉的逻辑、分数计算等。

  • AI算法集成 :运用如AI算法等路径搜索算法,让蛇能够自动规划路径去追逐食物,实现智能化的贪吃蛇。

关键知识点

  • Pygame基础 :窗口创建、事件循环、绘图函数(如绘制矩形、线条等)的使用。

  • 数组操作 :对蛇身体的一维数组进行插入、删除、移动等操作,实现蛇的动态变化。

  • 随机数生成 :用于食物的随机位置生成,确保游戏的随机性和趣味性。

  • 算法应用 :理解并应用路径搜索算法,使蛇具备智能行为,这是本项目的一大亮点,涉及到对算法逻辑的代码实现以及在游戏场景中的适配。

源代码:

​
# 导入必要的库
import pygame  # 用于游戏开发
import sys  # 系统相关操作
from random import randint  # 随机数生成

# 游戏场地尺寸(以格子数计算)
HEIGHT = 25  # 场地高度(y轴方向格子数)
WIDTH = 25  # 场地宽度(x轴方向格子数)

# 计算实际屏幕像素尺寸(每个格子25x25像素)
SCREEN_X = HEIGHT * 25  # 屏幕宽度
SCREEN_Y = WIDTH * 25  # 屏幕高度

FIELD_SIZE = HEIGHT * WIDTH  # 场地总格子数

# 蛇头在snake数组中的索引位置
HEAD = 0  # 蛇头总是数组第一个元素

# 定义场地元素状态常量(需保持足够间隔)
FOOD = 0  # 食物标识
UNDEFINED = (HEIGHT + 1) * (WIDTH + 1)  # 未定义状态(路径长度初始值)
SNAKE = 2 * UNDEFINED  # 蛇身标识

# 移动方向常量(用一维数组索引偏移量表示二维移动)
LEFT = -1  # 向左移动(索引-1)
RIGHT = 1  # 向右移动(索引+1)
UP = -WIDTH  # 向上移动(跨过WIDTH个元素)
DOWN = WIDTH  # 向下移动

# 错误码常量
ERR = -1111  # 错误移动方向

# 初始化游戏数据(用一维数组模拟二维场地)
board = [0] * FIELD_SIZE  # 主场地状态数组
snake = [0] * (FIELD_SIZE + 1)  # 蛇身位置数组(最大长度+1)
snake[HEAD] = 1 * WIDTH + 1  # 初始蛇头位置(第1行第1列)
snake_size = 1  # 初始蛇长度

# 临时变量用于模拟移动时的计算
tmpboard = [0] * FIELD_SIZE  # 临时场地状态
tmpsnake = [0] * (FIELD_SIZE + 1)  # 临时蛇身数组
tmpsnake[HEAD] = 1 * WIDTH + 1  # 临时蛇头初始位置
tmpsnake_size = 1  # 临时蛇长度

food = 3 * WIDTH + 3  # 初始食物位置(第3行第3列)
best_move = ERR  # 当前最佳移动方向
mov = [LEFT, RIGHT, UP, DOWN]  # 移动方向数组
key = pygame.K_RIGHT  # 初始移动方向(右侧)
score = 1  # 游戏分数(等于蛇长)


def show_text(screen, pos, text, color, font_bold=False, font_size=60, font_italic=False):
    """
    在屏幕上显示文字

    参数:
    screen: pygame.Surface - 要绘制文字的屏幕表面
    pos: tuple (x, y) - 文字左上角坐标
    text: str - 要显示的文字内容
    color: tuple (R, G, B) - 文字颜色
    font_bold: bool - 是否加粗(默认False)
    font_size: int - 字体大小(默认60)
    font_italic: bool - 是否斜体(默认False)
    """
    try:
        # 尝试加载中文字体(需要确保SimHei.ttf文件存在)
        font = pygame.font.Font("SimHei.ttf", font_size)
    except FileNotFoundError:
        font = pygame.font.Font(None, font_size)  # 使用默认字体

    # 设置字体样式
    font.set_bold(font_bold)
    font.set_italic(font_italic)

    # 渲染文字表面
    text_fmt = font.render(text, True, color)
    # 将文字绘制到屏幕
    screen.blit(text_fmt, pos)


def is_cell_free(idx, psize, psnake):
    """
    检查指定位置是否未被蛇身占据

    参数:
    idx: int - 要检查的位置索引
    psize: int - 当前蛇的长度
    psnake: list - 蛇身位置数组

    返回值:
    bool - True表示该位置空闲,False被蛇身占据
    """
    return idx not in psnake[:psize]


def is_move_possible(idx, move):
    """
    检查指定位置能否向给定方向移动

    参数:
    idx: int - 当前位置索引
    move: int - 移动方向(LEFT/RIGHT/UP/DOWN)

    返回值:
    bool - 是否可以移动
    """
    if move == LEFT:
        return idx % WIDTH > 1  # 不在最左列
    elif move == RIGHT:
        return idx % WIDTH < (WIDTH - 2)  # 不在最右列
    elif move == UP:
        return idx > (2 * WIDTH - 1)  # 不在最顶行
    elif move == DOWN:
        return idx < (FIELD_SIZE - 2 * WIDTH)  # 不在最底行


def board_reset(psnake, psize, pboard):
    """
    重置场地状态数组

    参数:
    psnake: list - 蛇身位置数组
    psize: int - 蛇当前长度
    pboard: list - 要重置的场地数组
    """
    for i in range(FIELD_SIZE):
        if i == food:
            pboard[i] = FOOD
        elif is_cell_free(i, psize, psnake):
            pboard[i] = UNDEFINED
        else:
            pboard[i] = SNAKE


def board_refresh(pfood, psnake, pboard):
    """
    使用BFS算法计算各位置到食物的最短路径

    参数:
    pfood: int - 食物位置索引
    psnake: list - 蛇身数组
    pboard: list - 场地状态数组

    返回值:
    bool - 是否找到从食物到蛇头的路径
    """
    queue = [pfood]  # BFS队列(从食物开始)
    inqueue = [0] * FIELD_SIZE
    found = False

    while queue:
        idx = queue.pop(0)
        if inqueue[idx]:
            continue
        inqueue[idx] = 1

        for move in mov:
            new_idx = idx + move
            # 检查移动是否合法
            if is_move_possible(idx, move):
                # 如果新位置是蛇头,标记找到路径
                if new_idx == psnake[HEAD]:
                    found = True
                # 更新路径长度
                if pboard[new_idx] < SNAKE:  # 排除蛇身位置
                    if pboard[new_idx] > pboard[idx] + 1:
                        pboard[new_idx] = pboard[idx] + 1
                    if not inqueue[new_idx]:
                        queue.append(new_idx)
    return found


def choose_shortest_safe_move(psnake, pboard):
    """
    选择离食物最近的合法移动方向

    参数:
    psnake: list - 蛇身数组
    pboard: list - 场地状态数组

    返回值:
    int - 最佳移动方向(LEFT/RIGHT/UP/DOWN)
    """
    best_move = ERR
    min_dist = SNAKE  # 初始设为最大值

    for move in mov:
        new_idx = psnake[HEAD] + move
        # 检查移动是否合法且路径更短
        if is_move_possible(psnake[HEAD], move) and pboard[new_idx] < min_dist:
            min_dist = pboard[new_idx]
            best_move = move
    return best_move


def choose_longest_safe_move(psnake, pboard):
    """
    选择离食物最远的合法移动方向(用于追尾策略)

    参数同上
    """
    best_move = ERR
    max_dist = -1

    for move in mov:
        new_idx = psnake[HEAD] + move
        if is_move_possible(psnake[HEAD], move) and pboard[new_idx] < UNDEFINED:
            if pboard[new_idx] > max_dist:
                max_dist = pboard[new_idx]
                best_move = move
    return best_move


def is_tail_inside():
    """
    检查虚拟移动后蛇尾是否可达(防止被困)

    使用临时数组进行计算,不影响主游戏状态
    """
    global tmpboard, tmpsnake, food, tmpsnake_size
    # 将临时蛇尾设为食物
    tmpboard[tmpsnake[tmpsnake_size - 1]] = FOOD
    tmpboard[food] = SNAKE  # 原食物位置设为蛇身

    # 计算路径
    result = board_refresh(tmpsnake[tmpsnake_size - 1], tmpsnake, tmpboard)

    # 还原临时修改
    tmpboard[tmpsnake[tmpsnake_size - 1]] = SNAKE
    # 检查是否紧邻蛇尾(长于3时不允许)
    for move in mov:
        if (tmpsnake[HEAD] + move == tmpsnake[tmpsnake_size - 1] and
                tmpsnake_size > 3 and
                is_move_possible(tmpsnake[HEAD], move)):
            result = False
    return result


def follow_tail():
    """
    让蛇头朝着蛇尾移动(防被困策略)
    """
    global tmpboard, tmpsnake, tmpsnake_size
    tmpsnake_size = snake_size
    tmpsnake = snake.copy()

    # 在临时场地模拟追尾
    board_reset(tmpsnake, tmpsnake_size, tmpboard)
    tmpboard[tmpsnake[-1]] = FOOD  # 蛇尾设为食物
    tmpboard[food] = SNAKE  # 食物位置设为蛇身
    board_refresh(tmpsnake[-1], tmpsnake, tmpboard)
    return choose_longest_safe_move(tmpsnake, tmpboard)


def any_possible_move():
    """
    当所有策略失败时,随机选择一个合法移动方向
    """
    board_reset(snake, snake_size, board)
    board_refresh(food, snake, board)

    best_move = ERR
    min_dist = SNAKE
    for move in mov:
        new_idx = snake[HEAD] + move
        if is_move_possible(snake[HEAD], move) and board[new_idx] < min_dist:
            min_dist = board[new_idx]
            best_move = move
    return best_move


def shift_array(arr, size):
    """
    将数组元素向右移位(模拟蛇移动)

    参数:
    arr: list - 要移位的数组
    size: int - 有效数据长度
    """
    for i in range(size, 0, -1):
        arr[i] = arr[i - 1]


def new_food():
    """
    随机生成新的食物位置
    """
    global food
    while True:
        # 生成随机行列(避开边界)
        row = randint(1, HEIGHT - 2)
        col = randint(1, WIDTH - 2)
        new_food = row * WIDTH + col
        # 检查位置是否空闲
        if is_cell_free(new_food, snake_size, snake):
            food = new_food
            break


def make_move(pbest_move):
    """
    执行实际的移动操作

    参数:
    pbest_move: int - 要执行的移动方向
    """
    global snake, board, snake_size, score
    # 移动蛇身
    shift_array(snake, snake_size)
    snake[HEAD] += pbest_move

    # 吃到食物的处理
    if snake[HEAD] == food:
        board[snake[HEAD]] = SNAKE
        snake_size += 1
        score += 1
        if snake_size < FIELD_SIZE:
            new_food()
    else:
        board[snake[HEAD]] = SNAKE
        board[snake[snake_size]] = UNDEFINED  # 清除旧蛇尾


def virtual_shortest_move():
    """
    虚拟移动(用于路径预测)
    """
    global tmpsnake, tmpboard, tmpsnake_size
    tmpsnake = snake.copy()
    tmpsnake_size = snake_size
    tmpboard = board.copy()

    food_eated = False
    while not food_eated:
        board_refresh(food, tmpsnake, tmpboard)
        move = choose_shortest_safe_move(tmpsnake, tmpboard)
        shift_array(tmpsnake, tmpsnake_size)
        tmpsnake[HEAD] += move

        if tmpsnake[HEAD] == food:
            tmpsnake_size += 1
            board_reset(tmpsnake, tmpsnake_size, tmpboard)
            tmpboard[food] = SNAKE
            food_eated = True
        else:
            tmpboard[tmpsnake[HEAD]] = SNAKE
            tmpboard[tmpsnake[tmpsnake_size]] = UNDEFINED


def find_safe_way():
    """
    综合策略寻找安全路径
    """
    if board_refresh(food, snake, board):
        # 优先使用最短路径
        virtual_shortest_move()
        if is_tail_inside():
            return choose_shortest_safe_move(snake, board)
    # 次选追尾策略
    return follow_tail()


def main():
    """
    主游戏循环
    """
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_X, SCREEN_Y))
    pygame.display.set_caption('AI-Snake By aalibuhuilagan')
    clock = pygame.time.Clock()
    isdead = False

    # 加载背景图(需要背景.png存在)
    try:
        bg = pygame.image.load("背景.png").convert()
        bg = pygame.transform.scale(bg, (SCREEN_X, SCREEN_Y))
    except:
        bg = None

    while True:
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE and isdead:
                return main()  # 重新开始

        # 画面绘制
        screen.fill((255, 255, 255)) if not bg else screen.blit(bg, (0, 0))

        # 绘制蛇身(根据长度调整线条连接方式)
        if snake_size == 1:
            # 单节蛇特殊处理
            pygame.draw.circle(screen, (136, 0, 21),
                               ((snake[0] // WIDTH) * 25 + 12, (snake[0] % WIDTH) * 25 + 12), 10)
        else:
            # 多节蛇用线条连接
            points = [((pos // WIDTH) * 25 + 12, (pos % WIDTH) * 25 + 12) for pos in snake[:snake_size]]
            pygame.draw.lines(screen, (136, 0, 21), False, points, 20)

        # 绘制食物
        pygame.draw.rect(screen, (20, 220, 39),
                         ((food // WIDTH) * 25, (food % WIDTH) * 25, 20, 20))

        # 游戏逻辑更新
        if not isdead:
            board_reset(snake, snake_size, board)
            if board_refresh(food, snake, board):
                best_move = find_safe_way()
            else:
                best_move = follow_tail()

            best_move = best_move if best_move != ERR else any_possible_move()

            if best_move != ERR:
                make_move(best_move)
            else:
                isdead = True  # 游戏结束

        # 显示游戏状态
        if isdead:
            show_text(screen, (100, 200), 'Game Over!', (227, 29, 18), font_size=100)
            show_text(screen, (150, 260), 'Try again!', (0, 0, 22), font_size=30)
        show_text(screen, (50, 500), f'Score: {score}', (255, 255, 255))

        pygame.display.update()
        clock.tick(20)  # 控制游戏帧率


if __name__ == '__main__':
    main()

​

效果展示:

参数详解

参数含义取值范围作用
HEIGHT场地高度(y轴方向格子数)25定义游戏场地的高度
WIDTH场地宽度(x轴方向格子数)25定义游戏场地的宽度
SCREEN_X屏幕宽度HEIGHT * 25计算实际屏幕像素宽度
SCREEN_Y屏幕高度WIDTH * 25计算实际屏幕像素高度
FIELD_SIZE场地总格子数HEIGHT * WIDTH存储场地的总格子数量
HEAD蛇头在snake数组中的索引位置0表示蛇头总是数组的第一个元素
FOOD食物标识0表示食物的位置
UNDEFINED未定义状态(路径长度初始值)(HEIGHT + 1) * (WIDTH + 1)用于路径计算的初始值
SNAKE蛇身标识2 * UNDEFINED表示蛇身的位置
LEFT向左移动(索引-1)-1表示向左移动的方向
RIGHT向右移动(索引+1)1表示向右移动的方向
UP向上移动(跨过WIDTH个元素)-WIDTH表示向上的移动方向
DOWN向下移动WIDTH表示向下的移动方向
ERR错误移动方向-1111表示非法的移动方向
board主场地状态数组[0] * FIELD_SIZE存储场地的状态信息
snake蛇身位置数组[0] * (FIELD_SIZE + 1)存储蛇身的位置信息
snake_size蛇的长度1初始化蛇的长度
tmpboard临时场地状态[0] * FIELD_SIZE用于模拟移动时的场地状态
tmpsnake临时蛇身数组[0] * (FIELD_SIZE + 1)用于模拟移动时的蛇身状态
tmpsnake_size临时蛇长度1临时蛇的长度
food食物位置(第3行第3列)3 * WIDTH + 3初始化食物的位置
best_move当前最佳移动方向ERR初始化最佳移动方向
mov移动方向数组[LEFT, RIGHT, UP, DOWN]定义可用的移动方向

三、Python五子棋

项目结构与思路

  • 界面设计 :创建游戏窗口,绘制棋盘网格,为黑白棋子设计不同的颜色和形状表示。

  • 游戏状态管理 :维护当前玩家的回合状态、棋盘上各位置的落子情况,判断游戏是否结束(即是否有玩家连成五子)。

  • 事件处理 :捕捉鼠标的点击事件,确定玩家在棋盘上的落子位置,并更新游戏状态。

  • 胜利判断 :在每次落子后,检查该位置周围是否形成连续的五个相同棋子,以确定游戏的胜负。

关键知识点

  • 二维数组应用 :用二维数组表示棋盘,数组元素存储对应位置的棋子状态(无子、黑子、白子),方便对棋盘状态进行操作和判断。

  • 鼠标事件处理 :获取鼠标点击的坐标,转换为棋盘上的行、列索引,实现精确的落子操作。

  • 逻辑判断 :围绕落子位置进行多方向(横向、纵向、对角线)的连续棋子判断,这需要对数组的遍历和条件判断有清晰的理解和巧妙的实现。

源代码

from tkinter.messagebox import *  # 导入消息弹出库
from tkinter import *  # 导入tkinter界面库
from random import *  # 导入random随机库
from PIL import Image, ImageTk  # 导入PIL包中的Image包和ImageTk包,用于打开图片,用作背景(可更换图片)
import winsound  # 导入声音库
import os

# 声明全局变量开始
global canvas
global back_x, back_y, last
global qipan
qipan = [[2 for i in range(16)] for i in range(16)]  # 2表示空,0表示蓝棋,1表示粉棋
col = ['蓝', '粉']
global index  # 创建落子计数变量
index = 0
global var_top
global hui
hui = 0
# 声明全局变量结束

long = 100
dtmf = [697, 770, 852, 941, 1209, 1336, 1477, 1633]
fequency = [440, 494, 554, 587, 659, 740, 831, 880]


# winsound.PlaySound("start.wav", 0)  # 播放启动音效


# 函数开始
def another_color(color):
    return 1 - color


def is_win(color):  # 判赢函数
    global qipan
    global index
    global canvas
    kuan = 16
    i = j = 0
    # 横排
    for i in range(kuan):
        for j in range(kuan - 4):
            if (qipan[i][j] == color and qipan[i][j + 1] == color and qipan[i][j + 2] == color and qipan[i][
                j + 3] == color and qipan[i][j + 4] == color):
                return 1
    # 竖排
    for i in range(kuan - 4):
        for j in range(kuan):
            if (qipan[i][j] == color and qipan[i + 1][j] == color and qipan[i + 2][j] == color and qipan[i + 3][
                j] == color and qipan[i + 4][j] == color):
                return 1
    # 副对角线
    for i in range(kuan - 4):
        for j in range(4, kuan):
            if (qipan[i][j] == color and qipan[i + 1][j - 1] == color and qipan[i + 2][j - 2] == color and qipan[i + 3][
                j - 3] == color and qipan[i + 4][j - 4] == color):
                return 1
    # 主对角线
    for i in range(kuan - 4):
        for j in range(kuan - 4):
            if (qipan[i][j] == color and qipan[i + 1][j + 1] == color and qipan[i + 2][j + 2] == color and qipan[i + 3][
                j + 3] == color and qipan[i + 4][j + 4] == color):
                return 1
    return 0  # 定义判定胜负函数


def is_kongde(x, y):  # 判空函数
    if (qipan[x][y] == 2):
        return 1
    else:
        return 0


def AI(colo):
    global qipan
    global index
    global canvas
    color = ['cyan', 'pink']
    kuan = 16
    initial_value = 2
    # 1--己方四连A--黑黑黑黑空
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i][j + 1] == colo and qipan[i][j + 2] == colo and qipan[i][
                j + 3] == colo and qipan[i][j + 4] == initial_value):
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 1
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == colo and qipan[i + 1][j] == colo and qipan[i + 2][j] == colo and qipan[i + 3][
                j] == colo and qipan[i + 4][j] == initial_value):
                i = i + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 1
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == colo and qipan[i + 1][j - 1] == colo and qipan[i + 2][j - 2] == colo and qipan[i + 3][
                j - 3] == colo and qipan[i + 4][j - 4] == initial_value):
                i = i + 4
                j = j - 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 1
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i + 1][j + 1] == colo and qipan[i + 2][j + 2] == colo and qipan[i + 3][
                j + 3] == colo and qipan[i + 4][j + 4] == initial_value):
                i = i + 4
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 1

    # 2--己方四连B--空黑黑黑黑
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i][j + 1] == colo and qipan[i][j + 2] == colo and qipan[i][
                j + 3] == colo and qipan[i][j + 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 2
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j] == colo and qipan[i + 2][j] == colo and qipan[i + 3][
                j] == colo and qipan[i + 4][j] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 2
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j - 1] == colo and qipan[i + 2][j - 2] == colo and
                    qipan[i + 3][j - 3] == colo and qipan[i + 4][j - 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 2
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i + 1][j + 1] == colo and qipan[i + 2][j + 2] == colo and
                    qipan[i + 3][j + 3] == colo and qipan[i + 4][j + 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 2

    # 3--对方四连A--空白白白白
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i][j + 1] == another_color(colo) and qipan[i][
                j + 2] == another_color(colo) and qipan[i][j + 3] == another_color(colo) and qipan[i][
                j + 4] == another_color(colo)):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 3
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j] == another_color(colo) and qipan[i + 2][
                j] == another_color(colo) and qipan[i + 3][j] == another_color(colo) and qipan[i + 4][
                j] == another_color(colo)):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 3
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j - 1] == another_color(colo) and qipan[i + 2][
                j - 2] == another_color(colo) and qipan[i + 3][j - 3] == another_color(colo) and qipan[i + 4][
                j - 4] == another_color(colo)):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 3
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i + 1][j + 1] == another_color(colo) and qipan[i + 2][
                j + 2] == another_color(colo) and qipan[i + 3][j + 3] == another_color(colo) and qipan[i + 4][
                j + 4] == another_color(colo)):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 3

    # 4--对方四连B--白白白白空
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == another_color(colo) and qipan[i][j + 1] == another_color(colo) and qipan[i][
                j + 2] == another_color(colo) and qipan[i][j + 3] == another_color(colo) and qipan[i][
                j + 4] == initial_value):
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 4
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == another_color(colo) and qipan[i + 1][j] == another_color(colo) and qipan[i + 2][
                j] == another_color(colo) and qipan[i + 3][j] == another_color(colo) and qipan[i + 4][
                j] == initial_value):
                i = i + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 4
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == another_color(colo) and qipan[i + 1][j - 1] == another_color(colo) and qipan[i + 2][
                j - 2] == another_color(colo) and qipan[i + 3][j - 3] == another_color(colo) and qipan[i + 4][
                j - 4] == initial_value):
                i = i + 4
                j = j - 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 4
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == another_color(colo) and qipan[i + 1][j + 1] == another_color(colo) and qipan[i + 2][
                j + 2] == another_color(colo) and qipan[i + 3][j + 3] == another_color(colo) and qipan[i + 4][
                j + 4] == initial_value):
                i = i + 4
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 3

    # 5--对方三连A--空白白白空
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i][j + 1] == another_color(colo) and qipan[i][
                j + 2] == another_color(colo) and qipan[i][j + 3] == another_color(colo) and qipan[i][
                j + 4] == initial_value):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j] == another_color(colo) and qipan[i + 2][
                j] == another_color(colo) and qipan[i + 3][j] == another_color(colo) and qipan[i + 4][
                j] == initial_value):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j - 1] == another_color(colo) and qipan[i + 2][
                j - 2] == another_color(colo) and qipan[i + 3][j - 3] == another_color(colo) and qipan[i + 4][
                j - 4] == initial_value):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i + 1][j + 1] == another_color(colo) and qipan[i + 2][
                j + 2] == another_color(colo) and qipan[i + 3][j + 3] == another_color(colo) and qipan[i + 4][
                j + 4] == initial_value):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5

    # 6--对方三连B--空白白白黑
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i][j + 1] == another_color(colo) and qipan[i][
                j + 2] == another_color(colo) and qipan[i][j + 3] == another_color(colo) and qipan[i][j + 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 6
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j] == another_color(colo) and qipan[i + 2][
                j] == another_color(colo) and qipan[i + 3][j] == another_color(colo) and qipan[i + 4][j] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 6
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == initial_value and qipan[i + 1][j - 1] == another_color(colo) and qipan[i + 2][
                j - 2] == another_color(colo) and qipan[i + 3][j - 3] == another_color(colo) and qipan[i + 4][
                j - 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 6
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == initial_value and qipan[i + 1][j + 1] == another_color(colo) and qipan[i + 2][
                j + 2] == another_color(colo) and qipan[i + 3][j + 3] == another_color(colo) and qipan[i + 4][
                j + 4] == colo):
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 6

    # 7--对方三连C--黑白白白空
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i][j + 1] == another_color(colo) and qipan[i][j + 2] == another_color(
                    colo) and qipan[i][j + 3] == another_color(colo) and qipan[i][j + 4] == initial_value):
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == colo and qipan[i + 1][j] == another_color(colo) and qipan[i + 2][j] == another_color(
                    colo) and qipan[i + 3][j] == another_color(colo) and qipan[i + 4][j] == initial_value):
                i = i + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == colo and qipan[i + 1][j - 1] == another_color(colo) and qipan[i + 2][
                j - 2] == another_color(colo) and qipan[i + 3][j - 3] == another_color(colo) and qipan[i + 4][
                j - 4] == initial_value):
                i = i + 4
                j = j - 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i + 1][j + 1] == another_color(colo) and qipan[i + 2][
                j + 2] == another_color(colo) and qipan[i + 3][j + 3] == another_color(colo) and qipan[i + 4][
                j + 4] == initial_value):
                i = i + 4
                j = j + 4
                qipan[i][j] = colo
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
                return 5

    # 8--己方三连
    # 横排
    for i in range(16):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i][j + 1] == colo and qipan[i][j + 2] == colo and qipan[i][
                j + 3] == colo and qipan[i][j + 4] == colo):
                return 1
    # 竖排
    for i in range(12):
        for j in range(16):
            if (qipan[i][j] == colo and qipan[i + 1][j] == colo and qipan[i + 2][j] == colo and qipan[i + 3][
                j] == colo and qipan[i + 4][j] == colo):
                return 1
    # 副对角线
    for i in range(12):
        for j in range(4, 16):
            if (qipan[i][j] == colo and qipan[i + 1][j - 1] == colo and qipan[i + 2][j - 2] == colo and qipan[i + 3][
                j - 3] == colo and qipan[i + 4][j - 4] == colo):
                return 1
    # 主对角线
    for i in range(12):
        for j in range(12):
            if (qipan[i][j] == colo and qipan[i + 1][j + 1] == colo and qipan[i + 2][j + 2] == colo and qipan[i + 3][
                j + 3] == colo and qipan[i + 4][j + 4] == colo):
                return 1
    while (True):
        i = int(randint(0, 15))
        j = int(randint(0, 15))
        if (is_kongde(i, j) == 1):
            qipan[i][j] = colo
            canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='%s' % color[colo],
                               tags=('chess'))
            break
    index += 1
    if (is_win(0) == 1):  # 判断是否获胜,若获胜:弹窗退出游戏
        print('%s棋赢啦' % col[0])
        var_top.set('%s棋赢啦' % col[0])
        showinfo('%s棋赢啦' % col[0], '%s棋赢啦!' % col[0])
        os._exit(0)
    return 0


def loser():  # 定义认输操作
    col = ['粉', '蓝']
    global on_hit
    global index
    global var_top

    if (AI_mode == True):
        var_top.set('人方认输!')
        showinfo('人方认输!', '人方认输!')
        os._exit(0)

    index % 2 == 1
    var_top.set('%s方认输!' % col[index % 2])
    showinfo('%s方认输!' % col[index % 2], '%s方认输!' % col[index % 2])

    os._exit(0)


def play_AI(event):  # 定义落子操作
    global back_x, back_y, last
    global index
    global hui
    color = ['cyan', 'pink']
    col = ['蓝', '粉']
    x = event.x  # 获取鼠标点击坐标
    y = event.y
    y = int((y + 15) / 30) * 30  # 利用鼠标点击坐标计算出落子坐标
    x = int((x + 15) / 30) * 30
    if (is_kongde(int((x + 15) / 30), int((y + 15) / 30)) == 1):
        index = index + 1
        last = color[1]  # 将棋子颜色存入全局变量“last”,用于悔棋操作
        fequency = [440, 494, 554, 587, 659, 740, 831, 880]  # 定义音阶频率
        winsound.Beep(fequency[index % 8], 300)  # 发出落子音效
        canvas.create_oval(x - 10, y - 10, x + 10, y + 10, fill='%s' % color[1], tags=('chess'))
        var_top.set('请粉棋落子')
        global qipan  # 声明全局数组变量“qipan”
        qipan[int((x + 15) / 30)][int((y + 15) / 30)] = 1  # 将落子情况存入数组“qipan”
        back_x = int((x + 15) / 30)  # 将棋子坐标存入全局变量“a”,“b”,用于悔棋操作
        back_y = int((y + 15) / 30)
        if (is_win(1) == 1):  # 判断是否获胜,若获胜:弹窗退出游戏
            print('%s棋赢啦' % col[1])
            var_top.set('%s棋赢啦' % col[1])
            showinfo('%s棋赢啦' % col[1], '%s棋赢啦!' % col[1])
            os._exit(0)
        if (hui == 0):
            AI(0)
            if (is_win(0) == 1):  # 判断是否获胜,若获胜:弹窗退出游戏
                print('%s棋赢啦' % col[0])
                var_top.set('%s棋赢啦' % col[0])
                showinfo('%s棋赢啦' % col[0], '%s棋赢啦!' % col[0])
                os._exit(0)
        else:
            hui = 0
    return 0


def play(event):  # 定义落子操作
    global back_x, back_y, last
    color = ['cyan', 'pink']
    col = ['蓝', '粉']
    global index
    x = event.x  # 获取鼠标点击坐标
    y = event.y
    y = int((y + 15) / 30) * 30  # 利用鼠标点击坐标计算出落子坐标
    x = int((x + 15) / 30) * 30
    if (is_kongde(int((x + 15) / 30), int((y + 15) / 30)) == 1):
        index = index + 1
        last = color[index % 2]  # 将棋子颜色存入全局变量“last”,用于悔棋操作
        fequency = [440, 494, 554, 587, 659, 740, 831, 880]  # 定义音阶频率
        winsound.Beep(fequency[index % 8], 300)  # 发出落子音效
        canvas.create_oval(x - 10, y - 10, x + 10, y + 10, fill='%s' % color[index % 2], tags=('chess'))
        var_top.set('请%s棋落子' % col[(1 + index) % 2])
        global qipan  # 声明全局数组变量“qipan”
        qipan[int((x + 15) / 30)][int((y + 15) / 30)] = index % 2  # 将落子情况存入数组“qipan”
        back_x = int((x + 15) / 30)  # 将棋子坐标存入全局变量“a”,“b”,用于悔棋操作
        back_y = int((y + 15) / 30)
        if (is_win(index % 2) == 1):  # 判断是否获胜,若获胜:弹窗退出游戏
            print('%s棋赢啦' % col[index % 2])
            var_top.set('%s棋赢啦' % col[index % 2])
            showinfo('%s棋赢啦' % col[index % 2], '%s棋赢啦!' % col[index % 2])
            os._exit(0)
    return 0


def goback():
    global canvas
    global index
    global back_x, back_y
    global hui
    canvas.delete('chess')  # 删除界面中所有棋子图案
    var = StringVar()  # 设置文字变量储存器
    label = Label(root, textvariable=var, bg='cyan', width=15, height=2)  # 使用 textvariable 替换 text, 因为这个可以变化
    var.set('%s方悔棋' % last)
    label.pack()

    qipan[back_x][back_y] = 2  # 在全局数组变量“qipan”中,将悔棋位置标记为空白
    for i in range(16):  # 根据全局数组变量“qipan”中的数据,重新绘制所有棋子
        for j in range(16):
            if (qipan[i][j] == 0):
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='cyan',
                                   tags=('chess'))
            elif (qipan[i][j] == 1):
                canvas.create_oval((i * 30) - 10, (j * 30) - 10, (i * 30) + 10, (j * 30) + 10, fill='pink',
                                   tags=('chess'))
    index = index + 1  # 更新落子计数器
    if (AI_mode == True):
        index += 1
    hui = 1
    return 0


# 创建窗口
root = Tk()  # 定义界面root
root.title('五子棋')
screenheight = root.winfo_screenheight()  # 获取当前屏幕高度
screenwidth = root.winfo_screenwidth()  # 获取当前屏幕宽度
width = 600  # 画布的宽
height = 600  # 画布的高
x = (screenwidth - width) // 2
y = (screenheight - height) // 2
root.geometry('%dx%d+%d+%d' % (width, height, x, y))  # 将界面放在屏幕中央

img_width = 450
img_height = 450
# 画布,棋盘描绘

canvas = Canvas(root, width=img_width, height=img_height)  # 将画布放在界面中铺满界面
image = Image.open("lss.png")  # 打开背景图片(可替换)
image.thumbnail((img_width, img_height))  # 将背景缩放为画布大小
image_file = ImageTk.PhotoImage(image)  # 将背景放在画布中
canvas.create_image(225, 225, anchor='center', image=image_file)  # 将图片导入画布
i = 30
while i < 450:  # 绘制棋盘网格
    canvas.create_line(i, 0, i, 450)
    canvas.create_line(0, i, 450, i)
    i = i + 30
var_top = StringVar()  # 设置文字变量储存器

label = Label(root, textvariable=var_top, font=('黑体', 12, 'bold'), fg='white', bg='red', width=15,
              height=2)  # 使用 textvariable 替换 text, 因为这个可以变化
label.pack()
var_top.set('请%s棋落子' % col[(1 + index) % 2])

AI_mode = askyesno('游戏模式', '是否进入人机对抗模式?')  # 返回并保存弹窗返回值'True'or'False'
if (AI_mode == False):
    canvas.bind("<Button-1>", play)  # 绑定鼠标左键,并调用落子操作
elif (AI_mode == True):
    canvas.bind("<Button-1>", play_AI)
canvas.pack()

button_exit = Button(root, text='投降', width=15, height=2, command=loser)  # 定义“投降”按钮
button_exit.pack()
button_undo = Button(root, text='悔棋', width=15, height=2, command=goback)  # 定义“悔棋”按钮
button_undo.pack()

root.mainloop()  # 进入到事件(消息)循环

效果展示

参数详解

参数含义取值范围作用
HEIGHT场地高度(y轴方向格子数)25定义游戏场地的高度
WIDTH场地宽度(x轴方向格子数)25定义游戏场地的宽度
SCREEN_X屏幕宽度HEIGHT * 25计算实际屏幕像素宽度
SCREEN_Y屏幕高度WIDTH * 25计算实际屏幕像素高度
FIELD_SIZE场地总格子数HEIGHT * WIDTH存储场地的总格子数量
HEAD蛇头在snake数组中的索引位置0表示蛇头总是数组的第一个元素
FOOD食物标识0表示食物的位置
UNDEFINED未定义状态(路径长度初始值)(HEIGHT + 1) * (WIDTH + 1)用于路径计算的初始值
SNAKE蛇身标识2 * UNDEFINED表示蛇身的位置
LEFT向左移动(索引-1)-1表示向左移动的方向
RIGHT向右移动(索引+1)1表示向右移动的方向
UP向上移动(跨过WIDTH个元素)-WIDTH表示向上的移动方向
DOWN向下移动WIDTH表示向下的移动方向
ERR错误移动方向-1111表示非法的移动方向
board主场地状态数组[0] * FIELD_SIZE存储场地的状态信息
snake蛇身位置数组[0] * (FIELD_SIZE + 1)存储蛇身的位置信息
snake_size蛇的长度1初始化蛇的长度
tmpboard临时场地状态[0] * FIELD_SIZE用于模拟移动时的场地状态
tmpsnake临时蛇身数组[0] * (FIELD_SIZE + 1)用于模拟移动时的蛇身状态
tmpsnake_size临时蛇长度1临时蛇的长度
food食物位置(第3行第3列)3 * WIDTH + 3初始化食物的位置
best_move当前最佳移动方向ERR初始化最佳移动方向
mov移动方向数组[LEFT, RIGHT, UP, DOWN]定义可用的移动方向

四、植物大战僵尸

项目结构与思路

  • 资源准备 :收集植物、僵尸、背景等图像资源,以及背景音乐、音效等音频资源,为游戏的丰富表现力奠定基础。

  • 游戏场景搭建 :创建游戏窗口,绘制游戏背景,包括天空、地面、房屋等元素,营造出游戏的整体氛围。

  • 植物与僵尸的实现 :定义植物和僵尸的类,包含它们的属性(如生命值、攻击力、阳光消耗等)和行为(如植物的攻击、生长,僵尸的移动、攻击等);通过实例化对象,在游戏场景中进行布局和交互。

  • 游戏流程控制 :管理游戏的开始、进行、结束等阶段,控制僵尸的生成频率、植物的种植限制(基于阳光数量),以及检测游戏的胜利(消灭所有僵尸)或失败(僵尸到达房屋)条件。

关键知识点

  • 面向对象编程 :充分运用类和对象的概念,将植物、僵尸等游戏元素封装成不同的类,每个类都有其独特的属性和方法,便于代码的组织和维护,也使得游戏逻辑更加清晰。

  • 图像与音频处理 :加载和显示图像资源,实现游戏中的各种视觉效果;播放背景音乐和音效,增强游戏的沉浸感,这涉及到pygame中相关模块的使用,如image、mixer等。

  • 多元素交互与碰撞检测 :处理植物与僵尸之间的攻击、碰撞等交互行为,需要精确地判断它们的位置关系和接触时刻,运用到坐标计算、矩形碰撞检测等技术。

源代码

# 1 引入需要的模块
import pygame
import random

# 1 配置图片地址
IMAGE_PATH = 'imgs/'
# 1 设置页面宽高
scrrr_width = 800
scrrr_height = 560
# 1 创建控制游戏结束的状态
GAMEOVER = False
# 4 图片加载报错处理
LOG = '文件:{}中的方法:{}出错'.format(__file__, __name__)


# 3 创建地图类
class Map():
    # 3 存储两张不同颜色的图片名称
    map_names_list = [IMAGE_PATH + 'map1.png', IMAGE_PATH + 'map2.png']

    # 3 初始化地图
    def __init__(self, x, y, img_index):
        self.image = pygame.image.load(Map.map_names_list[img_index])
        self.position = (x, y)
        # 是否能够种植
        self.can_grow = True

    # 3 加载地图
    def load_map(self):
        MainGame.window.blit(self.image, self.position)


# 4 植物类
class Plant(pygame.sprite.Sprite):
    def __init__(self):
        super(Plant, self).__init__()
        self.live = True

    # 加载图片
    def load_image(self):
        if hasattr(self, 'image') and hasattr(self, 'rect'):
            MainGame.window.blit(self.image, self.rect)
        else:
            print(LOG)


# 5 向日葵类
class Sunflower(Plant):
    def __init__(self, x, y):
        super(Sunflower, self).__init__()
        self.image = pygame.image.load('imgs/sunflower.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.price = 50
        self.hp = 100
        # 5 时间计数器
        self.time_count = 0

    # 5 新增功能:生成阳光
    def produce_money(self):
        self.time_count += 1
        if self.time_count == 25:
            MainGame.money += 5
            self.time_count = 0

    # 5 向日葵加入到窗口中
    def display_sunflower(self):
        MainGame.window.blit(self.image, self.rect)


# 6 豌豆射手类
class PeaShooter(Plant):
    def __init__(self, x, y):
        super(PeaShooter, self).__init__()
        # self.image 为一个 surface
        self.image = pygame.image.load('imgs/peashooter.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.price = 50
        self.hp = 200
        # 6 发射计数器
        self.shot_count = 0

    # 6 增加射击方法
    def shot(self):
        # 6 记录是否应该射击
        should_fire = False
        for zombie in MainGame.zombie_list:
            if zombie.rect.y == self.rect.y and zombie.rect.x < 800 and zombie.rect.x > self.rect.x:
                should_fire = True
        # 6 如果活着
        if self.live and should_fire:
            self.shot_count += 1
            # 6 计数器到25发射一次
            if self.shot_count == 25:
                # 6 基于当前豌豆射手的位置,创建子弹
                peabullet = PeaBullet(self)
                # 6 将子弹存储到子弹列表中
                MainGame.peabullet_list.append(peabullet)
                self.shot_count = 0

    # 6 将豌豆射手加入到窗口中的方法
    def display_peashooter(self):
        MainGame.window.blit(self.image, self.rect)


# 7 豌豆子弹类
class PeaBullet(pygame.sprite.Sprite):
    def __init__(self, peashooter):
        self.live = True
        self.image = pygame.image.load('imgs/peabullet.png')
        self.damage = 50
        self.speed = 10
        self.rect = self.image.get_rect()
        self.rect.x = peashooter.rect.x + 60
        self.rect.y = peashooter.rect.y + 15

    def move_bullet(self):
        # 7 在屏幕范围内,实现往右移动
        if self.rect.x < scrrr_width:
            self.rect.x += self.speed
        else:
            self.live = False

    # 7 新增,子弹与僵尸的碰撞
    def hit_zombie(self):
        for zombie in MainGame.zombie_list:
            if pygame.sprite.collide_rect(self, zombie):
                # 打中僵尸之后,修改子弹的状态,
                self.live = False
                # 僵尸掉血
                zombie.hp -= self.damage
                if zombie.hp <= 0:
                    zombie.live = False
                    self.nextLevel()

    # 7闯关方法
    def nextLevel(self):
        MainGame.score += 20
        MainGame.remnant_score -= 20
        for i in range(1, 100):
            if MainGame.score == 100 * i and MainGame.remnant_score == 0:
                MainGame.remnant_score = 100 * i
                MainGame.shaoguan += 1
                MainGame.produce_zombie += 50

    def display_peabullet(self):
        MainGame.window.blit(self.image, self.rect)


# 9 僵尸类
class Zombie(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Zombie, self).__init__()
        self.image = pygame.image.load('imgs/zombie.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.hp = 1000
        self.damage = 2
        self.speed = 1
        self.live = True
        self.stop = False

    # 9 僵尸的移动
    def move_zombie(self):
        if self.live and not self.stop:
            self.rect.x -= self.speed
            if self.rect.x < -80:
                # 8 调用游戏结束方法
                MainGame().gameOver()

    # 9 判断僵尸是否碰撞到植物,如果碰撞,调用攻击植物的方法
    def hit_plant(self):
        for plant in MainGame.plants_list:
            if pygame.sprite.collide_rect(self, plant):
                # 8  僵尸移动状态的修改
                self.stop = True
                self.eat_plant(plant)

    # 9 僵尸攻击植物
    def eat_plant(self, plant):
        # 9 植物生命值减少
        plant.hp -= self.damage
        # 9 植物死亡后的状态修改,以及地图状态的修改
        if plant.hp <= 0:
            a = plant.rect.y // 80 - 1
            b = plant.rect.x // 80
            map = MainGame.map_list[a][b]
            map.can_grow = True
            plant.live = False
            # 8 修改僵尸的移动状态
            self.stop = False

    # 9 将僵尸加载到地图中
    def display_zombie(self):
        MainGame.window.blit(self.image, self.rect)


# 1 主程序
class MainGame():
    # 2 创建关数,得分,剩余分数,钱数
    shaoguan = 1
    score = 0
    remnant_score = 100
    money = 200
    # 3 存储所有地图坐标点
    map_points_list = []
    # 3 存储所有的地图块
    map_list = []
    # 4 存储所有植物的列表
    plants_list = []
    # 7 存储所有豌豆子弹的列表
    peabullet_list = []
    # 9 新增存储所有僵尸的列表
    zombie_list = []
    count_zombie = 0
    produce_zombie = 100

    # 1 加载游戏窗口
    def init_window(self):
        # 1 调用显示模块的初始化
        pygame.display.init()
        # 1 创建窗口
        MainGame.window = pygame.display.set_mode([scrrr_width, scrrr_height])

    # 2 文本绘制
    def draw_text(self, content, size, color):
        pygame.font.init()
        font = pygame.font.SysFont('kaiti', size)
        text = font.render(content, True, color)
        return text

    # 2 加载帮助提示
    def load_help_text(self):
        text1 = self.draw_text('1.鼠标左键创建向日葵 2.鼠标右键创建豌豆射手', 26, (0, 0, 0))
        MainGame.window.blit(text1, (5, 5))

    # 3 初始化坐标点
    def init_plant_points(self):
        for y in range(1, 7):
            points = []
            for x in range(10):
                point = (x, y)
                points.append(point)
            MainGame.map_points_list.append(points)
            print("MainGame.map_points_list", MainGame.map_points_list)

    # 3 初始化地图
    def init_map(self):
        for points in MainGame.map_points_list:
            temp_map_list = list()
            for point in points:
                # map = None
                if (point[0] + point[1]) % 2 == 0:
                    map = Map(point[0] * 80, point[1] * 80, 0)
                else:
                    map = Map(point[0] * 80, point[1] * 80, 1)
                # 将地图块加入到窗口中
                temp_map_list.append(map)
                print("temp_map_list", temp_map_list)
            MainGame.map_list.append(temp_map_list)
        print("MainGame.map_list", MainGame.map_list)

    # 3 将地图加载到窗口中
    def load_map(self):
        for temp_map_list in MainGame.map_list:
            for map in temp_map_list:
                map.load_map()

    # 6 增加豌豆射手发射处理
    def load_plants(self):
        for plant in MainGame.plants_list:
            # 6 优化加载植物的处理逻辑
            if plant.live:
                if isinstance(plant, Sunflower):
                    plant.display_sunflower()
                    plant.produce_money()
                elif isinstance(plant, PeaShooter):
                    plant.display_peashooter()
                    plant.shot()
            else:
                MainGame.plants_list.remove(plant)

    # 7 加载所有子弹的方法
    def load_peabullets(self):
        for b in MainGame.peabullet_list:
            if b.live:
                b.display_peabullet()
                b.move_bullet()
                # v1.9 调用子弹是否打中僵尸的方法
                b.hit_zombie()
            else:
                MainGame.peabullet_list.remove(b)

    # 8事件处理

    def deal_events(self):
        # 8 获取所有事件
        eventList = pygame.event.get()
        # 8 遍历事件列表,判断
        for e in eventList:
            if e.type == pygame.QUIT:
                self.gameOver()
            elif e.type == pygame.MOUSEBUTTONDOWN:
                # print('按下鼠标按键')
                print(e.pos)
                # print(e.button)#左键1  按下滚轮2 上转滚轮为4 下转滚轮为5  右键 3

                x = e.pos[0] // 80
                y = e.pos[1] // 80
                print(x, y)
                map = MainGame.map_list[y - 1][x]
                print(map.position)
                # 8 增加创建时候的地图装填判断以及金钱判断
                if e.button == 1:
                    if map.can_grow and MainGame.money >= 50:
                        sunflower = Sunflower(map.position[0], map.position[1])
                        MainGame.plants_list.append(sunflower)
                        print('当前植物列表长度:{}'.format(len(MainGame.plants_list)))
                        map.can_grow = False
                        MainGame.money -= 50
                elif e.button == 3:
                    if map.can_grow and MainGame.money >= 50:
                        peashooter = PeaShooter(map.position[0], map.position[1])
                        MainGame.plants_list.append(peashooter)
                        print('当前植物列表长度:{}'.format(len(MainGame.plants_list)))
                        map.can_grow = False
                        MainGame.money -= 50

    # 9 新增初始化僵尸的方法
    def init_zombies(self):
        for i in range(1, 7):
            dis = random.randint(1, 5) * 200
            zombie = Zombie(800 + dis, i * 80)
            MainGame.zombie_list.append(zombie)

    # 9将所有僵尸加载到地图中
    def load_zombies(self):
        for zombie in MainGame.zombie_list:
            if zombie.live:
                zombie.display_zombie()
                zombie.move_zombie()
                # v2.0 调用是否碰撞到植物的方法
                zombie.hit_plant()
            else:
                MainGame.zombie_list.remove(zombie)

    # 1 开始游戏
    def start_game(self):
        # 1 初始化窗口
        self.init_window()
        # 3 初始化坐标和地图
        self.init_plant_points()
        self.init_map()
        # 9 调用初始化僵尸的方法
        self.init_zombies()
        # 1 只要游戏没结束,就一直循环
        while not GAMEOVER:
            # 1 渲染白色背景
            MainGame.window.fill((255, 255, 255))
            # 2 渲染的文字和坐标位置
            MainGame.window.blit(self.draw_text('当前钱数$: {}'.format(MainGame.money), 26, (0, 0, 0)), (500, 40))
            MainGame.window.blit(self.draw_text(
                '当前关卡{},得分{},距离下关还差{}分'.format(MainGame.shaoguan, MainGame.score, MainGame.remnant_score),
                26,
                (0, 0, 0)), (5, 40))
            self.load_help_text()

            # 3 需要反复加载地图
            self.load_map()
            # 6 调用加载植物的方法
            self.load_plants()
            # 7  调用加载所有子弹的方法
            self.load_peabullets()
            # 8 调用事件处理的方法
            self.deal_events()
            # 9 调用展示僵尸的方法
            self.load_zombies()
            # 9 计数器增长,每数到100,调用初始化僵尸的方法
            MainGame.count_zombie += 1
            if MainGame.count_zombie == MainGame.produce_zombie:
                self.init_zombies()
                MainGame.count_zombie = 0
            # 9 pygame自己的休眠
            pygame.time.wait(10)
            # 1 实时更新
            pygame.display.update()

    # 10 程序结束方法
    def gameOver(self):
        MainGame.window.blit(self.draw_text('游戏结束', 50, (255, 0, 0)), (300, 200))
        print('游戏结束')
        pygame.time.wait(400)
        global GAMEOVER
        GAMEOVER = True


# 1 启动主程序
if __name__ == '__main__':
    game = MainGame()
    game.start_game()

效果展示

参数详解

参数含义取值范围作用
IMAGE_PATH图片路径'imgs/'存储游戏所需图片资源的路径
scrrr_width屏幕宽度800设置游戏窗口的宽度
scrrr_height屏幕高度560设置游戏窗口的高度
GAMEOVER游戏结束状态False控制游戏是否结束
LOG日志信息'文件:{}中的方法:{}出错'.format(filename)用于记录错误信息
map_names_list地图图片名称列表[IMAGE_PATH + 'map1.png', IMAGE_PATH + 'map2.png']存储两种地图图片的路径
image地图图片pygame.image.load(Map.map_names_list[img_index])加载对应的地图图片
position地图位置(x, y)地图在游戏窗口中的位置
can_grow是否可以种植True表示该地图位置是否可以种植植物
live植物存活状态True表示植物是否存活
price植物价格50创建植物所需的阳光值
hp植物生命值100或200植物的耐久度,不同植物生命值不同
time_count时间计数器0用于记录时间,实现生成阳光等功能
shot_count射击计数器0用于记录射击的间隔时间
damage子弹伤害值50子弹对僵尸造成的伤害量
speed子弹速度10子弹在屏幕上的移动速度
rect矩形区域self.image.get_rect()获取图像的矩形区域,用于碰撞检测等
count_zombie僵尸计数器0用于控制僵尸的生成频率
produce_zombie僵尸生成间隔100每达到一定数值,生成新的僵尸
shaoguan关卡数1当前游戏的关卡
score得分0玩家当前的得分
remnant_score剩余分数100达到下关所需的分数
money金钱200玩家当前拥有的阳光值
map_points_list地图坐标点列表[]存储所有地图块的坐标点
map_list地图块列表[]存储所有的地图块对象
plants_list植物列表[]存储所有植物对象
peabullet_list豌豆子弹列表[]存储所有豌豆子弹对象
zombie_list僵尸列表[]

五、项目总结

   本次我们深入探讨了三个基于Pygame的实战项目:AI贪吃蛇、Python五子棋和植物大战僵尸。每个项目都展示了Pygame在不同游戏类型开发中的强大功能,同时也体现了编程思维的多样性和灵活性。

AI贪吃蛇

   这个项目通过AI算法让贪吃蛇具备了智能行为,能够自动规划路径追逐食物。我们学习了如何使用BFS算法进行路径搜索,以及如何将复杂的AI逻辑与游戏的图形界面相结合。这个项目让我们看到了算法在游戏开发中的实际应用,也理解了如何通过调整参数和逻辑来优化游戏体验。

Python五子棋

   五子棋项目则更侧重于游戏逻辑的实现,特别是如何判断五子连珠的胜利条件。我们深入研究了二维数组的操作,以及如何通过鼠标事件实现玩家的交互。这个项目展示了如何用简单的规则创造出一个具有策略性的游戏,同时也让我们掌握了事件驱动编程的基本思路。

植物大战僵尸

   作为最复杂的项目,植物大战僵尸融合了资源管理、即时战略和角色扮演等多种元素。我们学习了如何通过面向对象编程管理游戏中的各种实体(植物、僵尸、子弹等),以及如何实现它们之间的交互和碰撞检测。这个项目不仅展示了Pygame在处理复杂游戏场景中的能力,还让我们理解了如何设计和维护一个具有多个系统的游戏。

总结

    通过这三个项目,我们不仅掌握了Pygame的基本使用方法,如窗口创建、事件处理、图像加载与绘制等,还深入学习了如何将数据结构(如数组、列表)、算法(如路径搜索)和面向对象编程思想应用到实际的游戏开发中。每个项目都有其独特的技术亮点和挑战,从简单的贪吃蛇到复杂的植物大战僵尸,我们逐步提升了编程能力和问题解决能力。

    这些项目为我们提供了宝贵的实践经验,无论是对Pygame的深入理解,还是对游戏开发整体流程的把握,都为未来开发更高级、更复杂的游戏奠定了坚实的基础。希望这些项目能激发大家对游戏开发的热情,鼓励大家继续探索和创新,创造出更多精彩的作品!资源绑定含有全部项目文件,请感兴趣的读者自行下载。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值