SnakeGame (snake game)

Table of contents

I. Introduction

2. Project introduction

1. How to operate the game

2. Matters needing attention during the development process

(1) The left and right of the image

(2) The screen size of the camera

3. Key points of game realization

1. Select a third-party library

2. Find the key points and mark them

3. Create a class to hold all the functions about the game

4. Define functions for continuous updating 

Fourth, the overall code

5. Conclusion


I. Introduction

Presumably everyone has played the game of greedy snakes: by manipulating the moving direction of the snake, the snake can eat random food. The more food you eat, the longer the snake will become, but if you accidentally hit yourself , then the snake will die, game over!!  The snake games we have played are generally played on mobile phones or game consoles. The movement of the snake is controlled by the arrow keys, so can we directly use a camera to capture our gestures and use the movement of the hand to represent the snake? What about mobile? Of course, today I will complete the design of this game with you and play happily.

Let's Start!

2. Project introduction

1. How to operate the game

Snake game is well known, but computer vision is little known. Computer vision + snake game will bring people more sense of participation and freshness. This project mainly uses gesture recognition to complete the snake game. Simple game. In this game, the computer captures our gestures through the camera and judges whether to move. The player moves his hand to manipulate the snake to get food that appears randomly on the screen. Every time a food is obtained, it will be counted as one point, and the Score will be Add 1 and display it on the screen. When the player accidentally collides with the snake's head and body during operation, GameOver will be displayed! Press the ' r ' key to restart the game.

2. Matters needing attention during the development process

(1) The left and right of the image

Since we use gestures to control the snake's movement, but the camera's picture shows someone else's perspective, so this is exactly the opposite of the player's left and right consciousness, so we need to make a left and right of the picture read by the camera Flip. In principle, it is to exchange the left and right pixel positions, but in Python, a  cv2.flip( )  function can be used to realize the mirror image flip.

(2) The screen size of the camera

We need to play games on the image obtained by the camera, so the small screen will lead to insufficient game space. At the beginning, we can preprocess the size of the screen and set a more reasonable size. The final screen obtained when playing games So as not to appear cramped. The width and height of the screen can be set through the function  cap.set(3, m) cap.set(4, n) .

There will be some other precautions in this project, such as judging collisions, judging to obtain food, etc., which I will introduce later in the project process.

3. Key points of game realization

1. Select a third-party library

Some third-party libraries used:

import math
import random
import cvzone
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector

In this project, we mainly use the above libraries, among which the random library is used to randomly select pixels to place food donuts, the hand recognition in cvzone is used to detect player gestures, and cv2 is used for Some basic image operations, and some other libraries are also useful, which will be introduced one by one later.

2. Find the key points and mark them

In this game, we chose a hand as the target node, so when we detect the presence of a hand in the screen, we need to mark the key points, and this key point happens to be the head of our greedy snake , because we are calling a third-party library, and this library can mark the hand in 3D, but we only need two coordinate values ​​of x and y. The following functions are mainly used to mark the key nodes of the hand:

#检测到第一个手,并标记手部位置
    if hands:
        lmList = hands[0]['lmList']
        pointIndex = lmList[8][0:2] #第八个坐标点的 x, y值,其中 z 值不被包括在里面
        cv2.circle(img, pointIndex, 20, (200, 0, 200), cv2.FILLED) #在关键点处绘制一个圆点并进行填充(此处只是示范,后面会更改)

3. Create a class to hold all the functions about the game

The game we need to implement is a combination of many functions. If you want to use functions to implement these functions, it will be very troublesome. When we use class to complete, since many things are stored in the same class, it will be Reduce difficulty. In this class, we will create many important lists to store some key points we use, such as all the points on the snake, the length of the snake, the overall distance of the snake, food placement, score, etc. :

class SnakeGameClass:
    def __init__(self, pathFood):
        self.points = []  #贪吃蛇身上所有点
        self.lengths = []  #点与点之间的距离
        self.currentLength = 0  #当下蛇的长度
        self.allowedLength = 50  #最大允许长度(阈值)
        self.previousHead = 0, 0  #手部关键点之后的第一个点

        self.imgFood = cv2.imread(pathFood, cv2.IMREAD_UNCHANGED) #定义食物
        self.hFood, self.wFood, _ = self.imgFood.shape
        self.foodPoint = 0, 0
        self.randomFoodLocation()

        self.score = 0
        self.gameOver = False

