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()