sudezhao 发表于 2024-11-16 16:18

使用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


sudezhao 发表于 2024-11-16 19:05

代码只粘贴了一部分,完整的在github上,代码抽象和优化一点没有{:1_936:},希望大佬提出意见和建议

xiaoli521 发表于 2024-11-16 18:35

来看看哈哈哈哈哈哈哈哈哈哈

prettyafei 发表于 2024-11-22 16:57

sudezhao 发表于 2024-11-22 01:07
当时不会,就硬写哈哈哈

哈哈都一样
最开始的时候写到一起能跑起来就感觉非常有成就感了
后面慢慢的就想写的更漂亮一点import 就成了最常用的单词了

yixingk 发表于 2024-11-16 17:51

阿坤,跑一哈

AlbusGellert 发表于 2024-11-16 18:46

我来试一试

noddy 发表于 2024-11-16 18:47

这个可以,游戏看上去挺好玩的。

32100004 发表于 2024-11-16 18:55

学习一下~:lol

sudezhao 发表于 2024-11-16 18:59

AlbusGellert 发表于 2024-11-16 18:46
我来试一试

希望能运行{:1_923:}

WmeiShiLi 发表于 2024-11-16 19:01

你别说,看起来还挺有意思,代码也能错啊

sudezhao 发表于 2024-11-16 19:02

yixingk 发表于 2024-11-16 17:51
阿坤,跑一哈

要是有大佬能提交pr就好了{:1_889:}
页: [1] 2 3 4 5
查看完整版本: 使用python和opencv写的屎山贪吃蛇游戏