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

20254103 2025-2026-2 《Python程序设计》实验4报告

课程:《Python程序设计》
班级: 2541
姓名: 尉瀚文
学号:20254103
实验教师:王志强
实验日期:2026年5月26日
必修/选修: 专选课

一、实验内容

Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。

要求:
(1)程序能运行,功能丰富(至少5个功能)。(需求提交源代码,并建议录制程序运行的视频)
(2)综合实践报告,要体现实验分析、设计、实现过程、结果等信息,格式规范,逻辑清晰,结构合理。
(3)在实践报告中,需要对全课进行总结,并写课程感想体会、意见和建议等。

二、实验分析及设计

1、实验分析

       在最初选择要做的综合实验时,作为一个资深玩家(doge),小游戏成为了我的“无脑之选”,但在具体选择游戏时我却犯了难。其实我一开始纠结过该选什么类型的游戏,也有了不切实际的遐想,但考虑到pygame安装时容易出现环境问题,而turtle作为Python自带的标准库,不用额外配置,直接就能运行,对我这种小白更友好。“接球大作战”属于经典的休闲小游戏,规则简单好上手,同时又有很大的功能扩展空间,可以加道具、加关卡、加技能......可玩性很强,也能切实锻炼我的代码逻辑,所以最终选了它作为我的综合实践对象。

2、实验设计

       该游戏需要玩家通过键盘方向键或者鼠标来控制屏幕底部的挡板,接住从顶部随机掉落的球体来获得分数,同时要注意躲开红色炸弹球。游戏设置了简单、普通、困难三档难度,不同难度的初始生命值、球体下落速度、挡板长度和初始障碍物数量都不一样。

(1)得分规则:接到普通白球加10分,金色金币球加30分,连续接住球会触发连击倍数,得分翻倍;接到红色炸弹球会扣1条命,还会触发屏幕震动效果;每轮重置球体时按概率随机生成。

(2)胜负规则:得分到200分就算通关胜利;生命值扣光就游戏结束,点击屏幕可以重新开始。

(3)道具系统:会随机从顶部掉落共7种道具,有增益效果也有减益效果。增益道具有挡板变长、球速变慢、加一条命、奖励额外50分;减益道具有挡板变短、球速变快、额外扣除30分。道具效果都是限时的,会定时随机从顶部掉落,接住后触发对应时长的效果。

(4)关卡升级:每攒够50分自动升一级,背景颜色会变深,障碍物也会变多,球体下落的速度也会变快,难度慢慢往上增长。

(5)清屏技能:按空格键可以释放技能,把场上所有障碍物都清掉,还能让球短暂减速,技能有20秒冷却,左下角会显示冷却时间。

(6)多球分裂:每成功接住5个球,就会多分裂出来一个新球,场上最多同时有3个球,越玩越紧张,提升游戏的操作上限。

(7)连击得分:连续成功接球会提升连击倍数,得分按倍数翻倍计算;漏球或接到炸弹则重置连击,鼓励玩家保持稳定的操作精准度。

(8)动态障碍物:灰色障碍物可水平自主移动,碰到左右边界自动反弹;球体撞击障碍物会按碰撞轴反向弹开,增加游戏路径变数与操作难度。

(9)多操作方式:同时支持键盘方向键和鼠标移动控制挡板,玩家可自由选择习惯的操作方式;挡板具备边界限制,不会移出游戏窗口。

(10)接球闪光:接住球的瞬间挡板会闪变为黄色,快速恢复原色,提供明确的接球成功反馈。

(11)动态胜利动画:达成胜利条件后,结算文字颜色循环切换彩虹色,呈现庆祝动画。

三、实验过程及结果

(一)、实验过程

1、库导入模块      

导入turtle作为游戏核心图形引擎,承担窗口创建、图形绘制、事件监听与动画控制全部功能;导入random标准库提供随机数能力,支撑随机位置、速度、道具、特殊球等随机性玩法,二者均为Python内置库,无需额外安装。

屏幕截图 2026-06-16 103631

2、游戏窗口初始化模块

创建600×600固定尺寸的游戏主窗口,设置标题与黑色初始背景;核心优化是调用tracer(0)关闭自动重绘,改用手动帧刷新模式,从根源避免多元素同时移动时的画面闪烁,大幅提升游戏流畅度。

屏幕截图 2026-06-16 103651

3、游戏核心状态变量定义

(1)定义得分、生命、连击倍率、等级、游戏状态、难度六个核心维度变量,通过game_state字符串枚举实现开始、进行中、失败、胜利四态状态机,管控全游戏的流程分支。

屏幕截图 2026-06-16 103714

(2)效果计时器变量

采用帧计数机制管控所有时效类效果,分别对应挡板伸缩、小球变速、屏幕震动、技能冷却的剩余持续时长,每帧递减,归零后效果自动恢复,无需额外计时模块。

屏幕截图 2026-06-16 103725

(3)游戏平衡基础参数

存放游戏核心平衡基准值,包含小球基础下落速度、全局速度系数、挡板基础长度、接球计数、最大球数,所有动态难度、道具效果均基于这些参数缩放,保证数值体系统一。

屏幕截图 2026-06-16 103738

(4)实体管理列表

用三个列表分别存储障碍物、道具、小球三类动态实体,统一管理场景元素的增删、遍历更新与批量清理,是游戏实体调度的核心数据结构。

屏幕截图 2026-06-16 103758

(5)视觉配置常量

预定义两套配色方案,彩虹色列表用于胜利界面的闪烁庆祝动画,关卡背景色列表对应不同等级的背景切换,通过视觉变化直观体现游戏进度。

