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
Section titled “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
Section titled “Grid and Layout”We divide the screen into a grid:
GRID_W = 20GRID_H = 20CELL = 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) // 2OY = (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
Section titled “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
Section titled “Main Loop”This is the usual draw → input → update flow.
1. Drawing
Section titled “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
Section titled “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
Section titled “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
Section titled “Final Code”from gint import *from random import randint
# Grid sizeGRID_W = 20GRID_H = 20CELL = 10
# Position offset to center the gameOX = (DWIDTH - GRID_W * CELL) // 2OY = (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 rightapple = (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!