在这篇文章中,我们将使用Python和OpenCV创建贪吃蛇游戏。

1.贪吃蛇游戏

在贪吃蛇游戏中,我们使用箭头键控制数字蛇。

一个苹果随机出现在屏幕上,我们的目标是移动蛇,让它吃苹果。蛇在吃了苹果后会变大,我们的目标是让蛇尽可能长。

然而,如果蛇撞到墙或撞到自己,游戏就结束了。

2.用OpenCV实现贪吃蛇游戏

代码展示

import cv2
import numpy as np
from random import randint
from random import choice


class SnakePart:
  def __init__(self, front, x, y):
    self.front = front
    self.x = x
    self.y = y

  def move(self):
    # 跟随它前面的部分移动
    self.x = self.front.x
    self.y = self.front.y

class Head:
  def __init__(self, direction, x, y):
    self.direction = direction
    self.x = x
    self.y = y

  def move(self):
    # 检查它当前的方向,并相应地移动
    if self.direction == 0:
        self.x += 1
    elif self.direction == 1:
        self.y += 1
    elif self.direction == 2:
        self.x -= 1
    elif self.direction == 3:
        self.y -= 1

def display():

  # 创建空白图像
  board = np.zeros([BOARD_SIZE, BOARD_SIZE, 3])

  # 把蛇涂成绿色
  for part in snake:
    board[part.y, part.x] = [0, 255, 0]
  
  # 把苹果涂成红色
  board[appley, applex] = [0, 0, 255]
  
  # 显示屏
  cv2.imshow("Snake Game", np.uint8(board.repeat(CELL_SIZE, 0).repeat(CELL_SIZE, 1)))
  key = cv2.waitKey(int(1000/SPEED))
  
  # 返回按下的键。如果没有按下键,则为-1。 
  return key
  
def win_focus():
  # 全屏打开图像,然后返回正常窗口
  cv2.namedWindow("Snake Game", cv2.WINDOW_AUTOSIZE);
  board = np.zeros([BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE, 3])
  cv2.imshow("Snake Game", board);
  cv2.setWindowProperty("Snake Game", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN);
  cv2.waitKey(2000)
  cv2.setWindowProperty("Snake Game", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_AUTOSIZE)


if __name__ == '__main__' : 

  # 棋盘游戏中每个单元格的大小
  CELL_SIZE = 20
  # 游戏中沿宽度排列的单元数
  BOARD_SIZE = 45
  # 改变速度使游戏进行得更快
  SPEED = 12
  # 吃过苹果后,蛇会按生长单位生长
  GROWTH = 3

  
  # 变量跟踪苹果是否被吃
  eaten = True
  # 变量来检查游戏是否应该退出
  quit = False
  # 变量跟踪增长。
  grow = 0
  
  # 存储蛇的数组
  snake = []

  #蛇的头从棋盘的中心开始。
  head = Head(0, int((BOARD_SIZE - 1)/2), int((BOARD_SIZE - 1)/2))
  snake.append(head)

  # 通过打印说明开始游戏
  print('w = top, a = left, s = down, d = right')
  # 让窗口聚焦的丑把戏
  win_focus()

  while True:

    # 检查苹果是否被吃掉,并生成一个新的
    if eaten:
      # 列出所有可能的地点
      s = list(range(0, BOARD_SIZE ** 2))
      # 删除属于蛇的一部分的cell
      for part in snake:
          s.remove(part.x * BOARD_SIZE + part.y)
      
      # 从剩余的cell中随机挑选一个 
      a = choice(s)
      applex = int(a/BOARD_SIZE)
      appley = a % BOARD_SIZE
      
      eaten = False

    # 制作并展示显示屏
    key = display()

    
    # 获取按下的键并相应地移动
    # 8和27是删除键和退出键
    # 方向键在OpenCV中很棘手。所以我们使用
	#键'w', 'a','s','d'表示移动。
	# w = top, a = left, s = down, d = right


    if key == 8 or key == 27:
      break
    elif key == ord("d") :
      head.direction = 0
    elif key == ord("s") :
      head.direction = 1
    elif key == ord("a") :
      head.direction = 2
    elif key == ord("w") :
      head.direction = 3

    # 移动snake
    for part in snake[::-1]:
      part.move()    

    # 碰撞规则

    if head.x < 0 or head.x > BOARD_SIZE - 1 or head.y < 0 or head.y > BOARD_SIZE - 1:
      break
        
    for part in snake[1:]:
      if head.x == part.x and head.y == part.y:
        quit = True
        break
        
    if quit:
      break
      
    # 蛇在多帧中逐渐生长    
    if grow > 0:
      snake.append(SnakePart(snake[-1], subx, suby))
      grow -= 1
      
    # 当蛇吃苹果的时候,蛇就会长出来
    if applex == head.x and appley == head.y:
      subx = snake[-1].x
      suby = snake[-1].y
      eaten = True
      grow += GROWTH

