2026.3.14 snakegame
This commit is contained in:
BIN
snakeGame/donut.png
Normal file
BIN
snakeGame/donut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
290
snakeGame/main.py
Normal file
290
snakeGame/main.py
Normal file
@@ -0,0 +1,290 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user