本帖最后由 sudezhao 于 2024-11-16 16:25 编辑
使用python和opencv库写的一个贪吃蛇的游戏
大概是一年前写的,中间优化过好多次,仓库地址https://github.com/buptsdz/gluttonous-snake-cv,写了完整的readme(个人感觉)
代码还是比较屎山的(难以想象的屎),比如三秒倒计时那里感觉很冗余(在最底下)
但是运行效果还行,能跑(
运行方式
可以从源码运行也可以从exe文件运行,使用的pyinstaller进行的打包(这个耗费了好久,今年十月份更新,之前都失败了,然后顺手记了个文档)
多文件打包的文档https://www.yuque.com/u39067637/maezfz/qqm6xavvkp00blyb
打包后的文件我放网盘了,可以直接运行:
百度网盘:https://pan.baidu.com/s/1rw7uLH-ReM5BjVyY-mQTzw?pwd=1565
夸克网盘:https://pan.quark.cn/s/e64625f6e4d4
演示视频:https://www.bilibili.com/video/BV1UP411C7YJ/?share_source=copy_web&vd_source=15650a9af77f8d76a170b281763a3039
文件目录结构
├── main.py // 主循环函数
├── music.py // 音乐触发函数
├── sg.py // 逻辑处理函数
├── requirements.txt // 依赖库文件
├── history.txt // 存储最高记录
├── README.md // 说明文档
├── musicpackegs // 音乐包
│ ├── xxx.mp3 // 各类音效
│ └── ·······
├── photo // 图片素材
│ ├── xxx.jpg
│ ├── xxx.png
│ └── ·······
└── .gitignore // 忽略文件
运行效果:
部分代码(代码比较屎山没有全粘)
[Python] 纯文本查看 复制代码 import cv2
from cvzone.HandTrackingModule import HandDetector
import tkinter as tk
from PIL import Image, ImageTk,ImageSequence
import time
from sg import SnakeGameClass
from music import musics
import os
import random
script_dir = os.path.dirname(os.path.abspath(__file__))
donut_path = os.path.join(script_dir, "photos", "Donut.jpg")
chang_path = os.path.join(script_dir, "photos", "chang.png")
tiao_path = os.path.join(script_dir, "photos", "tiao.png")
rap_path = os.path.join(script_dir, "photos", "rap.png")
lanqiu_path = os.path.join(script_dir, "photos", "lanqiu.png")
music_path = os.path.join(script_dir, "photos", "music.png")
head_path = os.path.join(script_dir, "photos", "zhongfen.png")
history_path = os.path.join(script_dir, "history.txt")
beiyou_path=os.path.join(script_dir, "photos", "beiyou.png")
cap = cv2.VideoCapture(0)
cap.set(3, 1280) # width
cap.set(4, 800) # height
prev_frame_time = 0
detector = HandDetector(detectionCon=0.7, maxHands=1)
def close_window():
window.destroy()
def minimize_window():
window.iconify()
# 创建窗口
window = tk.Tk()
window.title("Snake Game")
window.attributes("-fullscreen", False) # 全屏显示
# 创建游戏对象
food_path=[chang_path,tiao_path,rap_path,lanqiu_path,music_path]
game = SnakeGameClass(food_path,head_path)
#图片路径
yanfei_path = os.path.join(script_dir, "photos", "yanfei.png")
shenlilinghua_path =os.path.join(script_dir, "photos", "shenlilinghua.png")
xiaogong_path =os.path.join(script_dir, "photos", "xiaogong.png")
ganyu_path =os.path.join(script_dir, "photos", "ganyu.png")
# 打开原始图片
yanfei = Image.open(yanfei_path)
shenlilinghua = Image.open(shenlilinghua_path)
xiaogong = Image.open(xiaogong_path)
ganyu = Image.open(ganyu_path)
# 缩放图片
yanfei= yanfei.resize((300, 300))
shenlilinghua = shenlilinghua.resize((300, 300))
xiaogong = xiaogong.resize((300, 300))
ganyu = ganyu.resize((300, 300))
# 将缩放后的图片转换为tkinter.PhotoImage对象
yanfei = ImageTk.PhotoImage(yanfei)
shenlilinghua = ImageTk.PhotoImage(shenlilinghua)
xiaogong = ImageTk.PhotoImage(xiaogong)
ganyu = ImageTk.PhotoImage(ganyu)
#转为tk的laber对象
label_wife1= tk.Label(window, image=yanfei)
label_wife2= tk.Label(window, image=shenlilinghua)
label_wife3= tk.Label(window, image=xiaogong)
label_wife4= tk.Label(window, image=ganyu)
canvas = None # 画布对象
start_button = None # 开始按钮
quit_button=None
restart_button = None # 重新开始按钮
reset_flag = 0
update_flag = 0
score_label=None
gameover_label=None
image=None
countdown_flag=0
start_time=0
flag_random=0
score_window=None
history=0
#获取文件
history_file = open(history_path, 'r')
try:
history = int(history_file.read())
except ValueError:
history = 0
# 关闭文件
history_file.close()
# 读取 GIF 图像
gif_path=os.path.join(script_dir, "photos", "snake.gif")
gif = Image.open(gif_path)
# 获取 GIF 图像的每一帧
frames = []
for frame in ImageSequence.Iterator(gif):
frames.append(ImageTk.PhotoImage(frame))
# 创建显示 GIF 图像的 Label
gif_label = tk.Label(window)
gif_label.pack()
# 更新 GIF 图像的显示
def update_frame(index):
gif_label.config(image=frames[index])
window.after(100, update_frame, (index + 1) % len(frames))
# 设置 GIF 图像的位置
gif_label.place(relx=0.5, rely=0.4, anchor=tk.CENTER)
# 开始播放 GIF 图像
update_frame(0)
# 创建beiyou的 PhotoImage 对象
beiyou = Image.open(beiyou_path)
beiyou = ImageTk.PhotoImage(beiyou)
def update_high_score(path):#更新文件中最高分
# 读取txt文件中的数值
with open(path, 'r') as file:
content = file.read()
max_value = int(content) if content else 0
if game.score >= max_value:
with open(path, 'w') as file:
file.write(str(game.score))
def clear_history_callback():#清空历史记录
global history
history = 0
with open(history_path, 'w') as file:
file.write('0')
history_label.config(text=f"历史最高: {history}")
def update_history():#清空历史记录
global history
with open(history_path, 'r') as file:
history=int(file.read())
def close_score_window():
global score_window
score_window.destroy()
def show_score(score):
global flag_random,history_path,history,score_window,history
# 创建一个新窗口
score_window = tk.Toplevel()
score_window.title("玩原神导致的")
score_window.protocol("WM_DELETE_WINDOW", close_score_window)
# 设置窗口尺寸
width = 350
height = 250
# 获取屏幕尺寸
screen_width = score_window.winfo_screenwidth()
screen_height = score_window.winfo_screenheight()
if flag_random==0:
x = (screen_width // 2) - (width // 2)
y = (screen_height // 2) - (height // 2)
score_window.geometry(f"{width}x{height}+{x}+{y}")
score_window.resizable(False, False) # 禁用窗口的调整大小功能
else:
# 计算窗口位置的随机范围
x_range = screen_width - width
y_range = screen_height - height
# 生成随机位置坐标
xran = random.randint(0, x_range)
yran = random.randint(0, y_range)
# 设置窗口位置
score_window.geometry(f"{width}x{height}+{xran}+{yran}")
score_window.resizable(False, False) # 禁用窗口的调整大小功能
def reopen():
global flag_random
if score_window.winfo_exists(): # 检查窗口是否存在
score_window.destroy() # 删除当前窗口
if flag_random==0:
flag_random=1
show_score(game.score) # 重新打开当前窗口
# 创建显示得分的标签
score_label = tk.Label(score_window, text=f"你的得分: {score}", font=("Helvetica", 14))
score_label.place(relx=0.5, rely=0.2, anchor="center")
if score<=history:
#历史得分
history_label =tk.Label(score_window, text=f"历史最高: {history}", font=("Helvetica", 12))
history_label.place(relx=0.5, rely=0.3, anchor="center")
# 创建标签并显示图片
beiyou_label = tk.Label(score_window, image=beiyou)
beiyou_label.place(relx=0.5, rely=0.55, anchor="center")
# 创建其他文本标签
text_label = tk.Button(score_window, text="原神", font=("Helvetica", 12), command=reopen,
padx=48, pady=2)
text_label.place(relx=0.27, rely=0.85, anchor="center")
# 添加关闭按钮
close_button = tk.Button(score_window, text="启动!", font=("Helvetica", 12), command=reopen,
padx=47, pady=2)
close_button.place(relx=0.73, rely=0.85, anchor="center")
else:
history_label = tk.Label(score_window, text=f"恭喜你!成为历史最高: {score}", font=("Helvetica", 12))
history_label.place(relx=0.5, rely=0.45, anchor="center")
# 添加关闭按钮
close_button = tk.Button(score_window, text="原神怎么你了", font=("Helvetica", 12), command=close_score_window,
padx=45, pady=3)
close_button.place(relx=0.5, rely=0.85, anchor="center")
def update_window():
if score_window.winfo_exists(): # 检查窗口是否存在
score_window.update() # 更新窗口,处理事件
score_window.after(100, update_window) # 定时调用更新函数
else:
pass
# 第一次调用更新函数
update_window()
倒计时的屎山:
[Python] 纯文本查看 复制代码
#采用了最笨的方法展示倒计时
def show1(self,imgmain,currenthead):
px, py = self.previoushead # previous xy坐标
cx, cy = currenthead # current xy坐标
self.points.append([cx, cy]) # 为蛇添加当前点
distance = math.hypot(cx - px, cy - py) # 计算两个点之间距离
self.lengths.append(distance) # 将距离加到总长度上
self.currentlength += distance # 计算当前长度
self.previoushead = cx, cy # 更新蛇头位置
#draw snake
if self.points:
for i, point in enumerate(self.points):
if i != 0: # 如果当前点不是第一个点
cv2.line(imgmain, self.points[i - 1], self.points[i], (0, 0, 255), 20) # 画线
cv2.circle(imgmain, self.points[-1], 20, (200, 0, 200), cv2.FILLED)
#length reduction
if self.currentlength>self.allowedlength:
for i,length in enumerate(self.lengths):
self.currentlength-=length
self.lengths.pop(i)
self.points.pop(i)
if self.currentlength<self.allowedlength:
break
#cvzone.putTextRect(imgmain, "1", [630, 400], scale=7, thickness=0, offset=50)
imgmain= cv2.putText(imgmain, "1", [580, 430], self.font,8, (0,200,200), 11)
if self.flag1==0:
musics.countdown.play()
self.flag1=1
return imgmain
def show2(self,imgmain,currenthead):
px, py = self.previoushead # previous xy坐标
cx, cy = currenthead # current xy坐标
self.points.append([cx, cy]) # 为蛇添加当前点
distance = math.hypot(cx - px, cy - py) # 计算两个点之间距离
self.lengths.append(distance) # 将距离加到总长度上
self.currentlength += distance # 计算当前长度
self.previoushead = cx, cy # 更新蛇头位置
# draw snake
if self.points:
for i, point in enumerate(self.points):
if i != 0: # 如果当前点不是第一个点
cv2.line(imgmain, self.points[i - 1], self.points[i], (0, 0, 255), 20) # 画线
cv2.circle(imgmain, self.points[-1], 20, (200, 0, 200), cv2.FILLED)
# length reduction
if self.currentlength > self.allowedlength:
for i, length in enumerate(self.lengths):
self.currentlength -= length
self.lengths.pop(i)
self.points.pop(i)
if self.currentlength < self.allowedlength:
break
#cvzone.putTextRect(imgmain, "2", [630, 400], scale=7, thickness=0, offset=50)
imgmain = cv2.putText(imgmain, "2", [580, 430], self.font,8, (0, 200, 200), 11)
if self.flag2==0:
musics.countdown.play()
self.flag2=1
return imgmain
def show3(self,imgmain,currenthead):
px, py = self.previoushead # previous xy坐标
cx, cy = currenthead # current xy坐标
self.points.append([cx, cy]) # 为蛇添加当前点
distance = math.hypot(cx - px, cy - py) # 计算两个点之间距离
self.lengths.append(distance) # 将距离加到总长度上
self.currentlength += distance # 计算当前长度
self.previoushead = cx, cy # 更新蛇头位置
# draw snake
if self.points:
for i, point in enumerate(self.points):
if i != 0: # 如果当前点不是第一个点
cv2.line(imgmain, self.points[i - 1], self.points[i], (0, 0, 255), 20) # 画线
cv2.circle(imgmain, self.points[-1], 20, (200, 0, 200), cv2.FILLED)
# length reduction
if self.currentlength > self.allowedlength:
for i, length in enumerate(self.lengths):
self.currentlength -= length
self.lengths.pop(i)
self.points.pop(i)
if self.currentlength < self.allowedlength:
break
#cvzone.putTextRect(imgmain, "3", [630, 400], scale=7, thickness=0, offset=50)
imgmain = cv2.putText(imgmain, "3", [580, 430], self.font ,8, (0, 200, 200), 11)
if self.flag3==0:
musics.countdown.play()
self.flag3=1
return imgmain
|