代码解析

首先,我们将导入一些库用于显示和生成苹果的随机位置。

import cv2
import numpy as np
from random import randint
from random import choice

让我们制作几个类来代表游戏。

  • 1.SnakePart:这代表了蛇的身体(不包括头部)。
  • 2.Head:这个类代表蛇头

    SnakePart类有x和y坐标,以及对它前面的另一个SnakePartHead的引用。
class SnakePart:
  def __init__(self, front, x, y):
    self.front = front
    self.x = x
    self.y = y

  def move(self):
    # Moves by following the part in front of it
    self.x = self.front.x
    self.y = self.front.y

move()方法只是复制前面部件的位置。
Head类也有x和y。此外,它还有一个指定移动方向的direction变量。

class Head:
  def __init__(self, direction, x, y):
    self.direction = direction
    self.x = x
    self.y = y

  def move(self):
    # 检查它当前的方向,并相应地移动
    if self.direction == 0:
        self.x += 1
    elif self.direction == 1:
        self.y += 1
    elif self.direction == 2:
        self.x -= 1
    elif self.direction == 3:
        self.y -= 1

现在,让我们转到主函数,看看游戏是如何实现的。我们首先定义几个有用的常量。
CELL_SIZE指定显示屏上每个单元格的宽度,以像素为单位。
BOARD_SIZE沿显示屏宽度的单元格数。在我们的示例中,CELL_SIZE是20,BOARD_SIZE是45。因此,显示器的大小是20 x 45 = 900像素。
SPEED决定了蛇的快慢。如果游戏对你来说太快,请使用较小的数字。
GROWTH是指蛇吃了一个苹果后生长的单位数。

if __name__ == '__main__' : 

  # Size of each cell in the board game
  CELL_SIZE = 20
  # Number of cells along the width in the game
  BOARD_SIZE = 45
  # Change SPEED to make the game go faster
  SPEED = 12
  # After eating an apple the snake grows by GROWTH units
  GROWTH = 3

接下来,我们定义一些以后会派上用场的变量。

# Variable to track if apple is eaten
eaten = True
# Variable to check if the game should quit
quit = False
# Variable to track growth. 
grow = 0

现在,让我们创建一个空数组来存储head 和 snakeparts.。

# Array for storing snake
snake = []  
#snake's head starts at the center of the board. 
head = Head(0, int((BOARD_SIZE - 1)/2), int((BOARD_SIZE - 1)/2))
snake.append(head)

在进入程序的主while循环之前,让我们先看看显示当前游戏的display()函数。

在下面的代码中,我们执行以下操作。

  • 我们首先创建一个显示屏,并将其填充为零(黑色),
  • 我们将蛇所占据的单元格涂成绿色。
  • 我们把被苹果占据的单元格涂成红色。
  • 如果键盘上有一个键被按下,返回该键的id。
def display():

  # Create a blank image
  board = np.zeros([BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE, 3])

  # Color the snake green
  for part in snake:
    x = part.x * CELL_SIZE
    y = part.y * CELL_SIZE
    board[y:y + CELL_SIZE, x:x + CELL_SIZE] = [0, 255, 0]
  
  # Color the apple red    
  x = applex * CELL_SIZE
  y = appley * CELL_SIZE
  board[y:y + CELL_SIZE, x:x + CELL_SIZE] = [0, 0, 255]
  
  # Display board
  cv2.imshow("Snake Game", np.uint8(board))
  key = cv2.waitKey(int(1000/SPEED))
  
  # Return the key pressed. It is -1 if no key is pressed. 
  return key