屏幕截图 2026-06-16 103814

4、玩家挡板实体初始化

创建玩家操控的挡板对象,基于基础方形横向拉伸为长条形状,抬笔避免移动留痕,初始固定在窗口底部中央,设置最快绘制速度保证操作响应无延迟。

屏幕截图 2026-06-16 103825

5、小球创建函数与初始小球实例

(1)小球创建封装函数

将小球创建逻辑封装为可复用函数,支持自定义位置与特殊类型,为小球附加速度、类型属性;通过颜色和尺寸区分普通白球、金色高分球、红色炸弹球,同时为多球分裂、重置场景提供统一创建入口。

屏幕截图 2026-06-16 103854

(2)初始小球实例化

游戏启动时生成第一个普通小球并加入管理列表,完成初始场景的核心实体搭建,让游戏开局即可进入玩法循环。

屏幕截图 2026-06-16 103912

 6、UI计分画笔初始化

创建专用的文字渲染画笔,与游戏实体画笔隔离,隐藏画笔本体,统一承担所有UI文字绘制工作,避免重复创建对象造成资源浪费。

屏幕截图 2026-06-16 103922

7、UI界面绘制函数模块

(1)计分板更新函数

统一管理实时UI刷新,绘制前清空旧内容避免重叠,顶部居中展示等级、分数、生命、连击核心数据,左下角同步技能冷却状态,所有数据变动后调用即可保证UI与状态实时同步。

屏幕截图 2026-06-16 103950

(2)开始菜单绘制函数

绘制游戏初始交互界面,顶部展示标题,中部通过固定坐标排布三个难度选项,配合后续点击检测实现“伪按钮”交互,底部补充操作说明与规则,降低新玩家学习成本。

屏幕截图 2026-06-16 104027

(3)游戏失败界面函数

生命值归零时调用,用红色醒目字体展示失败标题,同时呈现最终得分与等级,底部提示点击重开,给玩家清晰的结果反馈与后续操作指引。

屏幕截图 2026-06-16 174700

(4)胜利庆祝动画函数

通关后触发循环动画,通过索引取模逐帧切换文字颜色实现彩虹闪烁效果,同步展示成绩与重开提示,依托递归定时器实现无需额外线程的循环动画。

屏幕截图 2026-06-16 174801

8、障碍物系统模块

(1) 障碍物生成函数

支持批量生成灰色横向移动障碍物,随机分布在屏幕中部,每个障碍物赋予随机水平速度与方向,生成后加入统一列表管理,等级提升时调用新增障碍,实现难度渐进增长。

屏幕截图 2026-06-16 174831

(2)障碍物运动更新函数

每帧更新所有障碍物的水平位置,触碰窗口左右边界时反转速度,实现持续往返移动,持续干扰小球轨迹以提升接球难度。

屏幕截图 2026-06-16 174855

(3)-障碍物碰撞检测函数

实现小球与障碍物的物理碰撞反弹,通过横纵距离差判定碰撞,根据碰撞主方向反转对应速度轴模拟真实反弹,同时修正小球位置避免穿模卡顿,是游戏物理引擎的核心逻辑。

屏幕截图 2026-06-16 174926

9、道具系统模块

(1)道具生成函数

实现定时随机掉落道具的机制,包含4种增益与3种减益道具,通过加权随机保证增益占比更高,不同道具用颜色区分识别,依托递归定时器实现4-8秒间隔的自动生成,无需主循环额外判断。

 1 def spawn_powerup():
 2     if game_state != "playing":
 3         return
 4     # 正负道具混合
 5     pu_types = [
 6         "paddle_grow", "ball_slow", "extra_life", "bonus_score",  # 增益
 7         "paddle_shrink", "ball_fast", "minus_score"  # 减益
 8     ]
 9     weights = [3, 3, 1, 3, 2, 2, 2]
10     pu_type = random.choices(pu_types, weights=weights, k=1)[0]
11 
12     pu = turtle.Turtle()
13     pu.speed(0)
14     pu.shape("circle")
15     pu.penup()
16 
17     color_map = {
18         "paddle_grow": "blue",
19         "ball_slow": "cyan",
20         "extra_life": "pink",
21         "bonus_score": "gold",
22         "paddle_shrink": "purple",
23         "ball_fast": "orange",
24         "minus_score": "brown"
25     }
26     pu.color(color_map.get(pu_type, "white"))
27     pu.goto(random.randint(-250, 250), 280)
28     pu.type = pu_type
29     powerups.append(pu)
30 
31     win.ontimer(spawn_powerup, random.randint(4000, 8000))

(2)道具更新与碰撞处理函数

管控道具全生命周期,包含匀速下落、边界清理、碰撞检测、效果触发、时效管理五个环节,接住道具后触发对应效果,时效类道具到期自动恢复默认参数,保证游戏数值平衡。

 1 def update_powerups():
 2     global score, lives, paddle_grow_time, paddle_shrink_time
 3     global ball_slow_time, ball_fast_time, slow_factor, base_dy
 4 
 5     paddle_half_w = get_paddle_half_width()
 6 
 7     for pu in powerups[:]:
 8         pu.sety(pu.ycor() - 2.5)
 9 
