一、项目概述
本次将带大家深入探索三个经典的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(file, name) | 用于记录错误信息 |
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的深入理解,还是对游戏开发整体流程的把握,都为未来开发更高级、更复杂的游戏奠定了坚实的基础。希望这些项目能激发大家对游戏开发的热情,鼓励大家继续探索和创新,创造出更多精彩的作品!资源绑定含有全部项目文件,请感兴趣的读者自行下载。