# Automatic implementation of snake eating program in Python

Posted by vbracknell on Tue, 15 Feb 2022 12:47:35 +0100

Realization effect
Let's see the effect first

It's much faster than my manual game, and it's stand-alone. The automatic game doesn't provoke me to scold. Ha ha, the whole automatic game of multiplayer game will be scolded to death~

code
If the software is not installed, install the software first. If the module is not installed, install the pygame module.

1
pip install pygame
Import module

```import pygame,sys,time,random
from pygame.locals import *
```

Define color variables

```redColour = pygame.Color(255,0,0)
blackColour = pygame.Color(0,0,0)
whiteColour = pygame.Color(255,255,255)
greenColour = pygame.Color(0,255,0)
```

In all subsequent divisions, in order to prevent bias in pygame output, divisor (/ /) must be taken instead of simple division (/)

Program interface

Row 0, HEIGHT row, column 0 and WIDTH column are fences, so the actual size is 13 * 13

```IGHT = 15
WIDTH = 15
FIELD_SIZE = HEIGHT * WIDTH
# The snake head is in the first element of the snake array
```

Numbers are used to represent different objects. Because each grid on the matrix will be processed into the path length to reach the food during movement, there needs to be a large enough interval (> height * width) between the three variables to distinguish each other. Lowercase is generally coordinates and uppercase represents constants.

```FOOD = 0
UNDEFINED = (HEIGHT + 1) * (WIDTH + 1)
SNAKE = 2 * UNDEFINED
```

snake is a one-dimensional array. Directly adding the following values to the corresponding elements means moving in four directions.

```LEFT = -1
RIGHT = 1
UP = -WIDTH # One dimensional array, so you need to add the whole width to represent up and down movement.
DOWN = WIDTH
```

Error code

1
ERR = -2333
One dimensional array is used to represent two-dimensional things. board represents the rectangular field of snake movement. The initial snake head is at (1,1), and the initial snake length is 1.

```board = [0] * FIELD_SIZE #[0,0,0,......]
snake = [0] * (FIELD_SIZE+1)
snake_size = 1
```

The temporary variable corresponding to the above variable is used when the snake moves tentatively.

```tmpboard = [0] * FIELD_SIZE
tmpsnake = [0] * (FIELD_SIZE+1)
tmpsnake_size = 1
```

Food: the initial position of food is (4, 7), best_move: direction of motion.

```food = 4 * WIDTH + 7
best_move = ERR
```

Movement direction array, game score (snake length)

```mov = [LEFT, RIGHT, UP, DOWN]
score = 1
```

Check whether a cell is covered by a snake. If it is not covered, it is free and returns true.

```def is_cell_free(idx, psize, psnake):
return not (idx in psnake[:psize])
```

Check whether a position idx can move in the direction of move

```def is_move_possible(idx, move):
flag = False
if move == LEFT:
#Because the actual range is 13 * 13, [1,13] * [1,13], you cannot run to the left when idx is 1. At this time, the remainder is 1, so > 1
flag = True if idx%WIDTH > 1 else False
elif move == RIGHT:
#The < width-2 here is the same as the above
flag = True if idx%WIDTH < (WIDTH-2) else False
elif move == UP:
#The upward judgment drawing here is very easy to understand, because there is another one outside the actual motion range of [1,13] * [1,13]
#The big frame is a fence, which is the rows and columns mentioned earlier. The conditions for downward movement are similar
flag = True if idx > (2*WIDTH-1) else False
elif move == DOWN:
flag = True if idx < (FIELD_SIZE-2*WIDTH) else False
return flag
```

Reset board

board_ After BFS, the UNDEFINED value changes to the path length to reach the food.

If you need to restore, reset it.

```def board_reset(psnake, psize, pboard):
for i in range(FIELD_SIZE):
if i == food:
pboard[i] = FOOD
elif is_cell_free(i, psize, psnake): # The location is empty
pboard[i] = UNDEFINED
else: # This position is the snake body
pboard[i] = SNAKE
```

Breadth first search traverses the whole board and calculates the path length of each non SNAKE element in the board to the food.