10         if pu.ycor() < -280:
11             pu.hideturtle()
12             powerups.remove(pu)
13             continue
14 
15         if (pu.ycor() - 10 <= paddle.ycor() + 10) and \
16                 (paddle.xcor() - paddle_half_w < pu.xcor() < paddle.xcor() + paddle_half_w):
17 
18             if pu.type == "paddle_grow":
19                 paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len * 1.5)
20                 paddle_grow_time = 300
21             elif pu.type == "paddle_shrink":
22                 paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len * 0.6)
23                 paddle_shrink_time = 200
24             elif pu.type == "ball_slow":
25                 slow_factor = 0.6
26                 ball_slow_time = 180
27             elif pu.type == "ball_fast":
28                 slow_factor = 1.5
29                 ball_fast_time = 150
30             elif pu.type == "extra_life":
31                 lives += 1
32             elif pu.type == "bonus_score":
33                 score += 50
34             elif pu.type == "minus_score":
35                 score = max(0, score - 30)
36 
37             pu.hideturtle()
38             powerups.remove(pu)
39             update_scoreboard()
40 
41     # 效果计时
42     if paddle_grow_time > 0:
43         paddle_grow_time -= 1
44         if paddle_grow_time == 0:
45             paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)
46 
47     if paddle_shrink_time > 0:
48         paddle_shrink_time -= 1
49         if paddle_shrink_time == 0:
50             paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)
51 
52     if ball_slow_time > 0:
53         ball_slow_time -= 1
54         if ball_slow_time == 0:
55             slow_factor = 1
56 
57     if ball_fast_time > 0:
58         ball_fast_time -= 1
59         if ball_fast_time == 0:
60             slow_factor = 1

(3)挡板宽度计算辅助函数

统一计算挡板实际半宽像素值,作为所有碰撞检测与边界判断的尺寸依据,避免多处重复编写道具状态判断逻辑,减少代码冗余。

屏幕截图 2026-06-16 175606

10、小球重置与特殊球机制

封装小球重置逻辑,统一处理触底、接球、飞出顶部后的状态重置,随机生成新位置与速度避免玩家背板;内置10%金币球、8%炸弹球的随机机制,丰富游戏的风险收益玩法,速度同步适配当前难度与道具效果。

屏幕截图 2026-06-16 175644

11、视觉特效模块

(1)屏幕震动触发函数

对外提供统一的震动触发接口,设置5帧震动时长,在接球、接炸弹时调用,增强操作打击感与反馈强度。

屏幕截图 2026-06-16 175701

(2)震动效果应用函数

每帧通过偏移底层画布坐标实现屏幕震动,无需移动所有游戏元素,性能开销极低,震动结束后自动复位画布,避免永久偏移。

屏幕截图 2026-06-16 175720

12、技能与等级成长模块

(1)清屏技能函数

实现空格键触发的主动清屏技能,释放后清除全部障碍物并短暂减速所有小球,帮助玩家缓解高压局面;设置20秒冷却时间平衡技能强度,避免滥用破坏游戏平衡。

屏幕截图 2026-06-16 175742

(2)等级升级检测函数

每50分自动升一级,升级同步切换背景色、新增障碍物、提升小球下落速度,实现难度平滑渐进增长,同时设置速度下限避免难度过高导致游戏无法操作。

屏幕截图 2026-06-16 175757

13、游戏初始化与重置函数

是游戏状态流转的核心枢纽,负责开局与重开时的全量初始化:先清理上一局残留的所有实体,再重置全部数值与状态,根据选择的难度差异化配置参数,最后初始化场景元素并启动定时器,保证每局初始状态完全一致。

 1 def start_game():
 2     global score, lives, combo, level, game_state, base_dy
 3     global base_paddle_len, catch_count, skill_cd, slow_factor
 4     global paddle_grow_time, paddle_shrink_time, ball_slow_time, ball_fast_time
 5 
 6     # 清空对象
 7     for obs in obstacles:
 8         obs.hideturtle()
 9     obstacles.clear()
10     for pu in powerups:
11         pu.hideturtle()
12     powerups.clear()
13     for b in balls[1:]:
14         b.hideturtle()
15     balls.clear()
16 
17     # 重置状态
18     score = 0
19     lives = 3
20     combo = 1
21     level = 1
22     catch_count = 0
23     skill_cd = 0
24     slow_factor = 1
25     paddle_grow_time = 0
26     paddle_shrink_time = 0
27     ball_slow_time = 0
28     ball_fast_time = 0
29     win.bgcolor("black")
30 
31     # 难度参数
32     if difficulty == "easy":
33         lives = 5
34         base_dy = -2
35         base_paddle_len = 8
36         obs_count = 2
37     elif difficulty == "hard":
38         lives = 2
39         base_dy = -4
40         base_paddle_len = 4
41         obs_count = 4
42     else:
43         lives = 3
44         base_dy = -3
45         base_paddle_len = 6
46         obs_count = 3
47 
48     game_state = "playing"
49     paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)
50     paddle.goto(0, -250)
51 
52     # 初始化第一个球
53     b = create_ball()
54     balls.append(b)
55 
56     create_obstacles(obs_count)
57     update_scoreboard()
58     win.ontimer(spawn_powerup, random.randint(3000, 5000))
59     win.update()

14、玩家输入控制模块

(1)键盘右移控制函数

响应右方向键操作,控制挡板向右步进30像素,内置边界检测防止挡板移出窗口,非游戏状态下屏蔽操作避免逻辑混乱。

屏幕截图 2026-06-16 180031

(2)键盘左移控制函数

响应左方向键操作,逻辑与右移对称,控制挡板向左步进30像素,保持操作手感统一,同样带边界限制防止移出窗口。

屏幕截图 2026-06-16 180102

(3)鼠标跟随控制函数

