20244107 实验四《Python程序设计》 实验报告

20244107 2024-2025-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2441
姓名: 唐近仁
学号:20244107
实验教师:王志强
实验日期:2025年5月31日
必修/选修: 专选课
1.实验要求
(1)Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
(2)综合考虑我的兴趣爱好等,选择编写小游戏坦克大战。
2.分析设计
(1)编写准备:通过对《零基础学python》中13.1初识pygame章节的学习,以及在网络上广泛的收集相关资料,我掌握了初步的相关知识以及设计思路。
(2)总体思路:基于Python的Pygame库开发坦克大战小游戏,实现多关卡地图、双人模式、道具系统、碰撞检测等功能。通过面向对象编程设计游戏精灵(坦克、子弹、障碍物等),结合事件驱动机制处理用户输入与游戏逻辑,最终实现完整的游戏流程。
(3)核心功能:
支持单人 / 双人模式选择,玩家操控坦克移动、射击。
设计多关卡地图,包含砖墙、铁墙、河流、树木等障碍物。
实现敌方坦克生成、路径寻优及碰撞检测机制。
添加道具系统(如子弹强化、无敌护盾、增加生命等)。
设计开始界面、关卡切换界面及游戏结束界面。
(4)具体实现想法:
双人模式:通过is_dual_mode标志区分单双人,玩家 2 使用 WSAD 键控制。
敌方坦克生成:
每关初始生成固定数量敌方坦克,超出MAXENEMYNUM时暂停生成。
利用random模块随机选择出生点,避免与障碍物重叠。
碰撞检测:
子弹与障碍物 / 坦克碰撞时触发销毁与爆炸效果。
使用pygame.sprite.spritecollide检测坦克与道具、敌方与大本营的碰撞。
道具系统:随机生成道具(如clock= 加速,protect= 无敌),玩家拾取后触发对应效果(通过定时器实现限时增益)。
(5)设计模块:
cfg.py:存储游戏配置(路径、屏幕尺寸、素材路径等),即配置文件。
modules/sprites:定义坦克、子弹、道具等精灵类。
modules/levels:存储各关卡地图数据(文本格式)。
GameLevel.py:处理关卡逻辑(敌方生成、碰撞检测、胜利条件)。
3.实验过程
(1)安装pygame插件:在命令控制中,先下达命令:
如图,然后再输入pip install pycharm完成安装
接下来,检测一下pygame是否安装成功。打开IDLE,输入如下命令:import pygame,运行结果如图所示,说明安装成功
(2)素材准备
在项目根目录创建resources文件夹,按类型分层存储素材,结构如下:
resources/
├─ audios/ # 音效文件(.wav格式)
│ ├─ add.wav # 拾取道具音效
│ ├─ bang.wav # 爆炸音效
│ └─ fire.wav # 射击音效
├─ images/ # 图片素材(.png格式,部分支持透明通道)
│ ├─ background.png # 游戏背景图
│ ├─ bullet/ # 子弹图片
│ │ ├─ bullet_up.png
│ │ └─ bullet_left.png
│ ├─ enemyTank/ # 敌方坦克图片(不同等级/方向)
│ │ ├─ enemy_1_0.png # 敌方坦克1-上
│ │ └─ enemy_2_3.png # 敌方坦克2-右
│ ├─ playerTank/ # 玩家坦克图片
│ ├─ food/ # 道具图片
│ └─ scene/ # 场景障碍物图片(砖墙、铁墙等)
└─ font/ # 字体文件(.ttf格式)
└─ font.ttf # 用于显示游戏面板文字
(3)游戏原理
主要分为图形界面渲染、事件驱动处理和游戏逻辑控制三部分,综合运用面向对象编程与精灵系统实现复杂交互。
游戏初始化(实现以下三个功能)
①设置游戏窗口screen = pygame.display.set_mode((cfg.WIDTH, cfg.HEIGHT)) # 创建630x630像素窗口 pygame.display.set_caption(cfg.TITLE) # 设置标题
②创建游戏时钟
clock = pygame.time.Clock() # 控制帧率
③创建精灵与精灵组

player_tanks_group = pygame.sprite.Group()  # 玩家坦克组  
enemy_tanks_group = pygame.sprite.Group()  # 敌方坦克组  

游戏循环(对应 5 大功能)

while is_running:  
    clock.tick(60)  # 设置刷新频率为60FPS  
    self.__event_handler()  # 事件监听  
    self.__check_collide()  # 碰撞检测  
    self.__update_sprites()  # 更新绘制精灵组  
    pygame.display.update()  # 更新屏幕显示  

