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~
If the software is not installed, install the software first. If the module is not installed, install the pygame module.
pip install pygame
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 bias in pygame output, divisor (/ /) must be taken instead of simple division (/)
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 HEAD = 0
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
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 =  * FIELD_SIZE #[0,0,0,......] snake =  * (FIELD_SIZE+1) snake[HEAD] = 1*WIDTH+1 snake_size = 1
The temporary variable corresponding to the above variable is used when the snake moves tentatively.
tmpboard =  * FIELD_SIZE tmpsnake =  * (FIELD_SIZE+1) tmpsnake[HEAD] = 1*WIDTH+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
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 =  * 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): 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 following functions are assigned first and compared with each other min = pboard[psnake[HEAD]+mov[i]] 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 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 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): 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
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) snake[HEAD] += pbest_move p = snake[HEAD] 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)) pygame.draw.rect(playSurface,headColour,Rect(18*(p//WIDTH), 18*(p%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) 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 snakehead is not the location of food board[snake[HEAD]] = SNAKE # New snake head 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) tmpsnake[HEAD] += move # Add a new position in front of the snake head # 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 if tmpsnake[HEAD] == food: 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[HEAD]] = SNAKE 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
Define a variable to control the speed of the game
fpsClock = pygame.time.Clock() establish pygame Display layer
playSurface = pygame.display.set_mode((270,270)) pygame.display.set_caption('Greedy snake')
Draw pygame display layer
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!
Python learning roadmap:( Route)