snake_ai_minimal

The game screen appears here if your browser supports the Canvas API.

Attribution

Snakes, and an introduction to recursive backtracking, pages 50-55, by Andrew Gillett.

Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.

Original Python code


import pgzrun
from random import randint

WIDTH,HEIGHT = 1200,448
MAX_DEPTH = 20
GRID_SQ_SIZE = 64
TILE_SPRITES = {'X': 'gridblock', '.': 'wall'}
DIRECTIONS = ((1, 0), (-1, 0), (0, 1), (0, -1))

GRID = ['XXXXXXXXXXXXXXXXXXX',
        'X..  .   ..       X',
        'X.X X.X X X X X X X',
        'X .. ..  . ..     X',
        'X X X X X X X X X X',
        'X  .   . ..   .   X',
        'XXXXXXXXXXXXXXXXXXX']

def snake_step_score(score, depth, snake):
   head_pos = snake[0]
   square = GRID[head_pos[1]][head_pos[0]]
   if square == ' ':
       snake = snake[:-1]
   if square == 'X' or head_pos in snake[1:]:
      return score/2
   elif square == '.':
      score += 2
   else:
      score += 1

   if depth <= 0:
      return score

   best_score = 0
   for dir in DIRECTIONS:
      new_head_pos = (head_pos[0]+dir[0],
                      head_pos[1]+dir[1])
      result = snake_step_score(score, depth-1,
                          [new_head_pos]+snake)
      best_score = max(result, best_score)

   return best_score

snake = [(1,1)]

def change_grid_pos(row,col,char):
   grid_row = GRID[row]
   new_row = grid_row[:col] + char + grid_row[col+1:]
   GRID[row] = new_row

def update_snake():
   global snake
   head_pos = snake[0]
   best_score, best_snake = 0, None
   new_pos_eats_wall = False
   for dir in DIRECTIONS:
      new_head_pos = (head_pos[0] + dir[0],
                      head_pos[1] + dir[1])
      new_snake = [new_head_pos] + snake
      result = snake_step_score(0, MAX_DEPTH, new_snake)
      if result > best_score:
         best_score = result
         best_snake = new_snake
         new_pos_eats_wall = GRID[new_head_pos[1]][new_head_pos[0]] == '.'

   if best_snake != None:
      snake = best_snake
      if not new_pos_eats_wall:
         snake = snake[:-1]
      else:
         new_head_pos = snake[0]
         change_grid_pos(new_head_pos[1],
                         new_head_pos[0], ' ')

def update():
   update_snake()

def draw():
   screen.clear()
   for row in range(len(GRID)):
      for col in range(len(GRID[row])):
         square = GRID[row][col]
         if square in TILE_SPRITES:
            x = col * GRID_SQ_SIZE
            y = row * GRID_SQ_SIZE
            screen.blit(TILE_SPRITES[square], (x, y))
         pos = (col, row)
         if pos in snake:
            if pos == snake[0]:
                image = 'snakehead'
            else:
                image = 'snakebody2'
            screen.blit(image, (col * GRID_SQ_SIZE,
                                row * GRID_SQ_SIZE))

pgzrun.go()