4. Define functions for continuous updating 

As our hands move, the length and position of the snake will change, so we need to create a function to continuously update to meet the changing needs (this part is also completed in the previously created class):

    def update(self, imgMain, currentHead):
        #游戏结束,打印文本
        if self.gameOver:
            cvzone.putTextRect(imgMain, "Game Over", [300, 400],
                               scale=7, thickness=5, offset=20)
            cvzone.putTextRect(imgMain, f'Your Score: {self.score}', [300, 550],
                               scale=7, thickness=5, offset=20)
        else:
            px, py = self.previousHead
            cx, cy = currentHead

            self.points.append([cx, cy])
            distance = math.hypot(cx - px, cy - py)
            self.lengths.append(distance)
            self.currentLength += distance
            self.previousHead = cx, cy

            #长度缩小
            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

            #检查贪吃蛇是否已经触碰到食物
            rx, ry = self.foodPoint
            if rx - self.wFood // 2 < cx < rx + self.wFood // 2 and \
                    ry - self.hFood // 2 < cy < ry + self.hFood // 2:
                self.randomFoodLocation()
                self.allowedLength += 50
                self.score += 1
                print(self.score)

            #使用线条绘制贪吃蛇
            if self.points:
                for i, point in enumerate(self.points):
                    if i != 0:
                        cv2.line(imgMain, self.points[i - 1], self.points[i], (0, 0, 255), 20)
                cv2.circle(imgMain, self.points[-1], 20, (0, 255, 0), cv2.FILLED)

            #显示食物
            imgMain = cvzone.overlayPNG(imgMain, self.imgFood,
                                        (rx - self.wFood // 2, ry - self.hFood // 2))

            cvzone.putTextRect(imgMain, f'Score: {self.score}', [50, 80],
                               scale=3, thickness=3, offset=10)

            #检测是否碰撞
            pts = np.array(self.points[:-2], np.int32)
            pts = pts.reshape((-1, 1, 2))
            cv2.polylines(imgMain, [pts], False, (0, 255, 0), 3)
            minDist = cv2.pointPolygonTest(pts, (cx, cy), True)

            if -1 <= minDist <= 1:
                print("Hit")
                self.gameOver = True
                self.points = []  #蛇身上所有的点
                self.lengths = []  #不同点之间的距离
                self.currentLength = 0  #当前蛇的长度
                self.allowedLength = 50  #最大允许长度
                self.previousHead = 0, 0  #先前的蛇的头部
                self.randomFoodLocation()

        return imgMain

 In this updated function, we need to judge many things, such as whether the greedy snake touches the food (if it touches the food, we need to increase the length of the snake and accumulate points), whether the current length exceeds the maximum length allowed ( If the current length is less than the maximum length, there is no need to change it, but if the current length is greater than the maximum length, it needs to be shortened), whether the snake collides (judging whether the snake collides by the distance between key nodes, if If there is a collision, enter the gameover module, if not, continue the game), etc., are explained in the above code.

Mainly through the class defined above, we can realize the current Snake game.

Fourth, the overall code

For this mini game, I saw the tutorial on station b and reproduced it step by step. If you are interested, you can try it. Of course, the overall code will be posted below as usual:

"""
Author:XiaoMa
CSDN Address:一马归一码
"""
import math
import random
import cvzone
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector

cap = cv2.VideoCapture(0)

#设置画面的尺寸大小,过小的话导致贪吃蛇活动不开
cap.set(3, 1280)
cap.set(4, 720)

detector = HandDetector(detectionCon=0.8, maxHands=1)


class SnakeGameClass:
    def __init__(self, pathFood):
        self.points = []  #贪吃蛇身上所有点
        self.lengths = []  #每一个点之间的距离
        self.currentLength = 0  #当下蛇的长度
        self.allowedLength = 50  #最大允许长度(阈值)
        self.previousHead = 0, 0  #手部关键点之后的第一个点

        self.imgFood = cv2.imread(pathFood, cv2.IMREAD_UNCHANGED) #定义食物
        self.hFood, self.wFood, _ = self.imgFood.shape
        self.foodPoint = 0, 0
        self.randomFoodLocation()

        self.score = 0
        self.gameOver = False

    def randomFoodLocation(self):
        self.foodPoint = random.randint(100, 1000), random.randint(100, 600)

    def update(self, imgMain, currentHead):
        #游戏结束,打印文本
        if self.gameOver:
            cvzone.putTextRect(imgMain, "Game Over", [300, 400],
                               scale=7, thickness=5, offset=20)
            cvzone.putTextRect(imgMain, f'Your Score: {self.score}', [300, 550],
                               scale=7, thickness=5, offset=20)
        else:
            px, py = self.previousHead
            cx, cy = currentHead

            self.points.append([cx, cy])
            distance = math.hypot(cx - px, cy - py)
            self.lengths.append(distance)
            self.currentLength += distance
            self.previousHead = cx, cy

            #长度缩小
            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

            #检查贪吃蛇是否已经触碰到食物
            rx, ry = self.foodPoint
            if rx - self.wFood // 2 < cx < rx + self.wFood // 2 and \
                    ry - self.hFood // 2 < cy < ry + self.hFood // 2:
                self.randomFoodLocation()
                self.allowedLength += 50
                self.score += 1
                print(self.score)

            #使用线条绘制贪吃蛇
            if self.points:
                for i, point in enumerate(self.points):
                    if i != 0:
                        cv2.line(imgMain, self.points[i - 1], self.points[i], (0, 0, 255), 20)
                cv2.circle(imgMain, self.points[-1], 20, (0, 255, 0), cv2.FILLED)

            #显示食物
            imgMain = cvzone.overlayPNG(imgMain, self.imgFood,
                                        (rx - self.wFood // 2, ry - self.hFood // 2))

            cvzone.putTextRect(imgMain, f'Score: {self.score}', [50, 80],
                               scale=3, thickness=3, offset=10)

            #检测是否碰撞
            pts = np.array(self.points[:-2], np.int32)
            pts = pts.reshape((-1, 1, 2))
            cv2.polylines(imgMain, [pts], False, (0, 255, 0), 3)
            minDist = cv2.pointPolygonTest(pts, (cx, cy), True)

            if -1 <= minDist <= 1:
                print("Hit")
                self.gameOver = True
                self.points = []  #蛇身上所有的点
                self.lengths = []  #不同点之间的距离
                self.currentLength = 0  #当前蛇的长度
                self.allowedLength = 50  #最大允许长度
                self.previousHead = 0, 0  #先前的蛇的头部
                self.randomFoodLocation()

        return imgMain


game = SnakeGameClass("Donut.png")

while True:
    success, img = cap.read()
    img = cv2.flip(img, 1) #镜像翻转
    hands, img = detector.findHands(img, flipType=False)
    #检测到第一个手,并标记手部位置
    if hands:
        lmList = hands[0]['lmList']
        pointIndex = lmList[8][0:2] #第八个坐标点的 x, y值,其中 z 值不被包括在里面
        #cv2.circle(img, pointIndex, 20, (200, 0, 200), cv2.FILLED) #在关键点处绘制一个圆点并进行填充(此处只是示范,后面会更改)
        img = game.update(img, pointIndex)

    cv2.imshow("Image", img)
    key = cv2.waitKey(1)
    #按下‘r’重新开始游戏
    if key == ord('r'):
        game.gameOver = False

As for the donut pattern that needs to be used, you can find a pattern of a suitable size online to replace it.

5. Conclusion

Compared with gesture recognition, this game seems to focus more on python programming ability. Before I started writing this blog post, I felt that I had no way to write, but after I started writing, I found that there was really nothing to write. It was just an exercise. Bar.

Guess you like

Origin blog.csdn.net/qq_52309640/article/details/124076230