Files
coding-box/snakeGame/main.py
2026-03-14 08:25:12 +08:00

290 lines
9.3 KiB
Python

import math
import random
import cvzone
import cv2
from cvzone.HandTrackingModule import HandDetector
import time
def detectGesture(hand):
fingers = detector.fingersUp(hand)
lm = hand["lmList"]
# 获取关键点坐标
thumb_tip = lm[4]
index_tip = lm[8]
middle_tip = lm[12]
pinky_tip = lm[20]
wrist = lm[0]
# 计算手指高度
index_height = wrist[1] - index_tip[1]
middle_height = wrist[1] - middle_tip[1]
# 手势1: 比耶 → 暂停
if fingers == [0, 1, 1, 0, 0] and index_height > 30 and middle_height > 30:
return "STOP"
# 手势2: 蜘蛛侠 → 开始/继续
if fingers == [1, 1, 0, 0, 1] and index_height > 50:
return "START"
# 手势3: 摇滚手势 → 跳过食物
if fingers == [1, 0, 0, 0, 1]:
thumb_pinky_dist = math.dist(thumb_tip[:2], pinky_tip[:2])
base_dist = math.dist(wrist[:2], lm[9][:2])
if thumb_pinky_dist > base_dist * 0.7:
return "ROCK"
return "NONE"
class SnakeGame:
def __init__(self, food_path):
self.left_points = []
self.right_points = []
self.left_lengths = []
self.right_lengths = []
self.currentLeftLength = 0
self.currentRightLength = 0
self.allowedLength = 200
self.previousLeftHead = (0, 0)
self.previousRightHead = (0, 0)
self.imgFood = cv2.imread(food_path, cv2.IMREAD_UNCHANGED)
self.hFood, self.wFood, _ = self.imgFood.shape
self.foodPoint = (0, 0)
self.randomFoodLocation()
self.score = 0
self.startTime = time.time()
self.lastSkipTime = 0
self.skipCooldown = 1
self.trailDuration = 1.0
def randomFoodLocation(self):
self.foodPoint = (
random.randint(100, 1180),
random.randint(100, 620)
)
def update(self, imgMain, leftHeadPoint, rightHeadPoint):
current_time = time.time()
# 更新左手轨迹
if leftHeadPoint:
self._updateHandTrail(leftHeadPoint, current_time, is_left=True)
# 检测左手是否吃到食物
if self._checkFoodCollision(leftHeadPoint):
self._handleFoodEaten()
# 更新右手轨迹
if rightHeadPoint:
self._updateHandTrail(rightHeadPoint, current_time, is_left=False)
# 检测右手是否吃到食物
if self._checkFoodCollision(rightHeadPoint):
self._handleFoodEaten()
# 清理过期轨迹点
self._cleanOldPoints(current_time)
# 控制蛇身长度
self._controlSnakeLength()
# 绘制蛇身和蛇头
self._drawSnake(imgMain, current_time)
return imgMain
def _updateHandTrail(self, headPoint, current_time, is_left):
cx, cy = headPoint
points = self.left_points if is_left else self.right_points
lengths = self.left_lengths if is_left else self.right_lengths
prevHead = self.previousLeftHead if is_left else self.previousRightHead
px, py = prevHead
points.append([cx, cy, current_time])
dist = math.dist((cx, cy), (px, py))
lengths.append(dist)
if is_left:
self.currentLeftLength += dist
self.previousLeftHead = (cx, cy)
else:
self.currentRightLength += dist
self.previousRightHead = (cx, cy)
def _checkFoodCollision(self, headPoint):
cx, cy = headPoint
fx, fy = self.foodPoint
return (fx - self.wFood // 2 < cx < fx + self.wFood // 2 and
fy - self.hFood // 2 < cy < fy + self.hFood // 2)
def _handleFoodEaten(self):
self.randomFoodLocation()
self.allowedLength += 40
self.score += 1
def _cleanOldPoints(self, current_time):
# 清理左手过期点
while (self.left_points and
current_time - self.left_points[0][2] > self.trailDuration):
if self.left_lengths:
self.currentLeftLength -= self.left_lengths.pop(0)
self.left_points.pop(0)
# 清理右手过期点
while (self.right_points and
current_time - self.right_points[0][2] > self.trailDuration):
if self.right_lengths:
self.currentRightLength -= self.right_lengths.pop(0)
self.right_points.pop(0)
def _controlSnakeLength(self):
# 控制左手蛇长
while self.currentLeftLength > self.allowedLength and self.left_lengths:
self.currentLeftLength -= self.left_lengths.pop(0)
if self.left_points:
self.left_points.pop(0)
# 控制右手蛇长
while self.currentRightLength > self.allowedLength and self.right_lengths:
self.currentRightLength -= self.right_lengths.pop(0)
if self.right_points:
self.right_points.pop(0)
def _drawSnake(self, imgMain, current_time):
# 绘制左手蛇身(蓝色系)
for i in range(1, len(self.left_points)):
time_factor = 1.0 - (current_time - self.left_points[i][2]) / self.trailDuration
color_intensity = int(255 * time_factor)
color = (color_intensity, color_intensity // 2, 0) # 蓝到青
thickness = max(5, int(20 * time_factor))
cv2.line(imgMain, tuple(self.left_points[i-1][:2]),
tuple(self.left_points[i][:2]), color, thickness)
# 绘制右手蛇身(红色系)
for i in range(1, len(self.right_points)):
time_factor = 1.0 - (current_time - self.right_points[i][2]) / self.trailDuration
color_intensity = int(255 * time_factor)
color = (0, color_intensity // 2, color_intensity) # 红到橙
thickness = max(5, int(20 * time_factor))
cv2.line(imgMain, tuple(self.right_points[i-1][:2]),
tuple(self.right_points[i][:2]), color, thickness)
# 绘制蛇头
if self.left_points:
cv2.circle(imgMain, tuple(self.left_points[-1][:2]), 20, (255, 100, 100), cv2.FILLED)
cv2.circle(imgMain, tuple(self.left_points[-1][:2]), 20, (255, 255, 255), 2)
if self.right_points:
cv2.circle(imgMain, tuple(self.right_points[-1][:2]), 20, (100, 100, 255), cv2.FILLED)
cv2.circle(imgMain, tuple(self.right_points[-1][:2]), 20, (255, 255, 255), 2)
def skipFood(self):
currentTime = time.time()
if currentTime - self.lastSkipTime > self.skipCooldown:
self.randomFoodLocation()
self.lastSkipTime = currentTime
return True
return False
def drawFood(self, imgMain):
fx, fy = self.foodPoint
return cvzone.overlayPNG(imgMain, self.imgFood,
(fx - self.wFood // 2, fy - self.hFood // 2))
def drawUI(self, imgMain, gameRunning, skipMessageTime):
currentTime = time.time()
# 分数
cvzone.putTextRect(imgMain, f"Score: {self.score}", [50, 60], scale=2, thickness=3)
# 时间
elapsed = int(currentTime - self.startTime)
mm, ss = elapsed // 60, elapsed % 60
cvzone.putTextRect(imgMain, f"Time: {mm:02d}:{ss:02d}", [50, 120], scale=2, thickness=3)
# 状态提示
if not gameRunning:
cvzone.putTextRect(imgMain, "STOP", [50, 180], scale=2, thickness=2)
elif currentTime - skipMessageTime < 1.0:
cvzone.putTextRect(imgMain, "SKIP", [50, 180], scale=2, thickness=2)
return imgMain
# 初始化
cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)
detector = HandDetector(detectionCon=0.8, maxHands=2)
game = SnakeGame("donut.png")
gameRunning = True
skipMessageTime = 0
lastGesture = "NONE"
# 主循环
while True:
success, img = cap.read()
img = cv2.flip(img, 1)
hands, img = detector.findHands(img, flipType=False)
currentGesture = "NONE"
currentTime = time.time()
leftHandPoint = None
rightHandPoint = None
if hands:
for hand in hands:
handType = hand["type"]
index_tip = tuple(hand["lmList"][8][:2])
if handType == "Left":
leftHandPoint = index_tip
else:
rightHandPoint = index_tip
# 检测手势
gesture = detectGesture(hand)
if gesture != "NONE":
currentGesture = gesture
# 处理手势变化
if currentGesture != lastGesture:
if currentGesture == "START":
gameRunning = True
elif currentGesture == "STOP":
gameRunning = False
if gameRunning and currentGesture == "ROCK":
if game.skipFood():
skipMessageTime = currentTime
lastGesture = currentGesture
if gameRunning:
img = game.update(img, leftHandPoint, rightHandPoint)
else:
lastGesture = "NONE"
# 绘制食物和UI
img = game.drawFood(img)
img = game.drawUI(img, gameRunning, skipMessageTime)
# 显示当前手势
cvzone.putTextRect(img, f"Gesture: {currentGesture}", [50, 650], scale=1, thickness=2)
cv2.imshow("Snake Game", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()