实现鼠标实时跟随操控,将屏幕像素坐标转换为turtle画布坐标系,通过边界钳制保证挡板不超出窗口,操作比键盘更顺滑精准,适配不同玩家习惯。

屏幕截图 2026-06-16 180114

(4)鼠标点击交互函数

分状态处理全局鼠标点击,开始菜单下通过坐标区间判断玩家选择的难度并启动游戏,结算界面下点击任意位置直接重开,无需额外控件即可完成全流程鼠标交互。

屏幕截图 2026-06-16 180142

(5)全局事件绑定

将所有按键、鼠标事件与对应处理函数绑定,开启窗口事件监听,建立用户操作到游戏逻辑的完整映射,是交互系统生效的前提。

屏幕截图 2026-06-16 180205

15、核心游戏主循环函数

是游戏的运行心脏,每30毫秒执行一次,按顺序完成状态校验、技能冷却、场景更新、小球物理、碰撞判定、胜负处理、多球分裂、渐进提速等全部逻辑,最后手动刷新画面并递归调用自身,形成稳定的游戏主循环。

  1 # ---------- 核心游戏循环 ----------
  2 def update_game():
  3     global score, lives, combo, game_state, catch_count, skill_cd
  4 
  5     if game_state == "start" or game_state != "playing":
  6         win.ontimer(update_game, 50)
  7         return
  8 
  9     if skill_cd > 0:
 10         skill_cd -= 1
 11         if skill_cd % 30 == 0:
 12             update_scoreboard()
 13 
 14     apply_shake()
 15     update_obstacles()
 16     update_powerups()
 17     level_up_check()
 18 
 19     paddle_half_w = get_paddle_half_width()
 20 
 21     for b in balls[:]:
 22         b.setx(b.xcor() + b.dx)
 23         b.sety(b.ycor() + b.dy)
 24 
 25         # 左右墙反弹
 26         if b.xcor() > 290 or b.xcor() < -290:
 27             b.dx *= -1
 28 
 29         # 障碍物碰撞
 30         check_obstacle_collision(b)
 31 
 32         # 触底处理
 33         if b.ycor() < -280:
 34             if len(balls) > 1:
 35                 b.hideturtle()
 36                 balls.remove(b)
 37                 continue
 38 
 39             lives -= 1
 40             combo = 1
 41             update_scoreboard()
 42             if lives <= 0:
 43                 game_state = "gameover"
 44                 show_game_over()
 45             else:
 46                 reset_ball(b)
 47             continue
 48 
 49         # 挡板接球
 50         if (b.ycor() - 10 <= paddle.ycor() + 10) and \
 51                 (paddle.xcor() - paddle_half_w < b.xcor() < paddle.xcor() + paddle_half_w):
 52 
 53             # 特殊球处理
 54             if b.special == "bomb":
 55                 lives -= 1
 56                 screen_shake()
 57                 update_scoreboard()
 58                 if lives <= 0:
 59                     game_state = "gameover"
 60                     show_game_over()
 61                     break
 62                 reset_ball(b)
 63                 continue
 64             elif b.special == "gold":
 65                 add_score = 30 * combo
 66             else:
 67                 add_score = 10 * combo
 68 
 69             combo += 1
 70             score += add_score
 71             catch_count += 1
 72             screen_shake()
 73 
 74             # 接球闪光效果
 75             paddle.color("yellow")
 76             win.ontimer(lambda: paddle.color("green"), 100)
 77 
 78             update_scoreboard()
 79 
 80             # 胜利判定
 81             if score >= 200:
 82                 game_state = "win"
 83                 win_celebration()
 84                 break
 85 
 86             # 每接5个球分裂一个新球
 87             if catch_count % 5 == 0 and len(balls) < max_balls:
 88                 new_b = create_ball(b.xcor(), b.ycor())
 89                 new_b.dx = -b.dx
 90                 new_b.dy = b.dy
 91                 balls.append(new_b)
 92 
 93             reset_ball(b)
 94             # 全局缓慢提速
 95             global base_dy
 96             base_dy = max(base_dy - 0.1, -7)
 97 
 98         # 顶部保护
 99         if b.ycor() > 300:
100             reset_ball(b)
101 
102     win.update()
103     win.ontimer(update_game, 30)

16、程序启动入口

程序执行的入口逻辑,依次完成绘制初始菜单、启动游戏主循环、阻塞窗口保持运行三个步骤,保证程序启动后正常显示界面并等待用户交互,不会自动退出。

屏幕截图 2026-06-16 180401

(二)、实验结果

1、程序源代码

点击查看代码
import turtle
import random

# ---------- 窗口设置 ----------
win = turtle.Screen()
win.title("接球大作战·强化版")
win.bgcolor("black")
win.setup(width=600, height=600)
win.tracer(0)

# ---------- 游戏核心变量 ----------
score = 0
lives = 3
combo = 1
level = 1
game_state = "start"  # start / playing / gameover / win
difficulty = "normal"

# 效果计时器
paddle_grow_time = 0
paddle_shrink_time = 0
ball_slow_time = 0
ball_fast_time = 0
shake_time = 0
skill_cd = 0

# 基础参数
base_dy = -3
slow_factor = 1
base_paddle_len = 6
catch_count = 0
max_balls = 3

# 对象列表
obstacles = []
powerups = []
balls = []

# 庆祝动画
cele_colors = ["red", "yellow", "green", "blue", "purple", "orange", "cyan"]
cele_index = 0

# 关卡背景色
level_colors = ["black", "#1a1a2e", "#16213e", "#0f3460", "#533483", "#e94560"]

