diff --git a/snakeGame/donut.png b/snakeGame/donut.png new file mode 100644 index 0000000..3ccee7a Binary files /dev/null and b/snakeGame/donut.png differ diff --git a/snakeGame/main.py b/snakeGame/main.py new file mode 100644 index 0000000..49741f4 --- /dev/null +++ b/snakeGame/main.py @@ -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() \ No newline at end of file