关键功能实现
① 背景地图渲染
原理:通过两张背景图循环滚动模拟场景移动,利用精灵组管理位置更新。

# 背景精灵(简化版)  
class Background(pygame.sprite.Sprite):  
    def __init__(self, image_path):  
        self.image = pygame.image.load(image_path)  
        self.rect = self.image.get_rect()  
    def update(self):  
        self.rect.y += 2  # 向下滚动速度 
        if self.rect.top >= cfg.HEIGHT:  # 循环重置  
            self.rect.y = -self.rect.height  
# 主代码调用  
background_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS['background'])  
screen.blit(background_img, (0, 0))  # 绘制背景  

② 敌方坦克生成与移动
原理:通过定时器周期性生成敌方坦克,随机位置与方向实现动态效果。


# 定时器设置(每20秒生成新敌人)  
generate_enemies_event = pygame.USEREVENT  
pygame.time.set_timer(generate_enemies_event, 20000)  
# 敌方坦克初始化(随机位置)  
for position in self.enemy_tank_positions:  
    enemy_tanks_group.add(EnemyTank(position=position))  # 从关卡文件读取出生点  

③ 碰撞检测系统
子弹与障碍物碰撞:

# 砖墙可被摧毁(双方销毁)  
pygame.sprite.groupcollide(player_bullets_group, self.scene_elems['brick_group'], True, True)  

敌方坦克与玩家碰撞:

enemies = pygame.sprite.spritecollide(player_tank, enemy_tanks_group, True)  
if len(enemies) > 0:  
    player_tank.kill()  # 玩家坦克销毁  

④ 玩家操作控制
键盘事件监听:

key_pressed = pygame.key.get_pressed()  
if key_pressed[pygame.K_w]:  # 玩家1上移  
    player_tank.move('up')  
if self.is_dual_mode and key_pressed[pygame.K_UP]:  # 玩家2上移(双人模式)  
    player2_tank.move('up')  

子弹发射逻辑:

bullet = player_tank.shoot()  # 发射子弹  
player_bullets_group.add(bullet)  

⑤ 关卡与资源管理
关卡文件解析:

def __parseLevelFile(self):  
    with open(self.levelfilepath) as f:  
        for line in f:  
            if line.startswith('%TOTALENEMYNUM'):  
                self.total_enemy_num = int(line.split(':')[-1])  # 解析总敌人数  

素材路径配置:

# cfg.py中定义素材路径  
BULLET_IMAGE_PATHS = {  
    'up': 'resources/images/bullet/bullet_up.png',  
    # 其他方向子弹路径...  
}  

(4)代码展现

以下是完整的主代码:

'''
Function:
    坦克大战小游戏
Author:
 god,it's brutal out here!!
'''
import os
import cfg
import pygame
from modules import *


'''主函数'''
def main(cfg):
    # 游戏初始化
    pygame.init()
    pygame.mixer.init()
    screen = pygame.display.set_mode((cfg.WIDTH, cfg.HEIGHT))
    pygame.display.set_caption(cfg.TITLE)
    # 加载游戏素材
    sounds = {}
    for key, value in cfg.AUDIO_PATHS.items():
        sounds[key] = pygame.mixer.Sound(value)
        sounds[key].set_volume(1)
    # 开始界面
    is_dual_mode = gameStartInterface(screen, cfg)
    # 关卡数
    levelfilepaths = [os.path.join(cfg.LEVELFILEDIR, filename) for filename in sorted(os.listdir(cfg.LEVELFILEDIR))]
    # 主循环
    for idx, levelfilepath in enumerate(levelfilepaths):
        switchLevelIterface(screen, cfg, idx+1)
        game_level = GameLevel(idx+1, levelfilepath, sounds, is_dual_mode, cfg)
        is_win = game_level.start(screen)
        if not is_win: break
    is_quit_game = gameEndIterface(screen, cfg, is_win)
    return is_quit_game


'''run'''
if __name__ == '__main__':
    while True:
        is_quit_game = main(cfg)
        if is_quit_game:
            break

以下是完整的配置文件代码:

'''配置文件'''
import os