# ---------- 挡板 ----------
paddle = turtle.Turtle()
paddle.speed(0)
paddle.shape("square")
paddle.color("green")
paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)
paddle.penup()
paddle.goto(0, -250)


# ---------- 初始球 ----------
def create_ball(x=0, y=250, is_special=None):
    b = turtle.Turtle()
    b.speed(0)
    b.shape("circle")
    b.penup()
    b.goto(x, y)
    b.dx = random.choice([-1, -0.5, 0.5, 1])
    b.dy = base_dy * slow_factor
    b.special = is_special  # None普通 / gold金币 / bomb炸弹

    if is_special == "gold":
        b.color("gold")
    elif is_special == "bomb":
        b.color("red")
        b.shapesize(stretch_wid=1.2, stretch_len=1.2)
    else:
        b.color("white")
    return b


ball = create_ball()
balls.append(ball)

# ---------- 计分画笔 ----------
pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle()


# ---------- 辅助功能函数 ----------
def update_scoreboard():
    pen.color("white")
    pen.clear()
    pen.goto(0, 260)
    pen.write(f"Lv.{level}  Score: {score}  Lives: {lives}  x{combo}",
              align="center", font=("Courier", 18, "normal"))
    pen.goto(-280, -280)
    cd_text = f"技能[空格]: 就绪" if skill_cd <= 0 else f"技能冷却: {skill_cd // 30}s"
    pen.write(cd_text, align="left", font=("Courier", 12, "normal"))


def draw_start_menu():
    pen.color("white")
    pen.clear()
    pen.goto(0, 150)
    pen.write("接球大作战·强化版", align="center", font=("Courier", 32, "bold"))
    pen.goto(0, 80)
    pen.write("选择难度开始游戏", align="center", font=("Courier", 18, "normal"))

    pen.goto(-150, 20)
    pen.write("【简单】", align="center", font=("Courier", 20, "normal"))
    pen.goto(0, 20)
    pen.write("【普通】", align="center", font=("Courier", 20, "normal"))
    pen.goto(150, 20)
    pen.write("【困难】", align="center", font=("Courier", 20, "normal"))

    pen.goto(0, -40)
    pen.write("←→方向键 / 鼠标移动挡板", align="center", font=("Courier", 14, "normal"))
    pen.goto(0, -70)
    pen.write("空格释放清屏技能 · 金色球高分 · 红色球扣命", align="center", font=("Courier", 12, "normal"))


def create_obstacles(count):
    for _ in range(count):
        obs = turtle.Turtle()
        obs.speed(0)
        obs.shape("square")
        obs.color("gray")
        obs.shapesize(stretch_wid=1, stretch_len=2)
        obs.penup()
        x = random.randint(-250, 250)
        y = random.randint(-150, 150)
        obs.goto(x, y)
        obs.dx = random.choice([-0.8, -0.5, 0.5, 0.8])  # 移动速度
        obstacles.append(obs)


def spawn_powerup():
    if game_state != "playing":
        return
    # 正负道具混合
    pu_types = [
        "paddle_grow", "ball_slow", "extra_life", "bonus_score",  # 增益
        "paddle_shrink", "ball_fast", "minus_score"  # 减益
    ]
    weights = [3, 3, 1, 3, 2, 2, 2]
    pu_type = random.choices(pu_types, weights=weights, k=1)[0]

    pu = turtle.Turtle()
    pu.speed(0)
    pu.shape("circle")
    pu.penup()

    color_map = {
        "paddle_grow": "blue",
        "ball_slow": "cyan",
        "extra_life": "pink",
        "bonus_score": "gold",
        "paddle_shrink": "purple",
        "ball_fast": "orange",
        "minus_score": "brown"
    }
    pu.color(color_map.get(pu_type, "white"))
    pu.goto(random.randint(-250, 250), 280)
    pu.type = pu_type
    powerups.append(pu)

    win.ontimer(spawn_powerup, random.randint(4000, 8000))


def update_powerups():
    global score, lives, paddle_grow_time, paddle_shrink_time
    global ball_slow_time, ball_fast_time, slow_factor, base_dy

    paddle_half_w = get_paddle_half_width()

    for pu in powerups[:]:
        pu.sety(pu.ycor() - 2.5)

        if pu.ycor() < -280:
            pu.hideturtle()
            powerups.remove(pu)
            continue

        if (pu.ycor() - 10 <= paddle.ycor() + 10) and \
                (paddle.xcor() - paddle_half_w < pu.xcor() < paddle.xcor() + paddle_half_w):

            if pu.type == "paddle_grow":
                paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len * 1.5)
                paddle_grow_time = 300
            elif pu.type == "paddle_shrink":
                paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len * 0.6)
                paddle_shrink_time = 200
            elif pu.type == "ball_slow":
                slow_factor = 0.6
                ball_slow_time = 180
            elif pu.type == "ball_fast":
                slow_factor = 1.5
                ball_fast_time = 150
            elif pu.type == "extra_life":
                lives += 1
            elif pu.type == "bonus_score":
                score += 50
            elif pu.type == "minus_score":
                score = max(0, score - 30)

            pu.hideturtle()
            powerups.remove(pu)
            update_scoreboard()

    # 效果计时
    if paddle_grow_time > 0:
        paddle_grow_time -= 1
        if paddle_grow_time == 0:
            paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)

    if paddle_shrink_time > 0:
        paddle_shrink_time -= 1
        if paddle_shrink_time == 0:
            paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)

    if ball_slow_time > 0:
        ball_slow_time -= 1
        if ball_slow_time == 0:
            slow_factor = 1

    if ball_fast_time > 0:
        ball_fast_time -= 1
        if ball_fast_time == 0:
            slow_factor = 1


