Snake
In this tutorial, we’ll recreate the classic Snake game, using everything you’ve learned so far: drawing, input, and screen updates.
You’ve already mastered how to draw rectangles, check key input, and update frames. Now we’ll combine all that into your first full game!
How it works
Here’s the basic logic behind the Snake game:
- The snake is made of a list of positions (x, y), starting with 1 segment
- It moves in a direction on a grid
- If it eats the apple, the snake grows
- If it runs into itself or the wall — game over
- We update and redraw everything every frame
Grid and Layout
We divide the screen into a grid:
GRID_W = 20
GRID_H = 20
CELL = 10
This gives us a 20×20 grid, with each square being 10×10
pixels.
We center the game on the screen using:
OX = (DWIDTH - GRID_W * CELL) // 2
OY = (DHEIGHT - GRID_H * CELL) // 2
Then we define and draw the grid limits:
C_GRAY = C_RGB(200, 200, 200) # Light gray
while True:
dclear(C_WHITE)
# Draw grid border (kill zone)
drect(OX-1, OY-1, OX + GRID_W*CELL, OY-1, C_GRAY) # Top border
drect(OX-1, OY + GRID_H*CELL, OX + GRID_W*CELL, OY + GRID_H*CELL, C_GRAY) # Bottom
drect(OX-1, OY-1, OX-1, OY + GRID_H*CELL, C_GRAY) # Left border
drect(OX + GRID_W*CELL, OY-1, OX + GRID_W*CELL, OY + GRID_H*CELL, C_GRAY) # Right
So everything is aligned properly regardless of screen size.
Snake and Apple
The snake is stored as a list of coordinates:
snake = [(GRID_W//2, GRID_H//2)]
It starts in the center of the grid.
The apple is just a random position on the grid:
apple = (randint(0, GRID_W-1), randint(0, GRID_H-1))
We make sure the apple never spawns on top of the snake with place_apple()
.
Main Loop
This is the usual draw → input → update flow.
1. Drawing
We draw the background, the apple, and the snake:
dclear(C_WHITE)
draw_cell(apple[0], apple[1], C_RED)
for x, y in snake:
draw_cell(x, y, C_BLACK)
dupdate()
Each cell is drawn using draw_cell()
, which uses drect()
to draw one square.
2. Input
We read input with pollevent()
and use a dictionary to turn arrow keys into directions:
DIRS = {
KEY_UP: (0, -1),
KEY_DOWN: (0, 1),
KEY_LEFT: (-1, 0),
KEY_RIGHT: (1, 0),
}
Then we update the direction, unless the player is trying to reverse into themselves:
if ev.type == KEYEV_DOWN and ev.key in DIRS:
new_dir = DIRS[ev.key]
if (new_dir[0] != -direction[0]) or (new_dir[1] != -direction[1]):
direction = new_dir
3. Snake Movement
We calculate the new head position by adding the direction to the current head:
head = (snake[0][0] + direction[0], snake[0][1] + direction[1])
Then we check for collisions:
- If the snake hits itself or the wall →
break
- Otherwise, we add the new head to the front of the list
if head in snake or not (0 <= head[0] < GRID_W and 0 <= head[1] < GRID_H):
break # Game over
If the snake eats the apple, we place a new one. Otherwise, we pop()
the tail.
Final Code
from gint import *
from random import randint
# Grid size
GRID_W = 20
GRID_H = 20
CELL = 10
# Position offset to center the game
OX = (DWIDTH - GRID_W * CELL) // 2
OY = (DHEIGHT - GRID_H * CELL) // 2
# Directions: (dx, dy)
DIRS = {
KEY_UP: (0, -1),
KEY_DOWN: (0, 1),
KEY_LEFT: (-1, 0),
KEY_RIGHT: (1, 0),
}
C_GRAY = C_RGB(200, 200, 200) # Light gray
snake = [(GRID_W//2, GRID_H//2)]
direction = (1, 0) # Start moving right
apple = (randint(0, GRID_W-1), randint(0, GRID_H-1))
def draw_cell(x, y, color):
x1 = OX + x * CELL
y1 = OY + y * CELL
drect(x1, y1, x1 + CELL - 1, y1 + CELL - 1, color)
def place_apple():
while True:
a = (randint(0, GRID_W-1), randint(0, GRID_H-1))
if a not in snake:
return a
while True:
dclear(C_WHITE)
# Draw grid border (kill zone)
drect(OX-1, OY-1, OX + GRID_W*CELL, OY-1, C_GRAY) # Top border
drect(OX-1, OY + GRID_H*CELL, OX + GRID_W*CELL, OY + GRID_H*CELL, C_GRAY) # Bottom
drect(OX-1, OY-1, OX-1, OY + GRID_H*CELL, C_GRAY) # Left border
drect(OX + GRID_W*CELL, OY-1, OX + GRID_W*CELL, OY + GRID_H*CELL, C_GRAY) # Right
# Draw apple
draw_cell(apple[0], apple[1], C_RED)
# Draw snake
for x, y in snake:
draw_cell(x, y, C_BLACK)
dupdate()
# Read input
ev = pollevent()
if ev.type == KEYEV_DOWN and ev.key in DIRS:
new_dir = DIRS[ev.key]
# Prevent reversing
if (new_dir[0] != -direction[0]) or (new_dir[1] != -direction[1]):
direction = new_dir
elif ev.type == KEYEV_DOWN and ev.key == KEY_EXIT:
break
# Move snake
head = (snake[0][0] + direction[0], snake[0][1] + direction[1])
if head in snake or not (0 <= head[0] < GRID_W and 0 <= head[1] < GRID_H):
break # Game over
snake.insert(0, head)
if head == apple:
apple = place_apple()
else:
snake.pop()
Want to challenge yourself? Try adding:
- A score counter
- Difficulty that increases over time
- A title or game over screen
Need help building those? Ask the 👨💻 PythonExtra ChatGPT for ideas or code!