```def board_BFS(pfood, psnake, pboard):
queue = []
queue.append(pfood)
inqueue = [0] * FIELD_SIZE
found = False
# At the end of the while cycle, in addition to the snake's body,
# The number in each other square is the Manhattan distance from it to the food
while len(queue)!=0:
idx = queue.pop(0)#Initially, idx is the coordinate of food
if inqueue[idx] == 1: continue
inqueue[idx] = 1
for i in range(4):#Left and right up and down
if is_move_possible(idx, mov[i]):
if idx + mov[i] == psnake[HEAD]:
found = True
if pboard[idx+mov[i]] < SNAKE: # If the point is not the body of the snake
if pboard[idx+mov[i]] > pboard[idx]+1:#No matter when it is less than, otherwise the existing path data will be overwritten.
pboard[idx+mov[i]] = pboard[idx] + 1
if inqueue[idx+mov[i]] == 0:
queue.append(idx+mov[i])
return found
```

Starting from the snake head, select the shortest path from the four field points around the snake head according to the element value in the board.

```def choose_shortest_safe_move(psnake, pboard):
best_move = ERR
min = SNAKE
for i in range(4):
#The minimum judgment here and the maximum judgment of the following functions are assigned first and compared with each other
best_move = mov[i]
return best_move
```

Check whether it can follow the snake tail, that is, there is a path between the snake head and the snake tail, in order to avoid the snake head falling into a dead end. Virtual operations are performed in tmpboard and tmpsnake.

```def is_tail_inside():
global tmpboard, tmpsnake, food, tmpsnake_size
tmpboard[tmpsnake[tmpsnake_size-1]] = 0 # Virtually turn the snake tail into food (because it is virtual, it is carried out in tmpsnake and tmpboard)
tmpboard[food] = SNAKE # The place where food is placed is regarded as the body of a snake
result = board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # Find the path length from each position to the snake tail
for i in range(4): # Returns False if the head and tail of the snake are next to each other. That is, you cannot follow_tail, chasing the snake tail
result = False
return result
```

Let the snake head run one step towards the snake tail, regardless of the obstruction of the snake body, run towards the snake tail.

```def follow_tail():
global tmpboard, tmpsnake, food, tmpsnake_size
tmpsnake_size = snake_size
tmpsnake = snake[:]
board_reset(tmpsnake, tmpsnake_size, tmpboard) # Reset virtual board
tmpboard[tmpsnake[tmpsnake_size-1]] = FOOD # Make snake tail food
tmpboard[food] = SNAKE # Turn the place of food into a snake
board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # Find the path length from each position to the snake tail
tmpboard[tmpsnake[tmpsnake_size-1]] = SNAKE # Restore snake tail
return choose_longest_safe_move(tmpsnake, tmpboard) # Return to the running direction (let the snake head move for 1 step)
```

When all schemes fail, just find a feasible direction (step 1)

```def any_possible_move():
global food , snake, snake_size, board
best_move = ERR
board_reset(snake, snake_size, board)
board_BFS(food, snake, board)
min = SNAKE

for i in range(4):
best_move = mov[i]
return best_move
```

Convert array function

```def shift_array(arr, size):
for i in range(size, 0, -1):
arr[i] = arr[i-1]

def new_food():#Generate new food by random function
global food, snake_size
cell_free = False
while not cell_free:
w = random.randint(1, WIDTH-2)
h = random.randint(1, HEIGHT-2)
food = WIDTH*h + w
cell_free = is_cell_free(food, snake_size, snake)
pygame.draw.rect(playSurface,redColour,Rect(18*(food//WIDTH), 18*(food%WIDTH),18,18))
```

The real snake is in this function, towards pbest_move take one step.

```def make_move(pbest_move):
global snake, board, snake_size, score
shift_array(snake, snake_size)
for body in snake:#Draw snake, body, head and tail
pygame.draw.rect(playSurface,whiteColour,Rect(18*(body//WIDTH), 18*(body%WIDTH),18,18))
pygame.draw.rect(playSurface,greenColour,Rect(18*(snake[snake_size-1]//WIDTH),18*(snake[snake_size-1]%WIDTH),18,18))
#The following line is to fill out the first white block bug that will appear in the initial situation
pygame.draw.rect(playSurface,(255,255,0),Rect(0,0,18,18))
# Refresh pygame display layer
pygame.display.flip()

# If the newly added snakehead is the location of the food
# Add 1 to the snake length to generate new food and reset the board (because the original path length is no longer available)
snake_size += 1
score += 1
if snake_size < FIELD_SIZE: new_food()
else: # If the newly added snakehead is not the location of food
board[snake[snake_size]] = UNDEFINED # The snake tail turns UNDEFINED and black
pygame.draw.rect(playSurface,blackColour,Rect(18*(snake[snake_size]//WIDTH),18*(snake[snake_size]%WIDTH),18,18))
# Refresh pygame display layer
pygame.display.flip()

```

