constants.py:
import pyxel
# Geometry
WIDTH = 30
HEIGHT = 30
# Frame rate
FPS = 10
# Colors
BLACK = 0
WHITE = 7
PINK = 8
DARK_GREEN = 3
LIGHT_GREEN = 11
# Directions
UP = [0, -1]
RIGHT = [1, 0]
DOWN = [0, 1]
LEFT = [-1, 0]
ARROW_KEYS = [
pyxel.KEY_UP,
pyxel.KEY_DOWN,
pyxel.KEY_LEFT,
pyxel.KEY_RIGHT
]
maze.py:
from constants import UP, RIGHT, DOWN, LEFT
def maze_to_graph(maze):
vertices = set(maze)
edges = set()
weights = {}
for vertex in vertices:
x, y = vertex
for (dx, dy) in [UP, RIGHT, DOWN, LEFT]:
neighbor = (x + dx, y + dy)
if neighbor in vertices:
edge = (vertex, neighbor)
edges.add(edge)
weights[edge] = 1
return (vertices, edges, weights)
def path_from(maze, source):
vertices, edges, _ = maze_to_graph(maze)
todo = set()
done = set()
path = {}
if source in maze:
todo.add(source)
path[source] = [source]
while todo:
current = todo.pop()
neighbors = {
v for v in vertices
if (current, v) in edges
}
for n in neighbors:
if n not in done and n not in todo:
path[n] = path[current] + [n]
todo.add(n)
done.add(current)
return path
snake.py:
import pyxel
from constants import *
from maze import path_from
def spawn_new_rocks():
global rocks
rocks = []
for i in range(WIDTH):
for j in range(HEIGHT):
if (i+j) % 5 == 0 and (i-j) % 11 == 0:
rocks.append((i, j))
def spawn_new_snake():
global snake_geometry, snake_direction
snake_geometry = [
(10, 15),
(11, 15),
(12, 15),
]
snake_direction = RIGHT
def spawn_new_fruit():
global fruit
while True:
fruit = (pyxel.rndi(0, WIDTH-1), pyxel.rndi(0, HEIGHT-1))
if fruit not in snake_geometry and fruit not in rocks:
break
def spawn_everything():
spawn_new_rocks()
spawn_new_snake()
spawn_new_fruit()
path_to_fruit = None
def compute_path_to_fruit():
head, body = snake_geometry[-1], snake_geometry[:-1]
maze = {(i, j) for j in range(HEIGHT) for i in range(WIDTH)}
maze = maze - set(rocks)
maze = maze - set(body)
path_map = path_from(maze, head)
if fruit in path_map:
return path_map[fruit]
else:
return None
def select_move():
global snake_direction, path_to_fruit
if path_to_fruit is None or len(path_to_fruit) == 1:
path_to_fruit = compute_path_to_fruit()
if path_to_fruit is not None:
current, next = path_to_fruit[0], path_to_fruit[1]
snake_direction = (next[0] - current[0], next[1] - current[1])
del path_to_fruit[0]
def crash(head):
"""
Returns True if the snake crashes:
- into itself,
- into a rock,
- or into the arena wall.
"""
return (
head in snake_geometry
or head in rocks
or (
head[0] < 0
or head[0] > WIDTH-1
or head[1] < 0
or head[1] > HEIGHT-1
)
)
def snake_move():
global snake_geometry, fruit
snake_head = snake_geometry[-1]
new_snake_head = (
snake_head[0] + snake_direction[0],
snake_head[1] + snake_direction[1],
)
if crash(new_snake_head): # 💀
# 🐍 keep the old head and shrink the tail
snake_geometry = snake_geometry[1:-1] + [snake_head]
elif new_snake_head == fruit: # 🍓
# 🐍 move the head and grow the tail
snake_geometry = snake_geometry + [new_snake_head]
spawn_new_fruit()
else:
# 🐍 move the head; the tail follows
snake_geometry = snake_geometry[1:] + [new_snake_head]
def update():
select_move()
snake_move()
def display(color, pixels=None):
if pixels is None:
pyxel.cls(color)
elif len(pixels) >= 1 and isinstance(list(pixels)[0], int):
display(color, [pixels])
else:
for x, y in pixels:
pyxel.pset(x, y, color)
def draw():
display(WHITE)
snake_body = snake_geometry[:-1]
snake_head = snake_geometry[-1]
display(DARK_GREEN, snake_body)
display(LIGHT_GREEN, snake_head)
display(BLACK, rocks)
display(PINK, fruit)
pyxel.init(WIDTH, HEIGHT, fps=FPS)
spawn_everything()
pyxel.run(update, draw)