'''字体'''
FONTPATH = os.path.join(os.getcwd(), 'resources/font/font.ttf')
BULLET_IMAGE_PATHS = {
    'up': os.path.join(os.getcwd(), 'resources/images/bullet/bullet_up.png'),
    'down': os.path.join(os.getcwd(), 'resources/images/bullet/bullet_down.png'),
    'left': os.path.join(os.getcwd(), 'resources/images/bullet/bullet_left.png'),
    'right': os.path.join(os.getcwd(), 'resources/images/bullet/bullet_right.png')
}
ENEMY_TANK_IMAGE_PATHS = {
    '1': [
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_1_0.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_1_1.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_1_2.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_1_3.png')
    ],
    '2': [
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_2_0.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_2_1.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_2_2.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_2_3.png')
    ],
    '3': [
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_3_0.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_3_1.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_3_2.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_3_3.png')
    ],
    '4': [
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_4_0.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_4_1.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_4_2.png'),
        os.path.join(os.getcwd(), 'resources/images/enemyTank/enemy_4_3.png')
    ]
}
PLAYER_TANK_IMAGE_PATHS = {
    'player1': [
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T1_0.png'),
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T1_1.png'),
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T1_2.png')
    ],
    'player2': [
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T2_0.png'),
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T2_1.png'),
        os.path.join(os.getcwd(), 'resources/images/playerTank/tank_T2_2.png')
    ]
}
FOOD_IMAGE_PATHS = {
    'boom': os.path.join(os.getcwd(), 'resources/images/food/food_boom.png'),
    'clock': os.path.join(os.getcwd(), 'resources/images/food/food_clock.png'),
    'gun': os.path.join(os.getcwd(), 'resources/images/food/food_gun.png'),
    'iron': os.path.join(os.getcwd(), 'resources/images/food/food_iron.png'),
    'protect': os.path.join(os.getcwd(), 'resources/images/food/food_protect.png'),
    'star': os.path.join(os.getcwd(), 'resources/images/food/food_star.png'),
    'tank': os.path.join(os.getcwd(), 'resources/images/food/food_tank.png')
}
HOME_IMAGE_PATHS = [
    os.path.join(os.getcwd(), 'resources/images/home/home1.png'),
    os.path.join(os.getcwd(), 'resources/images/home/home_destroyed.png')
]
SCENE_IMAGE_PATHS = {
    'brick': os.path.join(os.getcwd(), 'resources/images/scene/brick.png'),
    'ice': os.path.join(os.getcwd(), 'resources/images/scene/ice.png'),
    'iron': os.path.join(os.getcwd(), 'resources/images/scene/iron.png'),
    'river1': os.path.join(os.getcwd(), 'resources/images/scene/river1.png'),
    'river2': os.path.join(os.getcwd(), 'resources/images/scene/river2.png'),
    'tree': os.path.join(os.getcwd(), 'resources/images/scene/tree.png')
}
OTHER_IMAGE_PATHS = {
    'appear': os.path.join(os.getcwd(), 'resources/images/others/appear.png'),
    'background': os.path.join(os.getcwd(), 'resources/images/others/background.png'),
    'boom_dynamic': os.path.join(os.getcwd(), 'resources/images/others/boom_dynamic.png'),
    'boom_static': os.path.join(os.getcwd(), 'resources/images/others/boom_static.png'),
    'gameover': os.path.join(os.getcwd(), 'resources/images/others/gameover.png'),
    'logo': os.path.join(os.getcwd(), 'resources/images/others/logo.png'),
    'mask': os.path.join(os.getcwd(), 'resources/images/others/mask.png'),
    'protect': os.path.join(os.getcwd(), 'resources/images/others/protect.png'),
    'tip': os.path.join(os.getcwd(), 'resources/images/others/tip.png'),
    'gamebar': os.path.join(os.getcwd(), 'resources/images/others/gamebar.png')
}
'''声音'''
AUDIO_PATHS = {
    'add': os.path.join(os.getcwd(), 'resources/audios/add.wav'),
    'bang': os.path.join(os.getcwd(), 'resources/audios/bang.wav'),
    'blast': os.path.join(os.getcwd(), 'resources/audios/blast.wav'),
    'fire': os.path.join(os.getcwd(), 'resources/audios/fire.wav'),
    'Gunfire': os.path.join(os.getcwd(), 'resources/audios/Gunfire.wav'),
    'hit': os.path.join(os.getcwd(), 'resources/audios/hit.wav'),
    'start': os.path.join(os.getcwd(), 'resources/audios/start.wav')
}
'''屏幕'''
WIDTH = 630
HEIGHT = 630
BORDER_LEN = 3
GRID_SIZE = 24
PANEL_WIDTH = 150
TITLE = '坦克大战 —— 杏仁大王'
'''关卡'''
LEVELFILEDIR = os.path.join(os.getcwd(), 'modules/levels')

