Source code direct deliveryPython will move its own snake script?

Life is short, I use python

This time I bring you a super super super super!

Cool little thing!

It’s okay if you don’t want to move by yourself~

There are more good things~

All here?

Python installation package + information: click here to jump to the business card at the end of the article

Realize the effect

Please add a picture description

This is really a good thing to catch fish hahahaha

Code

If you haven’t installed the software, install the software first.
Install the pygame module without installing the module

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)
headColour = pygame. Color(0,119,255)

In all subsequent divisions,
In order to prevent deviations in pygame output,
Must take divisor (//) instead of simple division (/)

Program Interface

Line 0, HEIGHT line,
Column 0, WIDTH is listed as the fence,
So the actual size is 13*13

IGHT = 15
WIDTH = 15
FIELD_SIZE = HEIGHT * WIDTH
# The snake head is located in the first element of the snake array python learning exchange button qun:903971231
HEAD = 0

Use numbers to represent different objects,
Because each grid on the matrix will be processed into the length of the path to the food during the movement,
Therefore, there needs to be a large enough interval (>HEIGHT*WIDTH) between these three variables to distinguish each other.
Lowercase is generally coordinates, and uppercase represents constants.

FOOD = 0
UNDEFINED = (HEIGHT + 1) * (WIDTH + 1)
SNAKE = 2 * UNDEFINED
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 the entire width needs to be added to represent up and down movement.
DOWN = WIDTH

Error code

ERR = -2333

Use a one-dimensional array to represent two-dimensional things,
board represents the rectangular field of snake movement,
Initialize the snake head at (1,1),
The initial snake length is 1.

board = [0] * FIELD_SIZE #[0,0,0,…]
snake = [0] * (FIELD_SIZE + 1)
snake[HEAD] = 1*WIDTH + 1
snake_size = 1

A temporary variable corresponding to the above variable,
Used when the snake moves tentatively.

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

food: The food position is initially at (4, 7),
best_move: direction of movement.

food = 4 * WIDTH + 7
best_move = ERR

array of motion directions,
Game Score (Snake Length)

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

Check if a cell is covered by snake body,
If there is no coverage, 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], so when idx is 1, you cannot run to the left, and the remainder is 1 at this time, so >1
        flag = True if idx%WIDTH > 1 else False
    elif move == RIGHT:
        #The <WIDTH-2 here is the same as above
        flag = True if idx%WIDTH < (WIDTH-2) else False
    elif move == UP:
        #The upward judgment drawing here is easy to understand, because outside the actual range of motion of [1,13]*[1,13], there is another
        #The big frame is the wall, which is the ranks mentioned before, and the conditions for judging the 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
After board_BFS, the UNDEFINED value becomes the path length to reach the food.
To restore it, 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 position is empty
            pboard[i] = UNDEFINED
        else: # The position is snake body
            pboard[i] = SNAKE

Breadth-first search traverses the entire board,
Calculate the path length of each non-SNAKE element in the board to reach the food.

def board_BFS(pfood, psnake, pboard):
    queue = []
    queue. append(pfood)
    inqueue = [0] * FIELD_SIZE
    found = False
    # After the while loop ends, except for the body of the snake,
    # 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 coordinates of the 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: #Don't care if 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

From the head of the snake,
According to the element value in the board,
Select the shortest path from the 4 domain points around the snake head.

def choose_shortest_safe_move(psnake, pboard):
    best_move = ERR
    min = SNAKE
    for i in range(4):
        if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD] + mov[i]]<min:
          #The minimum judgment here and the maximum judgment of the function below are all assigned first, and then compared with each other in a loop
            min = pboard[psnake[HEAD] + mov[i]]
            best_move = mov[i]
    return best_move

Check if you can chase the tail of the snake,

That is, there is a path between the snake head and the snake tail,

In order to prevent the snakehead from falling into a dead end.

Virtual operation is carried out in tmpboard, 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, so do it in tmpsnake, tmpboard)
    tmpboard[food] = SNAKE # place where food is placed, as a snake body
    result = board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # Find the path length from each position to the tail of the snake
    for i in range(4): # Return False if the snake head and tail are next to each other. That is, you can't follow_tail, and you are chasing the tail of the snake.
        if is_move_possible(tmpsnake[HEAD], mov[i]) and tmpsnake[HEAD] + mov[i]==tmpsnake[tmpsnake_size-1] and tmpsnake_size>3:
            result = False
    return result

Let the head of the snake run one step towards the tail,
No matter what the body of the snake blocks, run towards the tail of the snake.

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 tails food
    tmpboard[food] = SNAKE # Make the food place become a snake body
    board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # Find the length of the path from each position to the tail of the snake
    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 1 step)

