# Snake from the viewpoint of the snake

I wrote a little game of snake that where you can see the field in which the snake moves fixed and you can also see the "viewpoint" of the snake, which is basically calculating the positions of all the things in the board taking the snake's head as the center.

Right now I'm doing it through two for loops in the following way:

for x in range(ROWS):
n_x = (x - snake_head - center) % ROWS
for y in range(ROWS):
n_y = (y - snake_head - center) % ROWS
snake_view.grid[n_x, n_y] = field.grid[x,y]


ROWS is the number of rows the field's grid has (currently 15) and it has to be an odd number so there's a center. snake_head is a numpy array of shape (2,) which has the coordinates of head in the field grid. field.grid is a numpy array of shape (ROWS, ROWS) that holds the value of what is in each square of the grid, -1 for snake body, -2 for snake head, 0 for nothing or empty, 1 for a fruit or snack.

snake_view.grid is a numpy array of the same dimensions as field.grid but has the viewpoint as the snake sees it.

This works fine, as you can see in the code that I have pasted below (it is in two different .py files but I have pasted it all into one so it can be copy/pasted easily). But since I want to use this game to train an AI, I need help optimizing it.

Is there a way to vectorize this?

import pygame
import time
import random
import numpy as np

WIN_WIDTH, WIN_HEIGHT = 800, 720
GRID_WIDTH = 400
ROWS = 15
GRID_SPACE = GRID_WIDTH // ROWS

class Snake:
"""
Snake Object
"""
def __init__(self, x, y, body_color = (255,0,0), head_color = (255,0,255)):
"""
Initializes the Snake Object on a given coordinate
"""
# Position is a list of tuples where each tuple
# is a coordinate of the position of a certain
# block in the field
self.positions = np.array([np.array([x, y])])
self.direction = np.array([0, 1])
# dir is an array of [x,y] values for the direction
# of the snake's movement
self.vel = 1
self.body_color = body_color
self.eaten = False

# Returns the position of the head
return self.positions[-1]

def move(self):
# Funcion that moves the snake in a particular direction
if not self.eaten:
self.positions[:-1] = self.positions[1:]
else:
self.eaten = False
self.positions = np.mod(self.positions, ROWS)

def bite(self):
return np.unique(self.positions, axis=0).shape != self.positions.shape

def eat(self):
self.eaten = True

class Fruit:
"""
Fruits the snake can eat
"""
def __init__(self, grid):
x, y = random.sample(grid, 1)
grid.remove((x,y))
self.position = np.array([x, y])

class Field():
COLORS = {
-1: (255, 0, 0),
-2: (255, 0, 255),
0: (50,50,50),
1: (0,255,0)
}
available_grid = set(
(i,j)
for i in range(ROWS)
for j in range(ROWS)
)
available_grid.remove((5,5))
def __init__(self):
self.grid = np.zeros((ROWS, ROWS), dtype=np.int8)

def empty_grid(self):
self.grid = np.zeros((ROWS, ROWS), dtype=np.int8)

def update_grid(self, snake=None, fruits=None):
self.empty_grid()
self.available_grid = set(
(i,j)
for i in range(ROWS)
for j in range(ROWS)
)
for pos in snake.positions[:-1]:
self.grid[pos, pos] = -1
if (pos, pos) in self.available_grid:
self.available_grid.remove((pos, pos))
self.grid[pos, pos] = -2
if (pos, pos) in self.available_grid:
self.available_grid.remove((pos, pos))
for fruit in fruits:
self.grid[fruit.position, fruit.position] = 1
if (fruit.position, fruit.position) in self.available_grid:
self.available_grid.remove((fruit.position, fruit.position))

def draw(self, win, offset_x = 0, offset_y = 0):
x, y = 0, 0
for row in range(ROWS):
pos_x = x * GRID_SPACE + offset_x
for col in range(ROWS):
pos_y = y * GRID_SPACE + offset_y
pygame.draw.rect(win, self.COLORS[self.grid[x,y]], (
pos_x + 2,
pos_y + 2,
GRID_SPACE - 2,
GRID_SPACE -2
))
y+=1
x+=1
y=0
pygame.font.init()
STAT_FONT = pygame.font.SysFont("arial", 50)
SCORE = 0
try:
with open("HIGH_SCORE.txt", "r") as f:
if len(HIGH_SCORE) > 0:
HIGH_SCORE = int(HIGH_SCORE)
else:
HIGH_SCORE = 0
except FileNotFoundError:
HIGH_SCORE = 0

def record_hs():
if SCORE > HIGH_SCORE:
with open("HIGH_SCORE.txt", "w") as f:
f.write(str(SCORE))

def draw_window(win, field, snake_view=None):
win.fill((0,0,0))
pygame.draw.line(win, (150, 150, 150), (GRID_WIDTH, 0), (GRID_WIDTH, GRID_WIDTH))
field.draw(win)
snake_view.draw(win, offset_x=GRID_WIDTH)
text = STAT_FONT.render(f"HIGH SCORE: {HIGH_SCORE}", 1,(255,255,255))
win.blit(text, (10,10+GRID_WIDTH))
text = STAT_FONT.render(f"Score: {SCORE}", 1,(255,255,255))
win.blit(text, (10,10+GRID_WIDTH+text.get_height()))
pygame.display.update()

def main():
# Initializes the screen
center = ROWS // 2 + 1 if ROWS % 2 == 1 else ROWS // 2
n_fruits = 5
global HIGH_SCORE, SCORE
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
field = Field()
snake = Snake(5,5)
snake_view = Field()
fruits = [Fruit(field.available_grid) for i in range(n_fruits)]
clock = pygame.time.Clock()
run = True
while run:
clock.tick(8)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
quit()
# Get the direction the snake should turn from keyboard
keys = pygame.key.get_pressed()
if keys[pygame.K_DOWN]:
if snake.direction != -1:
snake.direction = 1
snake.direction = 0
elif keys[pygame.K_LEFT]:
if snake.direction != 1:
snake.direction = -1
snake.direction = 0
elif keys[pygame.K_UP]:
if snake.direction != 1:
snake.direction = -1
snake.direction = 0
elif keys[pygame.K_RIGHT]:
if snake.direction != -1:
snake.direction = 1
snake.direction = 0

snake.move()
if snake.bite():
record_hs()
run = False
rem = None
d = np.sqrt(np.square(a) + np.square(a))

for fruit in fruits:
if abs(snake_head - fruit.position) < 1 and abs(snake_head - fruit.position) < 1:
rem = fruit
if rem:
fruits.remove(rem)
fruits.append(Fruit(field.available_grid))
snake.eat()
SCORE += 1

field.update_grid(snake, fruits)
for x in range(ROWS):
n_x = (x - snake_head - center) %ROWS
for y in range(ROWS):
n_y = (y - snake_head - center) % ROWS
snake_view.grid[n_x, n_y] = field.grid[x,y]

draw_window(win, field, snake_view)

if __name__ == "__main__":
main()


shift = center - snake_head