我们几乎准备好进入游戏的主要while循环了。在此之前,我们先打印一些说明。

# Start the game by printing instructions
print('w = top, a = left, s = down, d = right')
# Ugly trick to bring the window in focus
win_focus()

您将注意到一个函数win_focus()。取决于OpenCV在你的系统上使用的窗口方法,当你运行这个游戏时,显示窗口可能不在焦点中。为了解决这一问题,我们使用了这种丑陋的方法来聚焦游戏窗口,这样用户就不必点击窗口。

在下面的代码中,我们执行以下操作

  • 我们打开一个窗口
  • 显示游戏窗口
  • 将窗口设置为全屏。这使窗口成为焦点。
  • 我们将窗口调整回正常大小。
def win_focus():
  # Ugly trick to get the window in focus.
  # Opens an image in fullscreen and then back to normal window
  cv2.namedWindow("Snake Game", cv2.WINDOW_AUTOSIZE);
  board = np.zeros([BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE, 3])
  cv2.imshow("Snake Game", board);
  cv2.setWindowProperty("Snake Game", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN);
  cv2.waitKey(2000)
  cv2.setWindowProperty("Snake Game", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_AUTOSIZE)

如果遇到任何故障,可以在主函数中注释掉win_focus()

对于代码的主要部分,我们将使用一个while循环,其中每次迭代都是蛇的一次移动。在下面的循环中,我们完成两件事:

  • 检查苹果是否被吃了。
  • 如果是,生成一个新的苹果。在生成苹果的时候,我们确保它的位置不与蛇重叠。
while True:

   # Checks if the apple is eaten and generates a new one
   if eaten:
     # Create a list of all possible locations
     s = list(range(0, BOARD_SIZE ** 2))
     # Delete cells that are part of the snake
     for part in snake:
         s.remove(part.x * BOARD_SIZE + part.y)
     
     # Randomly pick from one of the remaining cells    
     a = choice(s)
     applex = int(a/BOARD_SIZE)
     appley = a % BOARD_SIZE
     
     eaten = False

接下来,我们显示显示屏,并查找键盘输入。

if/elif 语句检查是否按下了‘w’、‘a’、’、‘d’、escape 或 delete 键。根据键盘输入,蛇改变方向或停止程序。

# Makes and displays the board
key = display()


# Gets key presses and moves accordingly
# 8 and 27 are delete and escape keys
# Arrow keys are tricky in OpenCV. So we use
# keys 'w', 'a','s','d' for movement. 
# w = top, a = left, s = down, d = right


if key == 8 or key == 27:
  break
elif key == ord("d") :
  head.direction = 0
elif key == ord("s") :
  head.direction = 1
elif key == ord("a") :
  head.direction = 2
elif key == ord("w") :
  head.direction = 3

根据键盘输入,我们移动蛇。您可能还记得,SnakePartmove方法只是在它前面复制蛇部分的位置。

# Moving the snake
for part in snake[::-1]:
    part.move()

接下来,我们实现碰撞检查。下面的代码实现了两个规则:

  • 如果蛇的头移出显示屏,游戏就结束了。
  • 如果蛇的头部与蛇的身体发生碰撞,游戏就结束了。
# Collision rules

if head.x < 0 or head.x > BOARD_SIZE - 1 or head.y < 0 or head.y > BOARD_SIZE - 1:
  break
    
for part in snake[1:]:
  if head.x == part.x and head.y == part.y:
    quit = True
    break
    
if quit:
  break

最后,我们制定了蛇吃苹果生长的规则。当苹果被吃掉的时候,生长变量的增量GROWTH是不变的,蛇每帧生长一个单元格,直到它完全长大。

# The snake grows gradually over multiple frames    
 if grow > 0:
   snake.append(SnakePart(snake[-1], subx, suby))
   grow -= 1
   
 # Grows the snake when it eats an apple
 if applex == head.x and appley == head.y:
   subx = snake[-1].x
   suby = snake[-1].y
   eaten = True
   grow += GROWTH

参考目录

https://learnopencv.com/snake-game-with-opencv-python/