使用python和opencv写的屎山贪吃蛇游戏
本帖最后由 sudezhao 于 2024-11-16 16:25 编辑使用python和opencv库写的一个贪吃蛇的游戏
大概是一年前写的,中间优化过好多次,仓库地址https://github.com/buptsdz/gluttonous-snake-cv,写了完整的readme(个人感觉)
代码还是比较屎山的(难以想象的屎),比如三秒倒计时那里感觉很冗余(在最底下)
但是运行效果还行,能跑(
https://static.52pojie.cn/static/image/hrline/4.gif
运行方式
可以从源码运行也可以从exe文件运行,使用的pyinstaller进行的打包(这个耗费了好久,今年十月份更新,之前都失败了{:1_908:},然后顺手记了个文档)
多文件打包的文档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
https://static.52pojie.cn/static/image/hrline/1.gif
文件目录结构
├── main.py // 主循环函数
├── music.py // 音乐触发函数
├── sg.py // 逻辑处理函数
├── requirements.txt // 依赖库文件
├── history.txt // 存储最高记录
├── README.md // 说明文档
├── musicpackegs // 音乐包
│ ├── xxx.mp3 // 各类音效
│ └── ·······
├── photo // 图片素材
│ ├── xxx.jpg
│ ├── xxx.png
│ └── ·······
└── .gitignore // 忽略文件
运行效果:
部分代码(代码比较屎山没有全粘)
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=
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)
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()
倒计时的屎山:
#采用了最笨的方法展示倒计时
def show1(self,imgmain,currenthead):
px, py = self.previoushead# previous xy坐标
cx, cy = currenthead# current xy坐标
self.points.append()# 为蛇添加当前点
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, self.points, (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", , scale=7, thickness=0, offset=50)
imgmain= cv2.putText(imgmain, "1", , 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()# 为蛇添加当前点
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, self.points, (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", , scale=7, thickness=0, offset=50)
imgmain = cv2.putText(imgmain, "2", , 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()# 为蛇添加当前点
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, self.points, (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", , scale=7, thickness=0, offset=50)
imgmain = cv2.putText(imgmain, "3", , self.font ,8, (0, 200, 200), 11)
if self.flag3==0:
musics.countdown.play()
self.flag3=1
return imgmain
代码只粘贴了一部分,完整的在github上,代码抽象和优化一点没有{:1_936:},希望大佬提出意见和建议 来看看哈哈哈哈哈哈哈哈哈哈 sudezhao 发表于 2024-11-22 01:07
当时不会,就硬写哈哈哈
哈哈都一样
最开始的时候写到一起能跑起来就感觉非常有成就感了
后面慢慢的就想写的更漂亮一点import 就成了最常用的单词了 阿坤,跑一哈 我来试一试 这个可以,游戏看上去挺好玩的。 学习一下~:lol AlbusGellert 发表于 2024-11-16 18:46
我来试一试
希望能运行{:1_923:} 你别说,看起来还挺有意思,代码也能错啊 yixingk 发表于 2024-11-16 17:51
阿坤,跑一哈
要是有大佬能提交pr就好了{:1_889:}