diff --git a/.idea/PythonV3_V4.iml b/.idea/PythonV3_V4.iml
index 9e2a31e..7a6134d 100644
--- a/.idea/PythonV3_V4.iml
+++ b/.idea/PythonV3_V4.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/test.py b/test.py
index 3ec3133..4b11763 100644
--- a/test.py
+++ b/test.py
@@ -1,19 +1,43 @@
-import datetime
-dict={
- 0:'星期一',
- 1:'星期二',
- 2:'星期三',
- 3:'星期四',
- 4:'星期五',
- 5:'星期六',
- 6:'星期日'
-}
-print(datetime.datetime.now())
-print(datetime.datetime.now().year)
-print(datetime.datetime.now().month)
-print(datetime.datetime.now().day)
-print(datetime.datetime.now().hour)
-print(datetime.datetime.now().minute)
-print(datetime.datetime.now().second)
-print(dict[datetime.datetime.now().weekday()])
+import random
+landmarks = {"中国": "长城", "古埃及": "金字塔", "法国": "埃菲尔铁塔",
+ "印度": "泰姬陵", "英国": "大本钟", "美国": "自由女神像",
+ "意大利": "比萨斜塔", "澳大利亚": "悉尼歌剧院", "希腊": "帕特农神庙",
+ "日本": "富士山", "南非": "好望角", "丹麦": "小美人鱼铜像",
+ "巴西": "基督像", "泰国": "大皇宫", "加拿大": "CN塔",
+ "荷兰": "桑斯安斯风车村", "俄罗斯": "红场", "捷克": "布拉格城堡",
+ "沙特阿拉伯": "麦加大清真寺", "西班牙": "圣家族大教堂"
+ }
+for i in range(1, 11):
+ filename = "小小旅行家" + str(i) + ".txt"
+ ansfilename = "小小旅行家" + str(i) + "答案.txt"
+ file = open(filename, "w")
+ ansfile = open(ansfilename, "w")
+
+ # 写入抬头
+ file.write("姓名\n\n班级\n\n日期\n\n成绩\n\n")
+ file.write(" " * 20 + f"小小旅行家挑战赛 form {i}\n")
+
+ # 生成题目
+ questions = list(landmarks.keys())
+ random.shuffle(questions)
+ for j in range(10):
+ # 生成题目
+ key = questions[j]
+ file.write(f"{j + 1}.{key}的标志性建筑是()\n")
+
+ # 生成选项
+ correct_option = landmarks[key]
+ options = list(landmarks.values())
+ options.remove(correct_option)
+ random.shuffle(options)
+ all_options = options[0:4] + [correct_option]
+ random.shuffle(all_options)
+ for k in range(4):
+ if (all_options[k] == correct_option):
+ # 写入答案
+ ansfile.write(f"{j + 1}.{chr(65 + k)}\n")
+ file.write(f"{chr(65 + k)}.{all_options[k]}\n")
+ file.write("\n")
+ file.close()
+ ansfile.close()
\ No newline at end of file
diff --git a/第10讲识图精灵/课堂成果/README.md b/第10讲识图精灵/课堂成果/README.md
index 0182181..fa95cc0 100644
--- a/第10讲识图精灵/课堂成果/README.md
+++ b/第10讲识图精灵/课堂成果/README.md
@@ -1,3 +1,11 @@
```bash
pip install baidu-aip chardet -i https://pypi.tuna.tsinghua.edu.cn/simple
+```
+
+```bash
+pip freeze > requirements.txt
+```
+
+```bash
+pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
```
\ No newline at end of file
diff --git a/第11讲语音识别与合成/课堂成果/语音合成.py b/第11讲语音识别与合成/课堂成果/语音合成.py
index 0da0638..7b933c4 100644
--- a/第11讲语音识别与合成/课堂成果/语音合成.py
+++ b/第11讲语音识别与合成/课堂成果/语音合成.py
@@ -5,9 +5,9 @@
账号注册指南:
https://huewq7h021.feishu.cn/wiki/Ry3UwaoceiMRXgklbWtc9mEUn9f?from=from_copylink
"""
-
+#baidu-aip
from aip import AipSpeech
-
+# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ baidu-aip
# 定义常量
APP_ID = '117920031'
API_KEY = '4icZSO1OlMCU2ZiRMhgGCXFu'
@@ -17,16 +17,16 @@ SECRET_KEY = '6wJldJ08m1jIX9hb0ULcJrIJ9D1OJW3c'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
# 要合成的文本
-text = "歪比巴卜"
+text = "你好"
# 调用【语音合成器】
res = client.synthesis(text, 'zh', 1, {
'vol': 5, # 音量
- 'per': 0, # 发音人
- 'spd': 5 # 语速
+ 'per': 5118, # 发音人
+ 'spd': 2 # 语速
})
print(res)
-
+#json
# 判断调用是否成功
if not isinstance(res, dict):
# 获取合成音频结果
diff --git a/第13讲你的颜值有几分二/课堂成果/你的颜值有几分2.py b/第13讲你的颜值有几分二/课堂成果/你的颜值有几分2.py
index e00fbd2..42ce878 100644
--- a/第13讲你的颜值有几分二/课堂成果/你的颜值有几分2.py
+++ b/第13讲你的颜值有几分二/课堂成果/你的颜值有几分2.py
@@ -33,7 +33,7 @@ update_id = None
root = tk.Tk()
root.geometry('700x700')
root.title('颜值测评')
-root.resizable(0, 0)
+root.resizable(1, 1)
# 百度AI的API配置信息
APP_ID = '你的 AppID'
API_KEY = '你的 API Key'
diff --git a/第14讲十秒挑战/课堂成果/click.py b/第14讲十秒挑战/课堂成果/click.py
new file mode 100644
index 0000000..0b6362e
--- /dev/null
+++ b/第14讲十秒挑战/课堂成果/click.py
@@ -0,0 +1,26 @@
+import time
+import pyautogui
+import keyboard # 监听按键
+#使用阿里云镜像源
+#pip install pyautogui -i https://mirrors.aliyun.com/pypi/simple/
+def double_click():
+ # 第一次点击
+ pyautogui.click()
+ print("第一次点击完成")
+
+ # 等待 10 秒
+ time.sleep(9.889)
+
+ # 第二次点击
+ pyautogui.click()
+ print("第二次点击完成")
+
+
+print("按 'o' 键触发两次点击...")
+
+# 持续监听
+while True:
+ if keyboard.is_pressed('o'): # 当按下 o 键
+ double_click()
+ # 防止按住键多次触发
+ time.sleep(0.5)
diff --git a/第14讲十秒挑战/课堂成果/十秒挑战.py b/第14讲十秒挑战/课堂成果/十秒挑战.py
index 7ec0cd2..8e6dce6 100644
--- a/第14讲十秒挑战/课堂成果/十秒挑战.py
+++ b/第14讲十秒挑战/课堂成果/十秒挑战.py
@@ -4,7 +4,7 @@ from tkinter import messagebox
# 创建主窗口
root = tk.Tk()
-root.geometry("300x400") # 设置窗口大小
+root.geometry("300x400+500+200") # 设置窗口大小
root.title("10秒挑战")
bg_image = tk.PhotoImage(file = '十秒挑战.png')
@@ -18,7 +18,7 @@ flag = False
def update_time():
if flag:
now = time.time()
- timex = now - start_time
+ timex = abs(now - start_time)
timex_label.config(text=f"时间:{timex:.3f} 秒\n")
root.after(10, update_time) # 10ms 后更新一次
@@ -33,11 +33,11 @@ def change():
else: # 如果计时已经开始
flag = False # 停止更新时间显示
end_time = time.time() # 获取当前时间戳
- timex = end_time - start_time # 计算实际耗时
+ timex =end_time - start_time # 计算实际耗时
goal = 10 # 目标耗时
# 计算时间差
- time_difference = timex - goal
+ time_difference = abs(timex - goal)
# 更新显示
timex_label.config(text=f"实际时间:{timex:.3f}秒\n误差:{time_difference:.3f}秒")
@@ -46,7 +46,7 @@ def change():
if f'{time_difference:.3f}' == '0.000':
messagebox.showinfo("结果","难以置信!你就是掌控时间的神!")
else:
- messagebox.showinfo("结果","还差一点点,再接再厉吧!")
+ messagebox.showinfo("结果","还差亿点点,再接再厉吧!")
# 创建标签
title_label = tk.Label(root, text="10 秒挑战", font=("楷体", 16))
diff --git a/第17讲中国地图我来拼(二)/课堂成果/template.py b/第17讲中国地图我来拼(二)/课堂成果/template.py
new file mode 100644
index 0000000..09e64fa
--- /dev/null
+++ b/第17讲中国地图我来拼(二)/课堂成果/template.py
@@ -0,0 +1,12 @@
+# 预置内容,请勿改动
+a = {"01新疆碎片": (225, 231), "02西藏碎片": (244, 457), "03内蒙古碎片": (623, 203),
+ "04青海碎片": (383, 396), "05四川碎片": (491, 521), "06黑龙江碎片": (861, 122),
+ "07甘肃碎片": (472, 358), "08云南碎片": (457, 621), "09广西碎片": (580, 656),
+ "10湖南碎片": (640, 584), "11陕西碎片": (586, 415), "12河北碎片": (718, 330),
+ "13吉林碎片": (859, 228), "14湖北碎片": (654, 506), "15广东碎片": (678, 677),
+ "16贵州碎片": (549, 593), "17河南碎片": (672, 446), "18江西碎片": (724, 584),
+ "19山东碎片": (758, 392), "20山西碎片": (650, 369), "21辽宁碎片": (805, 284),
+ "22安徽碎片": (740, 487), "23福建碎片": (766, 609), "24江苏碎片": (770, 462),
+ "25浙江碎片": (793, 538), "26重庆碎片": (573, 525), "27宁夏碎片": (548, 375),
+ "28台湾碎片": (824, 652), "29海南碎片": (610, 753), "30北京碎片": (715, 310),
+ "31天津碎片": (730, 326), "32上海碎片": (818, 495)} # 省份文件名称和对应的正确位置
\ No newline at end of file
diff --git a/第17讲中国地图我来拼(二)/课堂成果/中国地图我来拼2.py b/第17讲中国地图我来拼(二)/课堂成果/中国地图我来拼2.py
index 214d8d5..6082bf4 100644
--- a/第17讲中国地图我来拼(二)/课堂成果/中国地图我来拼2.py
+++ b/第17讲中国地图我来拼(二)/课堂成果/中国地图我来拼2.py
@@ -9,22 +9,24 @@ a = {"01新疆碎片": (225, 231), "02西藏碎片": (244, 457), "03内蒙古碎
"22安徽碎片": (740, 487), "23福建碎片": (766, 609), "24江苏碎片": (770, 462),
"25浙江碎片": (793, 538), "26重庆碎片": (573, 525), "27宁夏碎片": (548, 375),
"28台湾碎片": (824, 652), "29海南碎片": (610, 753), "30北京碎片": (715, 310),
- "31天津碎片": (730, 326), "32上海碎片": (818, 495)} # 省份文件名称和对应的正确位置
-
-# 导入库
+ "31天津碎片": (730, 326), "32上海碎片": (818, 495)} # 省份文件名称和对应的正确位置
+#基类 pygame pgzero
import pgzrun
import os
-import random
+import random # 导入操作系统和随机数库
# 设置窗口
-WIDTH = 1000 # 窗口的宽度
-HEIGHT = 800 # 窗口的高度
+WIDTH = 1000
+HEIGHT = 800
# 设置窗口标题
TITLE = '中国地图我来拼'
# 初始值设置
-selected_piece = None # 当前被选择的碎片
-selected_name = None # 当前被选择的碎片名字
+s_piece = None # 当前被选择的碎片
+s_name = None # 当前被选择的碎片名字
+count = 0 # 记录游戏中正确放置的碎片数量
+time = 120 # 游戏限时
+game_over = False # 标志游戏是否结束
# 加载碎片
pieces = []
@@ -33,37 +35,65 @@ for filename in os.listdir('images'):
if filename[-6:] == '碎片.png': # 只处理以碎片.png结尾的文件
img = Actor(filename[:-4], (random.randint(0, WIDTH), random.randint(0, HEIGHT)))
pieces.append([img, filename[:-4], False]) # 添加所有碎片和碎片信息到列表
-
+# 加载游戏结束画面
+win_img = Actor("成功", (WIDTH // 2, HEIGHT // 2))
+lose_img = Actor("失败", (WIDTH // 2, HEIGHT // 2))
# 刷新屏幕
def draw():
- screen.blit('中国地图背景', (0, 0)) # 绘制背景
+ screen.blit('中国地图背景',(0,0)) # 绘制背景
# 绘制每个碎片
for piece in pieces:
piece[0].draw()
-
+ # 显示剩余时间
+ screen.draw.text(f"Time: {time}", (40, 20), fontsize=40, color="white")
+ # 如果游戏结束,显示胜利或失败的图片
+ if time == 0:
+ lose_img.draw()
+ if count == len(a):
+ win_img.draw()
# 鼠标按下事件
def on_mouse_down(pos):
- global selected_piece, selected_name
- for piece, name, placed in pieces:
- if piece.collidepoint(pos) and not placed: # 检查是否点击到未正确放置的碎片
- selected_piece = piece # 记录选择的碎片
- selected_name = name # 记录碎片名称
- break
-
+ global s_piece, s_name
+ if not game_over:
+ for piece, name, placed in pieces:
+ if piece.collidepoint(pos) and not placed: # 检查是否点击到未正确放置的碎片
+ s_piece = piece # 记录选择的碎片
+ s_name = name # 记录碎片名称
+ break
# 鼠标移动事件
def on_mouse_move(pos):
- if selected_piece != None and 0 < pos[0] < 1000 and 0 < pos[1] < 800: # 如果有选中碎片且鼠标未移出窗口,就随着鼠标移动
- selected_piece.pos = pos # 更新选中碎片的位置
-
+ if s_piece != None and 0
- pass
+ global count, s_piece
+ if s_piece != None: # 如果有选中的碎片
+ c_pos = a[s_name] # 选中碎片的正确位置
+ # 检查当前位置与正确位置的距离
+ if abs(pos[0] - c_pos[0]) < 50 and abs(pos[1] - c_pos[1]) < 50:
+ s_piece.pos = c_pos # 放置到正确位置
+ for piece in pieces:
+ if piece[0] == s_piece and not piece[2]:
+ piece[2] = True # 标记为已正确放置
+ count += 1 # 正确放置的计数器加一
+ break
+ s_piece = None # 清除当前选中的碎片
+# 更新剩余时间
+def update_time():
+ global time, game_over
+ if time > 0:
+ if count < len(a):
+ time -= 1 # 每秒减少一个时间单位
+ else:
+ game_over = True # 游戏结束
+
+# 每隔 1 秒调用更新时间的函数
+clock.schedule_interval(update_time, 1)
# 启动游戏
pgzrun.go()
diff --git a/第19讲火眼金睛(二)/课堂成果/升级版.py b/第19讲火眼金睛(二)/课堂成果/升级版.py
new file mode 100644
index 0000000..3259d74
--- /dev/null
+++ b/第19讲火眼金睛(二)/课堂成果/升级版.py
@@ -0,0 +1,127 @@
+import pgzrun
+import random
+import time
+import pygame
+
+WIDTH = 1000
+HEIGHT = 725
+TOTAL_TIME = 30 # 初始倒计时
+
+# 载入中文字体(Windows 推荐使用 SimHei 或 Microsoft YaHei)
+try:
+ FONT = pygame.font.SysFont("SimHei", 80) # 黑体
+ SMALL_FONT = pygame.font.SysFont("SimHei", 50)
+except:
+ FONT = pygame.font.Font(None, 80)
+ SMALL_FONT = pygame.font.Font(None, 50)
+
+def draw():
+ screen.fill((30, 30, 30))
+ if win:
+ text1 = FONT.render("游戏成功!", True, (255, 255, 0))
+ text2 = SMALL_FONT.render(f"用时:{TOTAL_TIME - remain_time:.1f}s", True, (255, 255, 255))
+ screen.surface.blit(text1, text1.get_rect(center=(WIDTH/2, HEIGHT/2 - 50)))
+ screen.surface.blit(text2, text2.get_rect(center=(WIDTH/2, HEIGHT/2 + 50)))
+ elif lose:
+ text1 = FONT.render("游戏失败!", True, (255, 0, 0))
+ text2 = SMALL_FONT.render("时间到", True, (255, 255, 255))
+ screen.surface.blit(text1, text1.get_rect(center=(WIDTH/2, HEIGHT/2 - 50)))
+ screen.surface.blit(text2, text2.get_rect(center=(WIDTH/2, HEIGHT/2 + 50)))
+ else:
+ t = SMALL_FONT.render(f"{max(0, remain_time):.1f}s", True, (255, 255, 0))
+ screen.surface.blit(t, t.get_rect(center=(900, 662)))
+ for grid in grids:
+ screen.draw.filled_rect(Rect(grid['pos'], (side, side)), grid['color'])
+ for fx in effects:
+ fx_font = pygame.font.SysFont("SimHei", int(fx['size']))
+ txt = fx_font.render(fx['text'], True, pygame.Color(fx['color']))
+ screen.surface.blit(txt, txt.get_rect(center=fx['pos']))
+
+def update():
+ global remain_time, lose, effects
+ remain_time = TOTAL_TIME - (time.time() - start_t)
+ if remain_time <= 0 and not win:
+ lose = True
+ remain_time = 0
+ new_fx = []
+ for fx in effects:
+ x, y = fx['pos']
+ fx['pos'] = (x, y - 1.5)
+ fx['life'] -= 0.05
+ fx['size'] += 0.5
+ if fx['life'] > 0:
+ new_fx.append(fx)
+ effects = new_fx
+
+def base_color():
+ return (random.randint(0, 255),
+ random.randint(0, 255),
+ random.randint(0, 255))
+
+def dif_color(base):
+ r = min(base[0] + random.randint(8, 12), 255)
+ g = min(base[1] + random.randint(8, 12), 255)
+ b = min(base[2] + random.randint(8, 12), 255)
+ return (r, g, b)
+
+def game():
+ global grids, side, dc
+ side = 700 // n * 0.9
+ space = 700 // n * 0.1
+ grids = []
+ bc = base_color()
+ dc = dif_color(bc)
+ i = 1
+ dif_index = random.randint(1, n * n)
+ for row in range(n):
+ for col in range(n):
+ info = {
+ 'pos': (space + col * (side + space),
+ space + row * (side + space)),
+ 'color': dc if i == dif_index else bc
+ }
+ grids.append(info)
+ i += 1
+
+def on_mouse_down(pos):
+ global n, win, start_t, effects
+ if win or lose:
+ return
+ for grid in grids:
+ grid_rect = Rect(grid['pos'], (side, side))
+ if grid_rect.collidepoint(pos):
+ if grid['color'] == dc:
+ start_t += 10
+ effects.append({
+ 'text': '+10s',
+ 'pos': pos,
+ 'size': 60,
+ 'color': 'green',
+ 'life': 1.0
+ })
+ if n > 10:
+ win = True
+ else:
+ n += 1
+ game()
+ else:
+ start_t -= 3
+ effects.append({
+ 'text': '-3s',
+ 'pos': pos,
+ 'size': 60,
+ 'color': 'red',
+ 'life': 1.0
+ })
+ break
+
+# 初始化
+n = 3
+win = False
+lose = False
+start_t = time.time()
+remain_time = TOTAL_TIME
+effects = []
+game()
+
+pgzrun.go()
diff --git a/第19讲火眼金睛(二)/课堂成果/火眼金睛.py b/第19讲火眼金睛(二)/课堂成果/火眼金睛.py
index 21057c6..f47928e 100644
--- a/第19讲火眼金睛(二)/课堂成果/火眼金睛.py
+++ b/第19讲火眼金睛(二)/课堂成果/火眼金睛.py
@@ -31,9 +31,9 @@ def dif_color(base):
参数:
base:表示基础颜色的元组,格式为(r,g,b)
"""
- r = min(base[0] + random.randint(20, 30), 255)
- g = min(base[1] + random.randint(20, 30), 255)
- b = min(base[2] + random.randint(20, 30), 255)
+ r = min(base[0] + random.randint(5, 7), 255)
+ g = min(base[1] + random.randint(5, 7), 255)
+ b = min(base[2] + random.randint(5, 7), 255)
return (r, g, b)
def game():
@@ -71,12 +71,10 @@ def on_mouse_down(pos):
else:
n += 1 # 进入下一关
game()
-
# 初始化游戏参数
n = 3 # 初始为3×3的矩阵
win = False # 记录游戏成功状态
start_t = time.time() # 记录开始时间
game()
-
# 启动游戏
pgzrun.go()
\ No newline at end of file
diff --git a/第21讲疯狂的摩托(二)/课堂成果/pgzhelper.py b/第21讲疯狂的摩托(二)/课堂成果/pgzhelper.py
new file mode 100644
index 0000000..4a6adc7
--- /dev/null
+++ b/第21讲疯狂的摩托(二)/课堂成果/pgzhelper.py
@@ -0,0 +1,315 @@
+import math
+import pygame
+from pgzero.actor import Actor, POS_TOPLEFT, ANCHOR_CENTER, transform_anchor
+from pgzero import game, loaders
+import types
+import sys
+import time
+
+mod = sys.modules['__main__']
+_fullscreen = False
+
+def set_fullscreen():
+ global _fullscreen
+ mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT), pygame.FULLSCREEN)
+ _fullscreen = True
+
+def set_windowed():
+ global _fullscreen
+ mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT))
+ _fullscreen = False
+
+def toggle_fullscreen():
+ if _fullscreen:
+ set_windowed()
+ else:
+ set_fullscreen()
+
+def hide_mouse():
+ pygame.mouse.set_visible(False)
+
+def show_mouse():
+ pygame.mouse.set_visible(True)
+
+class Actor(Actor):
+ def __init__(self, image, pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs):
+ self._flip_x = False
+ self._flip_y = False
+ self._scale = 1
+ self._mask = None
+ self._animate_counter = 0
+ self.fps = 5
+ self.direction = 0
+ super().__init__(image, pos, anchor, **kwargs)
+
+ def distance_to(self, actor):
+ dx = actor.x - self.x
+ dy = actor.y - self.y
+ return math.sqrt(dx**2 + dy**2)
+
+ def direction_to(self, actor):
+ dx = actor.x - self.x
+ dy = self.y - actor.y
+
+ angle = math.degrees(math.atan2(dy, dx))
+ if angle > 0:
+ return angle
+
+ return 360 + angle
+
+ def move_towards(self, actor, dist):
+ angle = math.radians(self.direction_to(actor))
+ dx = dist * math.cos(angle)
+ dy = dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ def point_towards(self, actor):
+ print(self.direction_to(actor))
+ self.angle = self.direction_to(actor)
+
+ def move_in_direction(self, dist):
+ angle = math.radians(self.direction)
+ dx = dist * math.cos(angle)
+ dy = dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ def move_forward(self, dist):
+ angle = math.radians(self.angle)
+ dx = dist * math.cos(angle)
+ dy = dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ def move_left(self, dist):
+ angle = math.radians(self.angle + 90)
+ dx = dist * math.cos(angle)
+ dy = dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ def move_right(self, dist):
+ angle = math.radians(self.angle - 90)
+ dx = dist * math.cos(angle)
+ dy = dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ def move_back(self, dist):
+ angle = math.radians(self.angle)
+ dx = -dist * math.cos(angle)
+ dy = -dist * math.sin(angle)
+ self.x += dx
+ self.y -= dy
+
+ @property
+ def images(self):
+ return self._images
+
+ @images.setter
+ def images(self, images):
+ self._images = images
+ if len(self._images) != 0:
+ self.image = self._images[0]
+
+ def next_image(self):
+ if self.image in self._images:
+ current = self._images.index(self.image)
+ if current == len(self._images) - 1:
+ self.image = self._images[0]
+ else:
+ self.image = self._images[current + 1]
+ else:
+ self.image = self._images[0]
+
+ def animate(self):
+ now = int(time.time() * self.fps)
+ if now != self._animate_counter:
+ self._animate_counter = now
+ self.next_image()
+
+ @property
+ def angle(self):
+ return self._angle
+
+ @angle.setter
+ def angle(self, angle):
+ self._angle = angle
+ self._transform_surf()
+
+ @property
+ def scale(self):
+ return self._scale
+
+ @scale.setter
+ def scale(self, scale):
+ self._scale = scale
+ self._transform_surf()
+
+ @property
+ def flip_x(self):
+ return self._flip_x
+
+ @flip_x.setter
+ def flip_x(self, flip_x):
+ self._flip_x = flip_x
+ self._transform_surf()
+
+ @property
+ def flip_y(self):
+ return self._flip_y
+
+ @flip_y.setter
+ def flip_y(self, flip_y):
+ self._flip_y = flip_y
+ self._transform_surf()
+
+ @property
+ def image(self):
+ return self._image_name
+
+ @image.setter
+ def image(self, image):
+ self._image_name = image
+ self._orig_surf = self._surf = loaders.images.load(image)
+ self._update_pos()
+ self._transform_surf()
+
+ def _transform_surf(self):
+ self._surf = self._orig_surf
+ p = self.pos
+
+ if self._scale != 1:
+ size = self._orig_surf.get_size()
+ self._surf = pygame.transform.scale(self._surf, (int(size[0] * self.scale), int(size[1] * self.scale)))
+ if self._flip_x:
+ self._surf = pygame.transform.flip(self._surf, True, False)
+ if self._flip_y:
+ self._surf = pygame.transform.flip(self._surf, False, True)
+
+ self._surf = pygame.transform.rotate(self._surf, self._angle)
+
+ self.width, self.height = self._surf.get_size()
+ w, h = self._orig_surf.get_size()
+ ax, ay = self._untransformed_anchor
+ anchor = transform_anchor(ax, ay, w, h, self._angle)
+ self._anchor = (anchor[0] * self.scale, anchor[1] * self.scale)
+
+ self.pos = p
+ self._mask = None
+
+ def collidepoint_pixel(self, x, y=0):
+ if isinstance(x, tuple):
+ y = x[1]
+ x = x[0]
+ if self._mask == None:
+ self._mask = pygame.mask.from_surface(self._surf)
+
+ xoffset = int(x - self.left)
+ yoffset = int(y - self.top)
+ if xoffset < 0 or yoffset < 0:
+ return 0
+
+ width, height = self._mask.get_size()
+ if xoffset > width or yoffset > height:
+ return 0
+
+ return self._mask.get_at((xoffset, yoffset))
+
+ def collide_pixel(self, actor):
+ for a in [self, actor]:
+ if a._mask == None:
+ a._mask = pygame.mask.from_surface(a._surf)
+
+ xoffset = int(actor.left - self.left)
+ yoffset = int(actor.top - self.top)
+
+ return self._mask.overlap(actor._mask, (xoffset, yoffset))
+
+ def collidelist_pixel(self, actors):
+ for i in range(len(actors)):
+ if self.collide_pixel(actors[i]):
+ return i
+ return -1
+
+ def collidelistall_pixel(self, actors):
+ collided = []
+ for i in range(len(actors)):
+ if self.collide_pixel(actors[i]):
+ collided.append(i)
+ return collided
+
+ def obb_collidepoints(self, actors):
+ angle = math.radians(self._angle)
+ costheta = math.cos(angle)
+ sintheta = math.sin(angle)
+ width, height = self._orig_surf.get_size()
+ half_width = width / 2
+ half_height = height / 2
+
+ i = 0
+ for actor in actors:
+ tx = actor.x - self.x
+ ty = actor.y - self.y
+ rx = tx * costheta - ty * sintheta
+ ry = ty * costheta + tx * sintheta
+
+ if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height:
+ return i
+ i += 1
+
+ return -1
+
+ def obb_collidepoint(self, x, y=0):
+ if isinstance(x, tuple):
+ y = x[1]
+ x = x[0]
+ angle = math.radians(self._angle)
+ costheta = math.cos(angle)
+ sintheta = math.sin(angle)
+ width, height = self._orig_surf.get_size()
+ half_width = width / 2
+ half_height = height / 2
+
+ tx = x - self.x
+ ty = y - self.y
+ rx = tx * costheta - ty * sintheta
+ ry = ty * costheta + tx * sintheta
+
+ if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height:
+ return True
+
+ return False
+
+ def circle_collidepoints(self, radius, actors):
+ rSquare = radius ** 2
+
+ i = 0
+ for actor in actors:
+ dSquare = (actor.x - self.x)**2 + (actor.y - self.y)**2
+
+ if dSquare < rSquare:
+ return i
+ i += 1
+
+ return -1
+
+ def circle_collidepoint(self, radius, x, y=0):
+ if isinstance(x, tuple):
+ y = x[1]
+ x = x[0]
+ rSquare = radius ** 2
+ dSquare = (x - self.x)**2 + (y - self.y)**2
+
+ if dSquare < rSquare:
+ return True
+
+ return False
+
+
+ def draw(self):
+ game.screen.blit(self._surf, self.topleft)
+
+ def get_rect(self):
+ return self._rect
diff --git a/第21讲疯狂的摩托(二)/课堂成果/游戏优化.py b/第21讲疯狂的摩托(二)/课堂成果/游戏优化.py
new file mode 100644
index 0000000..15afc2e
--- /dev/null
+++ b/第21讲疯狂的摩托(二)/课堂成果/游戏优化.py
@@ -0,0 +1,267 @@
+import pgzrun
+import random
+import itertools
+from pgzhelper import *
+
+WIDTH = 1500
+HEIGHT = 800
+
+# 背景
+bg1 = Actor('连续道路', topleft=(0, 0))
+bg2 = Actor('连续道路', topleft=(1500, 0))
+
+# 摩托车
+motor = Actor('摩托1', (200, HEIGHT // 2))
+motor.images = ['摩托1', '摩托2']
+
+# 障碍物
+obstacles = [Actor('路障', (random.randint(WIDTH, WIDTH + 1200),
+ random.randint(150, HEIGHT - 150))) for _ in range(2)]
+
+# 终点线
+line = Actor('终点线', topleft=(30000, 163))
+
+# 钻石
+zuan = Actor('钻石', topleft=(WIDTH + 100, random.randint(150, HEIGHT - 150)))
+
+# 游戏参数
+speed = 10
+flag = 0 # 0=进行中,1=胜利,2=失败
+score = 0
+combo = 0
+particles = []
+blink = itertools.cycle([255, 180, 120, 180])
+
+# 加速状态
+boost_active = False
+boost_time = 0
+BOOST_DURATION = 5 # 秒
+BOOST_SPEED = 20
+BOOST_CD = 8 # 技能冷却时间
+boost_cd_timer = 0 # CD倒计时
+
+# 游戏计时
+game_time = 0
+
+# 加速提示圆圈参数
+boost_circle_radius = 50
+boost_circle_pos = (WIDTH - 100, HEIGHT - 100)
+
+
+# 绘制函数
+def draw():
+ bg1.draw()
+ bg2.draw()
+
+ if flag == 1 or flag == 2:
+ music.stop()
+ color = (255, next(blink), next(blink))
+ result_text = '胜利!' if flag == 1 else '失败!'
+ screen.draw.text(result_text, center=(WIDTH / 2, HEIGHT / 2 - 100),
+ fontsize=120, color=color, fontname='simkai')
+ # 显示用时和钻石数量
+ screen.draw.text(f'用时:{game_time:.2f}s', center=(WIDTH / 2, HEIGHT / 2 + 50),
+ fontsize=60, color='white', fontname='simkai')
+ screen.draw.text(f'收集钻石:{score}', center=(WIDTH / 2, HEIGHT / 2 + 120),
+ fontsize=60, color='yellow', fontname='simkai')
+ # 显示总评分
+ total_score = calculate_score()
+ screen.draw.text(f'最终评分:{total_score}', center=(WIDTH / 2, HEIGHT / 2 + 190),
+ fontsize=60, color='orange', fontname='simkai')
+ else:
+ for obs in obstacles:
+ obs.draw()
+ draw_particles()
+ motor.draw()
+ line.draw()
+ zuan.draw()
+ # draw_collision_rect() # 可选调试
+ x = int(line.x - motor.x)
+ screen.draw.text(f'距终点:{x} m', (50, 700),
+ fontname='simkai', fontsize=50)
+ screen.draw.text(f'钻石:{score}', (50, 100),
+ fontname='simkai', fontsize=50)
+ screen.draw.text(f'速度:{int(speed)}', (1300, 50),
+ fontname='simkai', fontsize=40, color='yellow')
+ # 绘制加速技能圆圈
+ draw_boost_circle()
+
+
+def draw_particles():
+ for p in particles:
+ screen.draw.filled_circle((p['x'], p['y']), 4,
+ (255, 255, 150, int(p['alpha'])))
+
+
+def update_particles():
+ for p in particles:
+ p['x'] -= 8
+ p['alpha'] -= 4
+ particles[:] = [p for p in particles if p['alpha'] > 0]
+
+
+# 背景与障碍物移动
+def bg_move():
+ bg1.x -= speed
+ bg2.x -= speed
+ if bg1.right <= 0:
+ bg1.left = bg2.right
+ if bg2.right <= 0:
+ bg2.left = bg1.right
+ line.x -= speed
+ zuan.x -= speed
+ if zuan.x <= 0:
+ zuan.x = random.randint(WIDTH, WIDTH + 1200)
+ zuan.y = random.randint(150, HEIGHT - 150)
+
+
+def obs_move():
+ for obs in obstacles:
+ obs.x -= speed
+ if obs.x < -100:
+ obs.x = random.randint(WIDTH, WIDTH + 1200)
+ obs.y = random.randint(150, HEIGHT - 150)
+
+
+def line_collide():
+ global flag
+ if motor.collide_pixel(line):
+ flag = 1
+ else:
+ motor.x += 10
+
+
+def obs_collide():
+ global flag
+ mx, my = motor.x, motor.y
+ mw, mh = motor.width, motor.height
+ rect_width = int(mw * 0.3)
+ rect_height = int(mh * 0.2)
+ left = int(mx - rect_width / 2)
+ top = int(my + mh / 2 - rect_height)
+ bottom_center_rect = Rect((left, top), (rect_width, rect_height))
+ for obs in obstacles:
+ obs_rect = Rect((obs.left, obs.top), (obs.width, obs.height))
+ if bottom_center_rect.colliderect(obs_rect):
+ flag = 2
+
+
+def zuan_collide():
+ global score, combo
+ if motor.collide_pixel(zuan):
+ combo += 1
+ score += combo
+ zuan.x = random.randint(WIDTH, WIDTH + 1200)
+ zuan.y = random.randint(150, HEIGHT - 150)
+ else:
+ combo = max(combo - 0.03, 0)
+
+
+def draw_collision_rect():
+ # 调试用,可注释
+ mx, my = motor.x, motor.y
+ mw, mh = motor.width, motor.height
+ rect_width = int(mw * 0.3)
+ rect_height = int(mh * 0.2)
+ left = int(mx - rect_width / 2)
+ top = int(my + mh / 2 - rect_height)
+ collision_rect = Rect((left, top), (rect_width, rect_height))
+ screen.draw.rect(collision_rect, (255, 0, 0))
+
+
+def draw_boost_circle():
+ # 圆圈颜色随CD变化
+ if boost_cd_timer > 0:
+ color = (100, 100, 100)
+ elif boost_active:
+ color = (0, 255, 255)
+ else:
+ color = (0, 200, 255)
+ screen.draw.circle(boost_circle_pos, boost_circle_radius, color)
+ screen.draw.text("SHIFT", center=boost_circle_pos, color="white", fontsize=30)
+
+
+# 评分计算
+def calculate_score():
+ """根据用时、钻石和胜利情况计算最终得分"""
+ global score, game_time, flag
+
+ base_score = 0
+
+ # 胜利奖励
+ if flag == 1:
+ base_score += 500
+
+ # 钻石加分,每颗钻石10分
+ base_score += score * 10
+
+ # 时间加分,时间越短,额外加分
+ time_score = max(0, int(1000 - game_time * 10))
+ base_score += time_score
+
+ return base_score
+
+
+def update():
+ global speed, boost_active, boost_time, boost_cd_timer, game_time
+
+ if flag == 0:
+ # 计时
+ game_time += 1 / 60
+
+ # 更新技能CD
+ if boost_cd_timer > 0 and not boost_active:
+ boost_cd_timer -= 1 / 60
+ if boost_cd_timer < 0:
+ boost_cd_timer = 0
+
+ # Shift 加速
+ if (keyboard['lshift'] or keyboard['rshift']) and not boost_active and boost_cd_timer == 0:
+ boost_active = True
+ boost_time = BOOST_DURATION
+ speed = BOOST_SPEED
+
+ if boost_active:
+ boost_time -= 1 / 60
+ if boost_time <= 0:
+ boost_active = False
+ speed = 10
+ boost_cd_timer = BOOST_CD # 启动冷却
+
+ # 正常加减速
+ if not boost_active:
+ if keyboard.right:
+ speed = min(speed + 0.2, 18)
+ elif keyboard.left:
+ speed = max(speed - 0.2, 6)
+
+ # 背景移动
+ if line.x > WIDTH - 200:
+ bg_move()
+ obs_move()
+ else:
+ line_collide()
+
+ # 上下移动
+ if keyboard.up and motor.top > 0:
+ motor.y -= 6
+ if keyboard.down and motor.bottom < HEIGHT - 50:
+ motor.y += 6
+
+ # 碰撞检测
+ obs_collide()
+ zuan_collide()
+
+ # 尾焰粒子
+ particles.append({'x': motor.x - 40, 'y': motor.y + 10, 'alpha': 255})
+ update_particles()
+
+
+def change_image():
+ motor.next_image()
+
+
+clock.schedule_interval(change_image, 0.3)
+music.play('速度与激情')
+
+pgzrun.go()
diff --git a/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.7z b/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.7z
deleted file mode 100644
index ae5c863..0000000
Binary files a/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.7z and /dev/null differ
diff --git a/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.py b/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.py
index 4382974..255e4c3 100644
--- a/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.py
+++ b/第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.py
@@ -1,17 +1,27 @@
import pgzrun
+#pygame zero模块 python game zero
import random
from pgzhelper import *
+#pygame zero模块 辅助模块
-# 游戏窗口大小
+# images 图片文件夹
+# sounds 音效文件夹
+# music 音乐文件夹
+# fonts 字体文件夹
+
+#参数名称不可变
WIDTH = 1500
HEIGHT = 800
-# 创建背景角色
-bg1 = Actor('连续道路', topleft = (0,0))
-bg2 = Actor('连续道路', topleft = (1500,0))
+#Actor 面向对象编程思想
+#角色类 基类
+#创建背景角色
+#创建了两个背景角色
+bg1 = Actor('连续道路.png', topleft = (0,0))
+bg2 = Actor('连续道路.png', topleft = (1500,0))
# 创建障碍物
-obstacles = []
+obstacles = [] #障碍物列表
for _ in range(3): # 障碍物数量
x = random.randint(WIDTH, WIDTH + 1500) # 障碍物出现在屏幕右侧外
y = random.randint(100, HEIGHT - 50)
@@ -26,9 +36,10 @@ motor.images = ['摩托1','摩托2']
line = Actor('终点线', topleft = (20000, 163))
# 初始化设置
-speed = 15
-flag = 0
+speed = 15 #速度
+flag = 0 #0为正常行驶,1为成功,2为失败
+# 绘制
def draw():
# 绘制连续背景
bg1.draw()
diff --git a/第22讲手势识别/课堂成果/README.md b/第22讲手势识别/课堂成果/README.md
deleted file mode 100644
index bcce49c..0000000
--- a/第22讲手势识别/课堂成果/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-```bash
-#阿里云镜像源
-pip install -i https://mirrors.aliyun.com/pypi/simple/ opencv-python mediapipe
-```
-
-```bash
-#阿里云镜像源
-pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt
-```
\ No newline at end of file
diff --git a/第22讲手势识别/课堂成果/requirements.txt b/第22讲手势识别/课堂成果/requirements.txt
deleted file mode 100644
index e20607d..0000000
Binary files a/第22讲手势识别/课堂成果/requirements.txt and /dev/null differ
diff --git a/第22讲手势识别/课堂成果/手势识别.py b/第22讲手势识别/课堂成果/手势识别.py
deleted file mode 100644
index a17814b..0000000
--- a/第22讲手势识别/课堂成果/手势识别.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import cv2 # 导入 OpenCV,用于视频捕获和图像处理
-import mediapipe as mp # 导入 MediaPipe,用于手部关键点检测
-
-# =================== 初始化 MediaPipe 模块 ===================
-
-# 获取 MediaPipe 手部识别模块
-mp_hands = mp.solutions.hands
-
-# 创建 Hands 对象,用于处理图像中的手部关键点
-# 默认参数含义:
-# - static_image_mode=False:处理连续视频流(非静态图片)
-# - max_num_hands=2:最多检测2只手
-# - min_detection_confidence=0.5:置信度低于0.5的检测结果将被忽略
-hands = mp_hands.Hands()
-
-# 获取用于绘制关键点和连接线的工具
-mp_draw = mp.solutions.drawing_utils
-
-# =================== 初始化摄像头 ===================
-
-# 打开默认摄像头(设备编号为0)
-cap = cv2.VideoCapture(0)
-
-# =================== 主循环,逐帧处理 ===================
-
-while True:
- # 捕获一帧图像
- ret, frame = cap.read()
-
- # 如果捕获失败,退出循环
- if not ret:
- break
-
- # 水平翻转图像(摄像头默认是镜像视角,翻转后更自然)
- frame = cv2.flip(frame, 1)
-
- # OpenCV 默认使用 BGR 格式,而 MediaPipe 要求输入 RGB 格式图像
- rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
-
- # 使用 MediaPipe 的 hand 模块处理当前帧,检测手部关键点
- results = hands.process(rgb_frame)
-
- # =================== 处理检测结果 ===================
-
- # 如果检测到了手部
- if results.multi_hand_landmarks:
- # 遍历每只手
- for landmarks in results.multi_hand_landmarks:
- # 在原图上绘制21个关键点及其连接骨骼(HAND_CONNECTIONS)
- mp_draw.draw_landmarks(frame, landmarks, mp_hands.HAND_CONNECTIONS)
-
- # 提取21个关键点的 Landmark 对象(含 x, y, z 三维坐标)
- landmark_points = [landmarks.landmark[i] for i in range(21)]
-
- # =================== 手势识别部分(可拓展) ===================
- # 例如:判断是否为“比赞”手势(此处未实现具体函数)
- # if detect_thumb_up(landmark_points):
- # cv2.putText(frame, "Thumb Up!", (50, 100),
- # cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
-
- # =================== 显示图像 ===================
-
- # 显示处理后的视频帧(窗口标题为 Hand Gesture Detection)
- cv2.imshow('Hand Gesture Detection', frame)
-
- # 等待键盘事件,如果按下 'q' 键则退出程序
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
-
-# =================== 清理资源 ===================
-
-# 释放摄像头
-cap.release()
-
-# 销毁所有 OpenCV 窗口
-cv2.destroyAllWindows()
diff --git a/第23讲扫雷/课堂成果/扫雷.py b/第23讲扫雷/课堂成果/扫雷.py
deleted file mode 100644
index 5d29501..0000000
--- a/第23讲扫雷/课堂成果/扫雷.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import tkinter as tk
-from tkinter import messagebox
-import random
-
-# 扫雷游戏类
-class MineSweeper:
- def __init__(self, master, rows, cols, mines):
- self.master = master # 主窗口
- self.rows = rows # 行数
- self.cols = cols # 列数
- self.mines = mines # 雷的数量
- self.buttons = {} # 存放每个按钮控件的字典 {(r, c): Button}
- self.board = [[0 for _ in range(cols)] for _ in range(rows)] # 雷区,-1 表示地雷,其他为周围雷的数量
- self.revealed = [[False for _ in range(cols)] for _ in range(rows)] # 是否已翻开
- self.flags = [[False for _ in range(cols)] for _ in range(rows)] # 是否标记为旗帜
- self.game_over = False # 游戏是否结束
-
- self.generate_mines() # 随机布雷并更新数字
- self.create_widgets() # 创建按钮控件界面
-
- # 布雷并更新周围格子的数字
- def generate_mines(self):
- count = 0
- while count < self.mines:
- r = random.randint(0, self.rows - 1)
- c = random.randint(0, self.cols - 1)
- if self.board[r][c] == -1:
- continue # 避免重复布雷
- self.board[r][c] = -1
- count += 1
-
- # 更新周围8格的数字
- for i in range(r - 1, r + 2):
- for j in range(c - 1, c + 2):
- if 0 <= i < self.rows and 0 <= j < self.cols and self.board[i][j] != -1:
- self.board[i][j] += 1
-
- # 创建游戏按钮界面
- def create_widgets(self):
- for r in range(self.rows):
- for c in range(self.cols):
- # 每个格子是一个 Button,绑定左键点击翻格子、右键标旗
- b = tk.Button(self.master, width=2, height=1, font=('Arial', 14),
- command=lambda r=r, c=c: self.reveal_cell(r, c))
- b.bind("", lambda e, r=r, c=c: self.toggle_flag(r, c))
- b.grid(row=r, column=c)
- self.buttons[(r, c)] = b
-
- # 翻开一个格子
- def reveal_cell(self, r, c):
- if self.game_over or self.revealed[r][c] or self.flags[r][c]:
- return # 游戏结束、已翻开或已标旗不响应
- if self.board[r][c] == -1:
- # 踩到地雷
- self.buttons[(r, c)].config(text="💣", bg="red")
- self.game_over = True
- self.show_all_mines()
- messagebox.showerror("游戏结束", "你踩到雷啦!")
- return
-
- # 否则开始翻格子(递归展开空格)
- self.flood_fill(r, c)
- self.check_win()
-
- # 类似于 BFS 的空白格子展开
- def flood_fill(self, r, c):
- if not (0 <= r < self.rows and 0 <= c < self.cols):
- return # 越界
- if self.revealed[r][c] or self.flags[r][c]:
- return # 已翻开或被标记不处理
- self.revealed[r][c] = True
- val = self.board[r][c]
- btn = self.buttons[(r, c)]
- # 设置按钮为灰色、不可点击、显示数字(或空)
- btn.config(relief=tk.SUNKEN, text=str(val) if val > 0 else "", state=tk.DISABLED, bg="lightgray")
- if val == 0:
- # 如果是 0,继续向四周递归展开
- for dr in (-1, 0, 1):
- for dc in (-1, 0, 1):
- if dr != 0 or dc != 0:
- self.flood_fill(r + dr, c + dc)
-
- # 标记旗帜(右键)
- def toggle_flag(self, r, c):
- if self.revealed[r][c] or self.game_over:
- return
- if self.flags[r][c]:
- self.flags[r][c] = False
- self.buttons[(r, c)].config(text="") # 取消旗帜
- else:
- self.flags[r][c] = True
- self.buttons[(r, c)].config(text="🚩") # 添加旗帜
-
- # 显示所有地雷
- def show_all_mines(self):
- for r in range(self.rows):
- for c in range(self.cols):
- if self.board[r][c] == -1:
- self.buttons[(r, c)].config(text="💣")
-
- # 检查是否胜利
- def check_win(self):
- for r in range(self.rows):
- for c in range(self.cols):
- if self.board[r][c] != -1 and not self.revealed[r][c]:
- return # 还有未翻开的非雷格子
- self.game_over = True
- messagebox.showinfo("胜利!", "恭喜你,排雷成功!")
-
-# 游戏入口,获取参数并启动游戏
-def start_game():
- try:
- # 从输入框获取参数并转为整数
- rows = int(entry_rows.get())
- cols = int(entry_cols.get())
- mines = int(entry_mines.get())
- # 检查输入合理性:不能为负、雷不能太多、不能太大
- if rows <= 0 or cols <= 0 or mines <= 0 or mines*4 >= rows * cols or rows >= 20 or cols >= 20:
- raise ValueError
- except:
- messagebox.showerror("输入错误", "请输入合理的行列和雷数")
- return
-
- # 关闭设置窗口,打开主游戏窗口
- settings_window.destroy()
- root = tk.Tk()
- root.title("扫雷")
- MineSweeper(root, rows, cols, mines)
- root.mainloop()
-
-# 设置窗口(输入行、列、雷数)
-settings_window = tk.Tk()
-settings_window.title("扫雷设置")
-
-tk.Label(settings_window, text="行数:").grid(row=0, column=0)
-entry_rows = tk.Entry(settings_window)
-entry_rows.insert(0, "20") # 默认行数
-entry_rows.grid(row=0, column=1)
-
-tk.Label(settings_window, text="列数:").grid(row=1, column=0)
-entry_cols = tk.Entry(settings_window)
-entry_cols.insert(0, "10") # 默认列数
-entry_cols.grid(row=1, column=1)
-
-tk.Label(settings_window, text="雷数:").grid(row=2, column=0)
-entry_mines = tk.Entry(settings_window)
-entry_mines.insert(0, "10") # 默认雷数
-entry_mines.grid(row=2, column=1)
-
-# 开始游戏按钮
-tk.Button(settings_window, text="开始游戏", command=start_game).grid(row=3, column=0, columnspan=2)
-
-# 启动设置窗口主循环
-settings_window.mainloop()
diff --git a/第23讲逃离地心引力(二)/课堂成果/逃离地心引力.py b/第23讲逃离地心引力(二)/课堂成果/逃离地心引力.py
new file mode 100644
index 0000000..ff89962
--- /dev/null
+++ b/第23讲逃离地心引力(二)/课堂成果/逃离地心引力.py
@@ -0,0 +1,96 @@
+import pgzrun
+import random
+from pgzhelper import *
+
+# 游戏常量配置
+WIDTH = 400 # 游戏窗口宽度
+HEIGHT = 700 # 游戏窗口高度
+TITLE = '逃离地心引力' # 游戏窗口标题
+speed = 5
+gap = 120 # 平台垂直间距
+g = 0.8 # 重力加速度
+jump = -15 # 跳跃力(负值表示向上)
+
+
+# 初始化游戏状态
+def start():
+ global plats, p, score, game_over
+ p = Actor("player", (WIDTH // 2, HEIGHT - 70)) # 初始位置底部
+ plats = [] # 清空平台列表
+ p.vy = 0 # 垂直速度重置为0
+ score = 0 # 分数重置为0
+ game_over = False # 游戏状态重置为未结束
+ # 生成底部固定平台(屏幕底部中央)
+ base = Actor("platform", (WIDTH // 2, HEIGHT - 30))
+ plats.append(base)
+ # 生成初始5个平台(从底部向上固定间距随机分布)
+ for i in range(5):
+ x = random.randint(75, WIDTH - 75) # 平台水平位置随机
+ y = base.y - (gap * (i + 1)) # 平台垂直位置按间距计算
+ plats.append(Actor("platform", (x, y)))
+
+
+def draw():
+ screen.blit("starbg.png", (0, 0)) # 绘制背景
+ # 绘制所有平台
+ for plat in plats:
+ plat.draw()
+ p.draw() # 绘制玩家
+ # 显示分数和结束提示
+ screen.draw.text(f"分数: {score}", (40, 50), color="white", fontname='simkai', fontsize=24)
+ if game_over:
+ screen.draw.text("游戏结束!按 R 重新开始", center=(WIDTH // 2, HEIGHT // 2 - 40),
+ color="white", fontname='simkai', fontsize=24, background="black")
+
+
+def update():
+ global score, game_over
+ # 游戏结束状态处理(按R键重新开始)
+ if game_over:
+ if keyboard.r:
+ start()
+
+ # 玩家移动(左右键)
+ p.x += (keyboard.right - keyboard.left) * speed
+ # 边界限制(确保玩家不超出屏幕左右边界)
+ p.x = max(p.width // 2, min(p.x, WIDTH - p.width // 2))
+ # 物理系统:应用重力(垂直速度递增)
+ p.vy += g
+ p.y += p.vy
+ # 平台碰撞检测(仅当玩家下落时触发)
+ if p.vy > 0:
+ # 像素级碰撞检测(返回碰撞的平台索引,-1表示无碰撞)
+ hit = p.collidelist_pixel(plats)
+ if hit != -1:
+ # 修正玩家位置到平台上方,避免穿模
+ p.bottom = plats[hit].y - 10
+ p.vy = jump # 跳跃
+ sounds.跳1.play() # 跳跃音效
+
+ # 屏幕滚动逻辑(玩家上升到中部时下移平台)
+ if p.y < HEIGHT // 2:
+ dis = HEIGHT // 2 - p.y # 计算需要滚动的距离
+ p.y += dis # 玩家回到屏幕中部
+ # 所有平台下移
+ for plat in plats:
+ plat.y += dis
+
+ # 生成新平台(当顶部平台进入屏幕时,在其上方生成新平台)
+ if plats[-1].y > 0:
+ x = random.randint(75, WIDTH - 75)
+ y = plats[-1].y - gap
+ new = Actor("platform", (x, y))
+ plats.append(new)
+
+ # 移除屏幕下方的平台(保留最小数量)
+ while len(plats) > 7: # 限制平台总数
+ plats.pop(0)
+ score += 10 # 得分
+
+ # 游戏结束条件(玩家掉落底部平台之外)
+ if p.y > HEIGHT + p.height:
+ game_over = True
+
+
+start() # 初始化游戏
+pgzrun.go() # 运行Pygame Zero游戏循环
\ No newline at end of file
diff --git a/第24讲贪吃蛇/课堂成果/贪吃蛇.py b/第24讲贪吃蛇/课堂成果/贪吃蛇.py
deleted file mode 100644
index c769c35..0000000
--- a/第24讲贪吃蛇/课堂成果/贪吃蛇.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""Snake,经典街机游戏。
-
-练习:
-1. 如何让蛇变快或变慢?
-2. 如何让蛇可以从一边穿越到另一边?
-3. 如何让食物移动?
-4. 修改为点击鼠标控制蛇移动。
-"""
-
-from random import randrange
-from turtle import *
-from freegames import square, vector
-#pip install freegames -i https://pypi.douban.com/simple/
-# 食物位置
-food = vector(0, 0)
-# 蛇的身体坐标列表(初始只有一个身体段)
-snake = [vector(10, 0)]
-# 移动方向
-aim = vector(10, 0)
-def change(x, y):
- """改变蛇的移动方向。"""
- aim.x = x
- aim.y = y
-def inside(head):
- """判断蛇头是否在边界内。"""
- return -200 < head.x < 190 and -200 < head.y < 190
-def move():
- """让蛇向前移动一个单位。"""
- head = snake[-1].copy()
- head.move(aim)
- # 如果撞墙或撞到自己,则游戏结束
- if not inside(head) or head in snake:
- square(head.x, head.y, 9, 'red') # 撞到后显示红色方块
- update()
- return
- snake.append(head) # 增加新的蛇头
- if head == food:
- print('蛇长:', len(snake))
- # 随机生成新的食物位置(在格子上)
- food.x = randrange(-15, 15) * 10
- food.y = randrange(-15, 15) * 10
- else:
- snake.pop(0) # 移除蛇尾
-
- clear()
-
- # 画出蛇的每一节身体
- for body in snake:
- square(body.x, body.y, 9, 'black')
-
- # 画出食物
- square(food.x, food.y, 9, 'green')
- update()
- ontimer(move, 100) # 每100毫秒调用一次 move,实现动画
-
-
-# 初始化窗口
-setup(420, 420, 370, 0)
-hideturtle() # 隐藏箭头图标
-tracer(False) # 关闭自动刷新
-listen() # 开启键盘监听
-
-# 绑定按键改变方向
-onkey(lambda: change(10, 0), 'Right')
-onkey(lambda: change(-10, 0), 'Left')
-onkey(lambda: change(0, 10), 'Up')
-onkey(lambda: change(0, -10), 'Down')
-
-# 开始游戏
-move()
-done()
diff --git a/第2讲弹幕/课堂成果/弹幕有问题.py b/第2讲弹幕/课堂成果/弹幕有问题.py
new file mode 100644
index 0000000..3504c39
--- /dev/null
+++ b/第2讲弹幕/课堂成果/弹幕有问题.py
@@ -0,0 +1,46 @@
+import tkinter as tk
+import random
+from PIL import Image, ImageTk
+
+# 弹幕类
+class MovingLabel:
+ def __init__(self, window, text):
+ self.text = text
+ self.label = tk.Label(window, image=kuang, text=self.text, compound="center", font=("黑体", 20), fg='white',width=190, height=45)
+ self.label.place(x=800, y=random.randint(50, 400))
+ self.x = 800 # 初始x坐标800
+ self.move()
+
+ def move(self):
+ if self.x > -200:
+ self.x -= 2
+ self.label.place(x=self.x) # 更新标签的x坐标,实现向左移动
+ # 每隔20毫秒调用一次move方法,实现连续移动效果
+ self.label.after(20, self.move)
+ else:
+ self.label.destroy() # 销毁移出窗口的标签
+
+# 发送弹幕
+def send():
+ text = e1.get() # 获取弹幕文本
+ ml = MovingLabel(window, text)
+
+# 创建窗口
+window = tk.Tk()
+window.geometry('1000x670')
+window.resizable(0, 0)
+
+kuang = ImageTk.PhotoImage(file='kuang.png') # 创建可以在Tkinter中使用的图像对象
+bg_image = Image.open("tv.png") # 导入背景图片
+bg_image = ImageTk.PhotoImage(bg_image) # 转换为PhotoImage对象,才可以在tkinter中显示图形
+bg_label = tk.Label(window, image=bg_image) # 创建标签显示背景图
+bg_label.pack() # 放置标签
+
+# 弹幕输入框
+e1 = tk.Entry(window, font=("黑体", 20))
+e1.place(x=280, y=620)
+# 发送按钮
+b1 = tk.Button(window, text="发送弹幕", font=("黑体", 20), command=send)
+b1.place(x=580, y=613)
+
+window.mainloop()
\ No newline at end of file
diff --git a/第6讲太阳系/课堂成果/简单井字棋.py b/第6讲太阳系/课堂成果/简单井字棋.py
index a420a89..22954c1 100644
--- a/第6讲太阳系/课堂成果/简单井字棋.py
+++ b/第6讲太阳系/课堂成果/简单井字棋.py
@@ -1,4 +1,5 @@
import turtle
+from tkinter import messagebox
# 设置屏幕和画笔
screen = turtle.Screen()
@@ -8,36 +9,49 @@ pen.hideturtle()
pen.speed(0)
pen.pensize(3)
-# 当前玩家(X 先手)
current_player = "X"
-# 保存每格的内容(3x3)
board = [["" for _ in range(3)] for _ in range(3)]
-# 画井字棋的格子
def draw_board():
- for i in range(0, 3): # 画2条竖线
+ for i in range(1, 3): # 竖线
pen.penup()
- pen.goto(-100 + i * 100, -150)
+ pen.goto(-150 + i * 100, -150)
pen.pendown()
- pen.goto(-100 + i * 100, 150)
- for i in range(0, 3): # 画2条横线
+ pen.goto(-150 + i * 100, 150)
+ for i in range(1, 3): # 横线
pen.penup()
- pen.goto(-150, -100 + i * 100)
+ pen.goto(-150, -150 + i * 100)
pen.pendown()
- pen.goto(150, -100 + i * 100)
+ pen.goto(150, -150 + i * 100)
-# 在某个格子画 X 或 O
def draw_symbol(row, col, symbol):
- x = -150 + col * 100 + 50
- y = 150 - row * 100 - 90
+ x = -100 + col * 100
+ y = 70 - row * 100
pen.penup()
pen.goto(x, y)
pen.write(symbol, align="center", font=("Arial", 36, "bold"))
-# 处理点击事件
+def check_win(player):
+ # 检查行
+ for r in range(3):
+ if all(board[r][c] == player for c in range(3)):
+ return True
+ # 检查列
+ for c in range(3):
+ if all(board[r][c] == player for r in range(3)):
+ return True
+ # 检查对角线
+ if all(board[i][i] == player for i in range(3)):
+ return True
+ if all(board[i][2 - i] == player for i in range(3)):
+ return True
+ return False
+
+def check_draw():
+ return all(board[r][c] != "" for r in range(3) for c in range(3))
+
def click(x, y):
global current_player
- # 将坐标转换为格子位置
if not (-150 < x < 150 and -150 < y < 150):
return
col = int((x + 150) // 100)
@@ -45,10 +59,18 @@ def click(x, y):
if board[row][col] == "":
board[row][col] = current_player
draw_symbol(row, col, current_player)
- # 切换玩家
+
+ if check_win(current_player):
+ messagebox.showinfo("游戏结束", f"玩家 {current_player} 获胜!")
+ screen.bye() # 关闭窗口
+ return
+ elif check_draw():
+ messagebox.showinfo("游戏结束", "平局!")
+ screen.bye()
+ return
+
current_player = "O" if current_player == "X" else "X"
-# 运行程序
draw_board()
screen.onclick(click)
screen.mainloop()