def get_paddle_half_width():
    if paddle_grow_time > 0:
        return base_paddle_len * 15
    elif paddle_shrink_time > 0:
        return base_paddle_len * 6
    return base_paddle_len * 10


def update_obstacles():
    for obs in obstacles:
        obs.setx(obs.xcor() + obs.dx)
        # 左右碰壁反弹
        if obs.xcor() > 280 or obs.xcor() < -280:
            obs.dx *= -1


def check_obstacle_collision(b):
    obs_w, obs_h = 20, 10
    ball_r = 10

    for obs in obstacles:
        dx = abs(b.xcor() - obs.xcor())
        dy = abs(b.ycor() - obs.ycor())

        if dx < obs_w + ball_r and dy < obs_h + ball_r:
            if dx / (obs_w + ball_r) > dy / (obs_h + ball_r):
                b.dx *= -1
            else:
                b.dy *= -1

            if b.xcor() > obs.xcor():
                b.setx(obs.xcor() + obs_w + ball_r)
            else:
                b.setx(obs.xcor() - obs_w - ball_r)
            break


def reset_ball(b):
    b.goto(random.randint(-200, 200), 250)
    b.dx = random.choice([-1, -0.5, 0.5, 1])
    b.dy = base_dy * slow_factor

    # 10%概率生成特殊球
    rand = random.random()
    if rand < 0.1:
        b.special = "gold"
        b.color("gold")
        b.shapesize(stretch_wid=1, stretch_len=1)
    elif rand < 0.18:
        b.special = "bomb"
        b.color("red")
        b.shapesize(stretch_wid=1.2, stretch_len=1.2)
    else:
        b.special = None
        b.color("white")
        b.shapesize(stretch_wid=1, stretch_len=1)


def screen_shake():
    global shake_time
    shake_time = 5


def apply_shake():
    global shake_time
    if shake_time > 0:
        offset_x = random.randint(-3, 3)
        offset_y = random.randint(-3, 3)
        win.cv.canvasx(offset_x)
        win.cv.canvasy(offset_y)
        shake_time -= 1
    else:
        win.cv.canvasx(0)
        win.cv.canvasy(0)


def use_skill():
    """空格释放清屏技能"""
    global skill_cd
    if game_state != "playing" or skill_cd > 0:
        return

    # 消除所有障碍物
    for obs in obstacles:
        obs.hideturtle()
    obstacles.clear()

    # 短暂减速所有球
    for b in balls:
        b.dy *= 0.5

    skill_cd = 600  # 20秒冷却
    update_scoreboard()


def level_up_check():
    global level, base_dy
    new_level = score // 50 + 1
    if new_level > level:
        level = new_level
        # 背景变色
        bg_color = level_colors[min(level - 1, len(level_colors) - 1)]
        win.bgcolor(bg_color)
        # 每升1关加1个障碍
        create_obstacles(1)
        # 略微提速
        base_dy = max(base_dy - 0.3, -7)
        update_scoreboard()


def win_celebration():
    global cele_index
    if game_state != "win":
        return
    pen.color(cele_colors[cele_index % len(cele_colors)])
    pen.clear()
    pen.goto(0, 50)
    pen.write("BLACKWALL NB!", align="center", font=("Courier", 36, "bold"))
    pen.goto(0, -20)
    pen.write(f"最终得分: {score}  等级: Lv.{level}", align="center", font=("Courier", 20, "normal"))
    pen.goto(0, -80)
    pen.write("点击屏幕重新开始", align="center", font=("Courier", 16, "normal"))
    cele_index += 1
    win.ontimer(win_celebration, 200)


def show_game_over():
    pen.color("red")
    pen.clear()
    pen.goto(0, 30)
    pen.write("GAME OVER", align="center", font=("Courier", 36, "bold"))
    pen.goto(0, -30)
    pen.write(f"最终得分: {score}  等级: Lv.{level}", align="center", font=("Courier", 20, "normal"))
    pen.goto(0, -90)
    pen.write("点击屏幕重新开始", align="center", font=("Courier", 16, "normal"))


def start_game():
    global score, lives, combo, level, game_state, base_dy
    global base_paddle_len, catch_count, skill_cd, slow_factor
    global paddle_grow_time, paddle_shrink_time, ball_slow_time, ball_fast_time

    # 清空对象
    for obs in obstacles:
        obs.hideturtle()
    obstacles.clear()
    for pu in powerups:
        pu.hideturtle()
    powerups.clear()
    for b in balls[1:]:
        b.hideturtle()
    balls.clear()

    # 重置状态
    score = 0
    lives = 3
    combo = 1
    level = 1
    catch_count = 0
    skill_cd = 0
    slow_factor = 1
    paddle_grow_time = 0
    paddle_shrink_time = 0
    ball_slow_time = 0
    ball_fast_time = 0
    win.bgcolor("black")

    # 难度参数
    if difficulty == "easy":
        lives = 5
        base_dy = -2
        base_paddle_len = 8
        obs_count = 2
    elif difficulty == "hard":
        lives = 2
        base_dy = -4
        base_paddle_len = 4
        obs_count = 4
    else:
        lives = 3
        base_dy = -3
        base_paddle_len = 6
        obs_count = 3

    game_state = "playing"
    paddle.shapesize(stretch_wid=1, stretch_len=base_paddle_len)
    paddle.goto(0, -250)

    # 初始化第一个球
    b = create_ball()
    balls.append(b)

    create_obstacles(obs_count)
    update_scoreboard()
    win.ontimer(spawn_powerup, random.randint(3000, 5000))
    win.update()