When all options fail,
Just find a feasible direction to go (1 step)

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):
        if is_move_possible(snake[HEAD], mov[i]) and board[snake[HEAD] + mov[i]]<min:
            min = board[snake[HEAD] + mov[i]]
            best_move = mov[i]
    return best_move,

Conversion array function

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

def new_food(): #random function to generate new food
    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,
Take 1 step towards pbest_move.

def make_move(pbest_move):
    global snake, board, snake_size, score
    shift_array(snake, snake_size)
    snake[HEAD] += pbest_move
    p = snake[HEAD]
    for body in snake:#drawing snake, body, head, 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))
    pygame.draw.rect(playSurface,headColour,Rect(18*(p//WIDTH), 18*(p%WIDTH),18,18))
    #The following line is to fill in 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 the pygame display layer
    pygame. display. flip()
    
    # If the newly added snake head is the food position
    # Increase the length of the snake by 1, generate new food, and reset the board (because the original path lengths are no longer available)
    if snake[HEAD] == food:
        board[snake[HEAD]] = SNAKE # new snake head
        snake_size += 1
        score + = 1
        if snake_size < FIELD_SIZE: new_food()
    else: # If the newly added snake head is not a food position
        board[snake[HEAD]] = SNAKE # new snake head
        board[snake[snake_size]] = UNDEFINED # snake tail becomes UNDEFINED, black
        pygame.draw.rect(playSurface,blackColour,Rect(18*(snake[snake_size]//WIDTH),18*(snake[snake_size]%WIDTH),18,18))
        # Refresh the pygame display layer
        pygame. display. flip()

run virtually once,

Then check if this run is feasible at the call site,

It works only if it works.

After the virtual run 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, the two point to the same memory
    tmpboard = board[:] # The length of the path to the food from each position 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)
        tmpsnake[HEAD] + = move # Add a new position in front of the snake head
        # If the position of the newly added snake head is exactly the position of the food
        # Add 1 to the length, reset the board, and the position of the food becomes a part of the snake (SNAKE)
        if tmpsnake[HEAD] == food:
            tmpsnake_size += 1
            board_reset(tmpsnake, tmpsnake_size, tmpboard) # After the virtual operation, the position of the snake on the board
            tmpboard[food] = SNAKE
            food_eated = True
        else: # If the snake head is not the food position, the newly added position is the snake head, and the last one becomes a space
            tmpboard[tmpsnake[HEAD]] = SNAKE
            tmpboard[tmpsnake[tmpsnake_size]] = UNDEFINED

If there is a path between the snake and the food,

Then call this function.

def find_safe_way():
    global snake board
    safe_move = ERR
    # Virtually run once, because it has ensured that there is a path between the snake and the food, so the execution is valid
    # Get the position of the virtual snake in the board after running, that is, tmpboard
    virtual_shortest_move() # The only place where this function is called
    if is_tail_inside(): # If there is a path between the head of the snake and the tail of the snake after virtual running, choose the shortest path to run (1 step)
        return choose_shortest_safe_move(snake, board)
    safe_move = follow_tail() # Otherwise follow_tail 1 step virtually, if it can be done, return true
    return safe_move

Initialize the pygame module

pygame.init()

Define a variable to control the game speed

fpsClock = pygame.time.Clock()

Create a pygame display layer

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

Draw the pygame display layer

playSurface.fill(blackColour)

initial food

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

while True:
    for event in pygame.event.get(): #Loop monitor keyboard and exit events
        if event.type == QUIT: #If you click to close
            print(score)#Print the score after the game is over
            pygame. quit()
            sys. exit()
        elif event.type == KEYDOWN: #If the esc key is pressed
            if event.key==K_ESCAPE:
                print(score)#Print the score after the game is over
                pygame. quit()
                sys. exit()
    # Refresh the pygame display layer
    pygame. display. flip()
    #Draw the wall, 255, 255, 0 is yellow, and the border is 36 because the pygame rectangle starts with the side and fills the border around it
    pygame.draw.rect(playSurface,(255,255,0),Rect(0,0,270,270),36)
    # reset distance
    board_reset(snake, snake_size, board)
    # If the snake can eat food, board_BFS returns true
    # In addition to the snake body (=SNAKE), other element values in the board represent the shortest path length from this point to the food
    if board_BFS(food, snake, board):
        best_move = find_safe_way() # The only call to find_safe_way
    else:
        best_move = follow_tail()
    if best_move == ERR:
        best_move = any_possible_move()
    # The above thought, only one direction, run one step
    if best_move != ERR: make_move(best_move)
    else:
        print(score)#Print the score after the game is over
        break
    # Control game speed
    fpsClock.tick(20)#20 looks like the speed is just right


Questions & Answers · Source Code Acquisition · Technical Exchange · Group learning please contact