Compare commits
No commits in common. "master" and "贪吃蛇单人版" have entirely different histories.
2
.idea/PythonV3_V4.iml
generated
2
.idea/PythonV3_V4.iml
generated
@ -4,7 +4,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (PythonV3_V4)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
|
||||
60
test.py
60
test.py
@ -1,43 +1,19 @@
|
||||
import random
|
||||
landmarks = {"中国": "长城", "古埃及": "金字塔", "法国": "埃菲尔铁塔",
|
||||
"印度": "泰姬陵", "英国": "大本钟", "美国": "自由女神像",
|
||||
"意大利": "比萨斜塔", "澳大利亚": "悉尼歌剧院", "希腊": "帕特农神庙",
|
||||
"日本": "富士山", "南非": "好望角", "丹麦": "小美人鱼铜像",
|
||||
"巴西": "基督像", "泰国": "大皇宫", "加拿大": "CN塔",
|
||||
"荷兰": "桑斯安斯风车村", "俄罗斯": "红场", "捷克": "布拉格城堡",
|
||||
"沙特阿拉伯": "麦加大清真寺", "西班牙": "圣家族大教堂"
|
||||
}
|
||||
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()])
|
||||
|
||||
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()
|
||||
@ -1,11 +1,3 @@
|
||||
```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
|
||||
```
|
||||
@ -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': 5118, # 发音人
|
||||
'spd': 2 # 语速
|
||||
'per': 0, # 发音人
|
||||
'spd': 5 # 语速
|
||||
})
|
||||
print(res)
|
||||
#json
|
||||
|
||||
# 判断调用是否成功
|
||||
if not isinstance(res, dict):
|
||||
# 获取合成音频结果
|
||||
|
||||
@ -33,7 +33,7 @@ update_id = None
|
||||
root = tk.Tk()
|
||||
root.geometry('700x700')
|
||||
root.title('颜值测评')
|
||||
root.resizable(1, 1)
|
||||
root.resizable(0, 0)
|
||||
# 百度AI的API配置信息
|
||||
APP_ID = '你的 AppID'
|
||||
API_KEY = '你的 API Key'
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
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)
|
||||
@ -4,7 +4,7 @@ from tkinter import messagebox
|
||||
|
||||
# 创建主窗口
|
||||
root = tk.Tk()
|
||||
root.geometry("300x400+500+200") # 设置窗口大小
|
||||
root.geometry("300x400") # 设置窗口大小
|
||||
root.title("10秒挑战")
|
||||
|
||||
bg_image = tk.PhotoImage(file = '十秒挑战.png')
|
||||
@ -18,7 +18,7 @@ flag = False
|
||||
def update_time():
|
||||
if flag:
|
||||
now = time.time()
|
||||
timex = abs(now - start_time)
|
||||
timex = 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 = abs(timex - goal)
|
||||
time_difference = 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))
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
# 预置内容,请勿改动
|
||||
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)} # 省份文件名称和对应的正确位置
|
||||
@ -9,24 +9,22 @@ 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)} # 省份文件名称和对应的正确位置
|
||||
#基类 pygame pgzero
|
||||
"31天津碎片": (730, 326), "32上海碎片": (818, 495)} # 省份文件名称和对应的正确位置
|
||||
|
||||
# 导入库
|
||||
import pgzrun
|
||||
import os
|
||||
import random # 导入操作系统和随机数库
|
||||
import random
|
||||
|
||||
# 设置窗口
|
||||
WIDTH = 1000
|
||||
HEIGHT = 800
|
||||
WIDTH = 1000 # 窗口的宽度
|
||||
HEIGHT = 800 # 窗口的高度
|
||||
# 设置窗口标题
|
||||
TITLE = '中国地图我来拼'
|
||||
|
||||
# 初始值设置
|
||||
s_piece = None # 当前被选择的碎片
|
||||
s_name = None # 当前被选择的碎片名字
|
||||
count = 0 # 记录游戏中正确放置的碎片数量
|
||||
time = 120 # 游戏限时
|
||||
game_over = False # 标志游戏是否结束
|
||||
selected_piece = None # 当前被选择的碎片
|
||||
selected_name = None # 当前被选择的碎片名字
|
||||
|
||||
# 加载碎片
|
||||
pieces = []
|
||||
@ -35,65 +33,37 @@ 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 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
|
||||
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
|
||||
|
||||
|
||||
# 鼠标移动事件
|
||||
def on_mouse_move(pos):
|
||||
if s_piece != None and 0<pos[0]<1000 and 0<pos[1]<800: # 如果有选中碎片且鼠标未移出窗口,就随着鼠标移动
|
||||
s_piece.pos = pos # 更新选中碎片的位置
|
||||
if selected_piece != None and 0 < pos[0] < 1000 and 0 < pos[1] < 800: # 如果有选中碎片且鼠标未移出窗口,就随着鼠标移动
|
||||
selected_piece.pos = pos # 更新选中碎片的位置
|
||||
|
||||
|
||||
# 鼠标释放事件
|
||||
def on_mouse_up(pos):
|
||||
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 # 清除当前选中的碎片
|
||||
# <检测碎片是否正确放置>
|
||||
pass
|
||||
|
||||
# 更新剩余时间
|
||||
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()
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
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()
|
||||
@ -31,9 +31,9 @@ def dif_color(base):
|
||||
参数:
|
||||
base:表示基础颜色的元组,格式为(r,g,b)
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
return (r, g, b)
|
||||
|
||||
def game():
|
||||
@ -71,10 +71,12 @@ def on_mouse_down(pos):
|
||||
else:
|
||||
n += 1 # 进入下一关
|
||||
game()
|
||||
|
||||
# 初始化游戏参数
|
||||
n = 3 # 初始为3×3的矩阵
|
||||
win = False # 记录游戏成功状态
|
||||
start_t = time.time() # 记录开始时间
|
||||
game()
|
||||
|
||||
# 启动游戏
|
||||
pgzrun.go()
|
||||
@ -1,315 +0,0 @@
|
||||
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
|
||||
@ -1,267 +0,0 @@
|
||||
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()
|
||||
BIN
第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.7z
Normal file
BIN
第21讲疯狂的摩托(二)/课堂成果/疯狂的摩托.7z
Normal file
Binary file not shown.
@ -1,27 +1,17 @@
|
||||
import pgzrun
|
||||
#pygame zero模块 python game zero
|
||||
import random
|
||||
from pgzhelper import *
|
||||
#pygame zero模块 辅助模块
|
||||
|
||||
# images 图片文件夹
|
||||
# sounds 音效文件夹
|
||||
# music 音乐文件夹
|
||||
# fonts 字体文件夹
|
||||
|
||||
#参数名称不可变
|
||||
# 游戏窗口大小
|
||||
WIDTH = 1500
|
||||
HEIGHT = 800
|
||||
|
||||
#Actor 面向对象编程思想
|
||||
#角色类 基类
|
||||
#创建背景角色
|
||||
#创建了两个背景角色
|
||||
bg1 = Actor('连续道路.png', topleft = (0,0))
|
||||
bg2 = Actor('连续道路.png', topleft = (1500,0))
|
||||
# 创建背景角色
|
||||
bg1 = Actor('连续道路', topleft = (0,0))
|
||||
bg2 = Actor('连续道路', topleft = (1500,0))
|
||||
|
||||
# 创建障碍物
|
||||
obstacles = [] #障碍物列表
|
||||
obstacles = []
|
||||
for _ in range(3): # 障碍物数量
|
||||
x = random.randint(WIDTH, WIDTH + 1500) # 障碍物出现在屏幕右侧外
|
||||
y = random.randint(100, HEIGHT - 50)
|
||||
@ -36,10 +26,9 @@ motor.images = ['摩托1','摩托2']
|
||||
line = Actor('终点线', topleft = (20000, 163))
|
||||
|
||||
# 初始化设置
|
||||
speed = 15 #速度
|
||||
flag = 0 #0为正常行驶,1为成功,2为失败
|
||||
speed = 15
|
||||
flag = 0
|
||||
|
||||
# 绘制
|
||||
def draw():
|
||||
# 绘制连续背景
|
||||
bg1.draw()
|
||||
|
||||
9
第22讲手势识别/课堂成果/README.md
Normal file
9
第22讲手势识别/课堂成果/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
```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
|
||||
```
|
||||
BIN
第22讲手势识别/课堂成果/requirements.txt
Normal file
BIN
第22讲手势识别/课堂成果/requirements.txt
Normal file
Binary file not shown.
76
第22讲手势识别/课堂成果/手势识别.py
Normal file
76
第22讲手势识别/课堂成果/手势识别.py
Normal file
@ -0,0 +1,76 @@
|
||||
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()
|
||||
154
第23讲扫雷/课堂成果/扫雷.py
Normal file
154
第23讲扫雷/课堂成果/扫雷.py
Normal file
@ -0,0 +1,154 @@
|
||||
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("<Button-3>", 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()
|
||||
@ -1,96 +0,0 @@
|
||||
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游戏循环
|
||||
71
第24讲贪吃蛇/课堂成果/贪吃蛇.py
Normal file
71
第24讲贪吃蛇/课堂成果/贪吃蛇.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""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()
|
||||
@ -1,46 +0,0 @@
|
||||
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()
|
||||
@ -1,5 +1,4 @@
|
||||
import turtle
|
||||
from tkinter import messagebox
|
||||
|
||||
# 设置屏幕和画笔
|
||||
screen = turtle.Screen()
|
||||
@ -9,49 +8,36 @@ 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(1, 3): # 竖线
|
||||
for i in range(0, 3): # 画2条竖线
|
||||
pen.penup()
|
||||
pen.goto(-150 + i * 100, -150)
|
||||
pen.goto(-100 + i * 100, -150)
|
||||
pen.pendown()
|
||||
pen.goto(-150 + i * 100, 150)
|
||||
for i in range(1, 3): # 横线
|
||||
pen.goto(-100 + i * 100, 150)
|
||||
for i in range(0, 3): # 画2条横线
|
||||
pen.penup()
|
||||
pen.goto(-150, -150 + i * 100)
|
||||
pen.goto(-150, -100 + i * 100)
|
||||
pen.pendown()
|
||||
pen.goto(150, -150 + i * 100)
|
||||
pen.goto(150, -100 + i * 100)
|
||||
|
||||
# 在某个格子画 X 或 O
|
||||
def draw_symbol(row, col, symbol):
|
||||
x = -100 + col * 100
|
||||
y = 70 - row * 100
|
||||
x = -150 + col * 100 + 50
|
||||
y = 150 - row * 100 - 90
|
||||
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)
|
||||
@ -59,18 +45,10 @@ 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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user