# ---------- 输入控制 ----------
def paddle_right():
    if game_state != "playing":
        return
    max_x = 300 - get_paddle_half_width()
    if paddle.xcor() < max_x:
        paddle.setx(paddle.xcor() + 30)


def paddle_left():
    if game_state != "playing":
        return
    min_x = -300 + get_paddle_half_width()
    if paddle.xcor() > min_x:
        paddle.setx(paddle.xcor() - 30)


def paddle_mouse(x, y):
    if game_state != "playing":
        return
    turtle_x = x - win.window_width() / 2
    max_x = 300 - get_paddle_half_width()
    new_x = max(-max_x, min(max_x, turtle_x))
    paddle.setx(new_x)


def on_click(x, y):
    global game_state, difficulty
    if game_state == "start":
        if -30 < y < 50:
            if -190 < x < -110:
                difficulty = "easy"
                start_game()
            elif -40 < x < 40:
                difficulty = "normal"
                start_game()
            elif 110 < x < 190:
                difficulty = "hard"
                start_game()
    elif game_state != "playing":
        start_game()


win.listen()
win.onkeypress(paddle_right, "Right")
win.onkeypress(paddle_left, "Left")
win.onkeypress(use_skill, "space")
win.onclick(on_click)
win.getcanvas().bind("<Motion>", lambda e: paddle_mouse(e.x, e.y))


# ---------- 核心游戏循环 ----------
def update_game():
    global score, lives, combo, game_state, catch_count, skill_cd

    if game_state == "start" or game_state != "playing":
        win.ontimer(update_game, 50)
        return

    if skill_cd > 0:
        skill_cd -= 1
        if skill_cd % 30 == 0:
            update_scoreboard()

    apply_shake()
    update_obstacles()
    update_powerups()
    level_up_check()

    paddle_half_w = get_paddle_half_width()

    for b in balls[:]:
        b.setx(b.xcor() + b.dx)
        b.sety(b.ycor() + b.dy)

        # 左右墙反弹
        if b.xcor() > 290 or b.xcor() < -290:
            b.dx *= -1

        # 障碍物碰撞
        check_obstacle_collision(b)

        # 触底处理
        if b.ycor() < -280:
            if len(balls) > 1:
                b.hideturtle()
                balls.remove(b)
                continue

            lives -= 1
            combo = 1
            update_scoreboard()
            if lives <= 0:
                game_state = "gameover"
                show_game_over()
            else:
                reset_ball(b)
            continue

        # 挡板接球
        if (b.ycor() - 10 <= paddle.ycor() + 10) and \
                (paddle.xcor() - paddle_half_w < b.xcor() < paddle.xcor() + paddle_half_w):

            # 特殊球处理
            if b.special == "bomb":
                lives -= 1
                screen_shake()
                update_scoreboard()
                if lives <= 0:
                    game_state = "gameover"
                    show_game_over()
                    break
                reset_ball(b)
                continue
            elif b.special == "gold":
                add_score = 30 * combo
            else:
                add_score = 10 * combo

            combo += 1
            score += add_score
            catch_count += 1
            screen_shake()

            # 接球闪光效果
            paddle.color("yellow")
            win.ontimer(lambda: paddle.color("green"), 100)

            update_scoreboard()

            # 胜利判定
            if score >= 200:
                game_state = "win"
                win_celebration()
                break

            # 每接5个球分裂一个新球
            if catch_count % 5 == 0 and len(balls) < max_balls:
                new_b = create_ball(b.xcor(), b.ycor())
                new_b.dx = -b.dx
                new_b.dy = b.dy
                balls.append(new_b)

            reset_ball(b)
            # 全局缓慢提速
            global base_dy
            base_dy = max(base_dy - 0.1, -7)

        # 顶部保护
        if b.ycor() > 300:
            reset_ball(b)

    win.update()
    win.ontimer(update_game, 30)


# ---------- 启动 ----------
draw_start_menu()
update_game()
turtle.done()

 

2、运行视频

3、视频链接

https://www.bilibili.com/video/BV1Ztj366EY4/?share_source=copy_web&vd_source=36b38531ac1f3769136a5b5cee3e3ac2

四、实验过程中遇到的问题和解决过程

问题1:初次编写的程序开始游戏时多个小球迅速下落,球速过快无法加速,导致游戏立刻快速结束且无法重启,球坐标更新速度失控。
解决方案1:借助大模型的帮助,发现出现问题的原因是因为初次编写的程序使用了 while True 无限循环,且循环内没有等待/延时。在 turtle 中,while True 会以极快的速度(每秒数百万次)不断更新小球坐标,导致小球瞬间从顶部(y=250)落到触底位置(y<-280),看起来就是“一闪而过”然后立即游戏结束。解决方案是大模型给出用 turtle.ontimer() 实现定时回调,控制游戏帧率(通常 30~60 FPS),并让 GUI 事件正常处理;使用 win.ontimer(update_game, 30) 定时执行;每 30 毫秒移动一次,速度稳定;添加 win.onclick() 点击重启;修正 ball.ycor() - 10 <= paddle.ycor() + 10 并增加范围判断;限制 ball.dy 最低为 -6。

问题2:编写的程序小球下落速度太慢了
解决方案2:将最大速度上限拉高,从原 -7 提升至 -12,后期加速空间更大;每次接球提速 0.2,关卡升级提速 0.5。

