From 9cb653850d3466153948cce1641e062d6d00e29f Mon Sep 17 00:00:00 2001 From: Kegongteng Date: Sat, 14 Mar 2026 08:25:12 +0800 Subject: [PATCH] 2026.3.14 snakegame --- snakeGame/donut.png | Bin 0 -> 5874 bytes snakeGame/main.py | 290 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 snakeGame/donut.png create mode 100644 snakeGame/main.py diff --git a/snakeGame/donut.png b/snakeGame/donut.png new file mode 100644 index 0000000000000000000000000000000000000000..3ccee7a246e90180b79c7c9266057ce0e128c3ba GIT binary patch literal 5874 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 IM6N<$f)IXmsQ>@~ literal 0 HcmV?d00001 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