以下是关卡逻辑核心代码:

def start(self, screen):  
    # 精灵组初始化  
    player_tanks_group = pygame.sprite.Group()  
    enemy_tanks_group = pygame.sprite.Group()  
    # 碰撞检测示例  
    pygame.sprite.groupcollide(player_bullets_group, self.scene_elems['brick_group'], True, True)  
    # 胜利/失败条件  
    if len(enemy_tanks_group) == 0 and self.total_enemy_num == 0: return True  

以下是界面交互模块:

def gameStartInterface(screen, cfg):  
    while True:  
        if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:  
            return is_dual_mode  # 返回单双人模式选择结果  

4.实验结果
可以正常运行,选择玩家数
可以正常进行游戏
以下是运行视频的链接:https://b23.tv/h1AwcRN

5. 实验过程中遇到的问题和解决过程

-问题1:安装pycharm板块时,输入“pip install pygame”指令后显示
-问题1解决方案:上网查询资料,发现是因为没有配置好环境变量,添加环境变量即可。
-问题2:双人模式按键冲突,玩家 2 按键时,玩家 1 坦克同时移动。
-问题2解决方案:查询资料并询问理科同学之后,了解到是使用pygame.key.get_pressed()获取按键状态时,未区分玩家输入。为玩家 2 单独设置按键映射(如玩家 1 用方向键,玩家 2 用 WSAD),在事件处理中分别判断即可。
-问题3:无法找到图片素材文件。
-问题3解决方案:图片文件夹路径错误,不小心在文件夹中又多嵌套了一个文件夹,修改过后成功运行。

6. 收获与感悟

这学期的 Python 课程,像一把精巧的钥匙,悄然为我开启了编程世界的奇妙大门。还记得初次接触时,连环境搭建都让我手忙脚乱,在命令行窗口反复输入指令,手忙脚乱。随着课程推进,从基础语法、数据类型,到控制结构、函数定义,再到面向对象编程、socket 编程、网络爬虫开发这些进阶内容,我在理论知识的海洋里不断遨游探索。老师授课时擅长化繁为简,讲解列表、字典等序列类型时,用一些贴近生活的案例,瞬间就让抽象的概念变得鲜活起来,我也很快掌握了不同数据结构的特点与适用场景。
在实践环节中,从简单的猜数字、猜拳游戏,到计算器制作,每一个项目都像是一场充满挑战的冒险。在编写猜数字游戏代码时,我反复琢磨如何设计游戏流程,怎样处理用户输入的各种情况,又该如何设置循环和结束条件。调试过程中,程序报错、逻辑混乱的状况时常出现,但正是在一次次修改代码、排除故障的过程中,我不仅巩固了知识,更学会了用编程思维拆解问题、解决问题。
Python 课程还极大地激发了我的探索精神。记得在 socket 编程实验中,程序始终无法建立连接,望着控制台的报错信息,我内心满是焦虑。但这次我没有轻易放弃,而是一头扎进技术论坛和官方文档,逐行分析代码逻辑,最终发现是端口号设置错误。当程序成功运行的那一刻,那份喜悦至今难忘。这个经历让我明白,每一个编程难题都是成长的阶梯,只要敢于探索尝试,就一定能找到解决办法。
编程对细节的严苛要求,也悄然改变着我的习惯。在 Python 编程中,一个标点符号的遗漏、缩进格式的错误,都可能让程序陷入 “瘫痪”。正是这种对精准度的极致追求,让我在日常学习和生活中也变得更加细致严谨,养成了反复检查、精益求精的做事风格。
如今回看,从最初连环境都搭建不好的编程小白,到能独立完成坦克大战这类小项目,尽管代码还有诸多不完善之处,但每一行代码的背后,都是成长的见证,都让我充满成就感。展望未来,我渴望在网络爬虫领域深入钻研,用 Python 实现数据的高效整理和文件的批量处理,让编程真正成为提升学习、工作效率的得力助手。感谢这门意义非凡的 Python 课程,它不仅教会我用代码解决实际问题,更赋予我紧跟时代技术浪潮的勇气与信心!

参考资料

posted @ 2025-06-02 16:14  唐近仁  阅读(113)  评论(0)    收藏  举报