Run it virtually once, and then check whether the operation is feasible at the calling place. Only when it is feasible can it run truly.

After the virtual operation eats the food, get the position of the virtual snake on the board.

```def virtual_shortest_move():
global snake, board, snake_size, tmpsnake, tmpboard, tmpsnake_size, food
tmpsnake_size = snake_size
tmpsnake = snake[:] # If tmpsnake=snake directly, they point to the same memory
tmpboard = board[:] # The length of the path from each position to the food is already in the board, so there is no need to calculate it
board_reset(tmpsnake, tmpsnake_size, tmpboard)

food_eated = False
while not food_eated:
board_BFS(food, tmpsnake, tmpboard)
move = choose_shortest_safe_move(tmpsnake, tmpboard)
shift_array(tmpsnake, tmpsnake_size)
# If the location of the newly added snake head is exactly the location of the food
# Add 1 to the length, reset the board, and the position of the food becomes part of the snake
tmpsnake_size += 1
board_reset(tmpsnake, tmpsnake_size, tmpboard) # After the virtual operation, the snake is in the position of the board
tmpboard[food] = SNAKE
food_eated = True
else: # If the snakehead is not the location of food, the newly added location is snakehead, and the last one becomes a space
tmpboard[tmpsnake[tmpsnake_size]] = UNDEFINED
```

Call this function if there is a path between the snake and the food.

```def find_safe_way():
global snake, board
safe_move = ERR
# Run virtually once, because it has been ensured that there is a path between the snake and the food, so the execution is effective
# After running, get the position of the virtual snake in the board, that is, tmpboard
virtual_shortest_move() # The function is unique
if is_tail_inside(): # If there is a path between snake head and snake tail after virtual operation, select the shortest circuit operation (step 1)
return choose_shortest_safe_move(snake, board)
safe_move = follow_tail() # Otherwise follow virtually_ Step 1 of tail. If it can be done, return true
return safe_move
```

Initialize pygame module

1
pygame.init()
Define a variable to control the speed of the game

1

```fpsClock = pygame.time.Clock()
establish pygame Display layer
```

1
2

```playSurface = pygame.display.set_mode((270,270))
pygame.display.set_caption('Greedy snake')
```

Draw pygame display layer

1

```playSurface.fill(blackColour)
```

Initialize food

```pygame.draw.rect(playSurface,redColour,Rect(18*(food//WIDTH), 18*(food%WIDTH),18,18))

while True:
for event in pygame.event.get():#Loop listening for keyboard and exit events
if event.type == QUIT:#If you click Close
print(score)#Print scores at the end of the game
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:#If the esc key is pressed
if event.key==K_ESCAPE:
print(score)#Print scores at the end of the game
pygame.quit()
sys.exit()
# Refresh pygame display layer
pygame.display.flip()
#Draw the fence. 255255,0 is yellow and the border is 36 because the pygame rectangle starts with the edge and fills the border around
pygame.draw.rect(playSurface,(255,255,0),Rect(0,0,270,270),36)
# Reset distance
board_reset(snake, snake_size, board)
# If snakes can eat food, board_BFS returns true
# Besides the snake body (= SNAKE), other element values in the board indicate the shortest path from the point to the food
if board_BFS(food, snake, board):
best_move  = find_safe_way() # find_ safe_ The only use of way
else:
best_move = follow_tail()
if best_move == ERR:
best_move = any_possible_move()
# The last time I thought about it, I had to go in one direction and take one step
if best_move != ERR: make_move(best_move)
else:
print(score)
#Print scores at the end of the game
break
# Control game speed
fpsClock.tick(20)
#20 looks just right
```

The above is the detailed content of Python's automatic snake playing program. For more information about Python's automatic snake playing, please pay attention to the introduction to Python tutorial and learn other relevant articles!