五、课程内容总结

1. 初识Python:了解Python的发展历程与简洁易读、库资源丰富的核心语言特点,熟悉其在多领域的应用场景,完成了本地开发环境的搭建与基础配置。

2. Python基础语法:掌握变量定义规则与数字、字符串、布尔值、空值等基础数据类型,能熟练运用算术、比较、逻辑、赋值四类运算符,可使用input()、print()完成基础输入输出,严格遵循缩进、注释等代码书写规范。

3. 字符串处理:掌握字符串的创建、索引与切片操作,能灵活运用split()、join()、replace()、strip()等常用方法处理文本;掌握多种字符串格式化输出方式,了解正则表达式基础规则,可实现简单的内容匹配与校验。

4. 核心内置数据结构:掌握列表、元组、字典、集合四种数据结构的特性与适用场景,能熟练完成元素的增删改查操作,掌握切片、列表推导式等高效的数据处理方式。

5. 流程控制语句:掌握if-elif-else条件分支结构,以及for、while两种循环语句的用法,能配合break、continue、pass关键字调整循环执行逻辑,理解程序的执行顺序与判断规则。

6. 函数使用:掌握用def关键字定义、调用函数的方法,理解形参与实参的区别,能灵活运用位置参数、默认参数、关键字参数与可变参数设计函数;可通过return语句返回单个或多个结果,实现代码逻辑的封装与复用。

7. 文件及目录操作:了解冯·诺依曼体系架构,明确内存与外存的存储差异,能区分结构化与非结构化数据;掌握文件打开、读写、指针移动与关闭的核心方法,可使用with语句安全管理文件操作,能通过os模块完成目录创建、路径管理等操作。

8. 模块与标准库:掌握Python模块的多种导入方式与别名设置方法,熟悉os、sys、math、random、datetime等常用标准库的基础功能;能够自主创建自定义模块与包,规范管理项目的代码结构。

9. 面向对象程序设计:理解类与对象“抽象模板-具体实例”的对应关系,掌握类的实例化方式与构造方法的作用,明确类方法与普通函数的区别;掌握封装、继承、多态三大面向对象核心特性,了解模块与包的代码组织管理逻辑。

10. 异常处理与程序调试:理解异常处理对提升程序健壮性的作用,掌握try-except-finally异常捕获结构;能识别ValueError、TypeError、FileNotFoundError等常见异常类型,可借助断点调试快速定位、解决代码问题。

11. Python数据库操作:了解数据库的基础作用与应用场景,掌握利用Python实现数据库增、删、改、查的基础操作流程。

12. 爬虫基础:了解爬虫的基本概念与数据采集、分析的应用场景,明确爬虫开发的法律边界与合规要求;知晓爬虫的核心技术组成,了解基础的反反爬虫应对思路。

六、课程感想体会及意见建议

1、课程感悟体会

       最开始接触Python的时候,真的是抗拒的,我总觉得编程python是计算机专业学生的事,对零基础的文科生自己来说门槛很高,看到代码就下意识觉得难。本人简直是个电脑白痴,对编程代码这些东西可谓是一窍不通。但是王老师带着我们梳理知识点,跟着课程一步步从基础语法学起,到写小练习,然后时不时的编一些小程序(虽然大部分都是copy老师写的),再到最后独立完成一个完整的小游戏,我真切感受到了编程的乐趣——它不是死记硬背的语法,而是把想法一步步落地实现的工具。

       这次做接球游戏的过程让我收获特别多。从最基础的挡板移动、球体反弹,到一点点加上道具、障碍物、技能这些功能,中间踩了很多坑,有时候一个小的bug需要调试很久(哭)。但每次找到问题、看到功能正常运行的时候,那种成就感是别的事情比不了的。我也慢慢明白,写代码从来不是一步到位写出完美的程序,而是不断调试、不断优化的过程,耐心和细心比天赋重要得多。

       这门课也打破了我对编程的刻板印象,它不只是枯燥的逻辑,也可以用来做有意思的小游戏、写实用的小工具。哪怕不是相关专业,学会一点Python,也能在很多地方帮到自己。特别感谢black wall老师的耐心讲解,把复杂的知识讲得通俗易懂,给了我很多试错的空间,让我这个编程新手也能跟上节奏,真正体会到了写代码的快乐。

2、课程意见建议

(1)建议增加随堂小练习的比重,讲完一个知识点就带着大家写一小段代码实操,既能及时巩固知识点,也能当场发现问题、解决问题,比单纯听课记忆更深刻。

(2)实验选题可以更丰富一些,除了游戏开发之外,也可以提供数据处理、实用小工具等不同方向的选题,让大家可以根据自己的兴趣和需求选择。

(3)课堂代码演示可以适当放慢节奏,关键步骤稍作停留,或者提前把演示代码发给大家,这样上课的时候可以跟着思路理解,不用只顾着抄代码跟不上讲解。

(4)本学期坚持英语打卡让我收获满满,在积累编程专业词汇、提升专业素养的同时,也高效巩固了四级英语知识,我十分认可这种学习方式,希望能够持续开展。

(5)老师可以在讲解一些python的复杂知识点时,适当放慢节奏,多引入生活实例或趣味案例,帮助我们理解。同时,希望可以向大家提供一些优质的网络教学视频(如b站),让在课上没有完全明白的同学自行查缺补漏。

 我为black wall举大旗,看谁敢与他为敌!

 

posted @ 2026-06-16 22:16  尉瀚文  阅读(1)  评论(0)    收藏  举报