Contents
Building a Snake Game in Python with Tkinter
This tutorial walks you through a complete Snake game written in Python using Tkinter.
It is designed for beginners who want to learn:
- GUI programming with Tkinter
- Game loops and state management
- Keyboard handling
- Object‑oriented Python
Requirements
- Python 3.8+
- Tkinter (comes preinstalled with Python on most systems)
Run the game using:
python main.py
Game Features
- Arrow‑key movement
- Pause / Resume (
Pkey or button) - Score tracking
- Start / Reset buttons
- Collision detection
- Wrap‑around walls
- Clean object‑oriented structure
Core Concepts Explained
The Direction Enum
from enum import Enum
class Direction(Enum):
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
Why this matters:
Enums prevent invalid directions and make movement logic readable and safe.
Game Grid Logic
Each cell is a square in a 20Ă—20 grid:
self.cell_size = 20
self.grid_width = 20
self.grid_height = 20
The snake moves one cell at a time, not pixel‑by‑pixel — a common technique in grid‑based games.
The Game Loop (after())
self.root.after(self.game_speed, self.move_snake)
Tkinter doesn’t have a traditional while loop for games.
Instead, it schedules updates using after() — this keeps the UI responsive.
Snake Movement Logic
new_head = (
(head_x + dx) % self.grid_width,
(head_y + dy) % self.grid_height
)
Why % modulo?
This allows the snake to wrap around the screen, instead of crashing into walls.
Collision Detection
if new_head in self.snake:
self.game_over()
The game ends if the snake collides with itself — a classic Snake rule.
Drawing with Canvas
self.canvas.create_rectangle(...)
self.canvas.create_oval(...)
Tkinter’s Canvas lets you draw:
- Rectangles → Snake body
- Ovals → Food
- Text → Game Over screen
Full Game Code
import tkinter as tk
import random
from enum import Enum
class Direction(Enum):
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
class SnakeGame:
def __init__(self, root):
self.root = root
self.root.title("Snake Game")
self.root.resizable(False, False)
# Game settings
self.cell_size = 20
self.grid_width = 20
self.grid_height = 20
self.game_speed = 100 # milliseconds
# Calculate window size
self.canvas_width = self.grid_width * self.cell_size
self.canvas_height = self.grid_height * self.cell_size
# Create canvas
self.canvas = tk.Canvas(
root,
width=self.canvas_width,
height=self.canvas_height,
bg="black",
highlightthickness=0
)
self.canvas.pack(padx=10, pady=10)
# Create score label
self.score_label = tk.Label(root, text="Score: 0", font=("Arial", 14))
self.score_label.pack()
# Create buttons frame
button_frame = tk.Frame(root)
button_frame.pack(pady=5)
# Control buttons
self.start_button = tk.Button(button_frame, text="Start", command=self.start_game)
self.start_button.pack(side=tk.LEFT, padx=5)
self.pause_button = tk.Button(button_frame, text="Pause", command=self.toggle_pause, state=tk.DISABLED)
self.pause_button.pack(side=tk.LEFT, padx=5)
self.reset_button = tk.Button(button_frame, text="Reset", command=self.reset_game)
self.reset_button.pack(side=tk.LEFT, padx=5)
# Initialize game state
self.reset_game()
# Bind keyboard events
self.root.focus_set()
self.root.bind("<KeyPress>", self.handle_keypress)
# Draw initial state
self.draw()
def reset_game(self):
# Initial snake position (centered)
start_x = self.grid_width // 2
start_y = self.grid_height // 2
self.snake = [(start_x, start_y)]
# Initial direction
self.direction = Direction.RIGHT
self.next_direction = self.direction
# Place first food
self.place_food()
# Reset score
self.score = 0
self.update_score()
# Reset game state
self.is_paused = False
self.is_game_over = False
self.start_button.config(state=tk.NORMAL)
self.pause_button.config(state=tk.DISABLED)
# Clear canvas and draw initial state
self.canvas.delete(tk.ALL)
self.draw()
def place_food(self):
while True:
x = random.randint(0, self.grid_width - 1)
y = random.randint(0, self.grid_height - 1)
if (x, y) not in self.snake:
self.food = (x, y)
break
def move_snake(self):
if self.is_paused or self.is_game_over:
return
# Update direction
self.direction = self.next_direction
# Calculate new head position
head_x, head_y = self.snake[0]
dx, dy = self.direction.value
new_head = ((head_x + dx) % self.grid_width, (head_y + dy) % self.grid_height)
# Check for collision with self
if new_head in self.snake:
self.game_over()
return
# Add new head
self.snake.insert(0, new_head)
# Check if food is eaten
if new_head == self.food:
self.score += 10
self.update_score()
self.place_food()
else:
# Remove tail if no food eaten
self.snake.pop()
# Draw updated game state
self.draw()
# Schedule next move
self.root.after(self.game_speed, self.move_snake)
def handle_keypress(self, event):
key = event.keysym
if key == "Up" and self.direction != Direction.DOWN:
self.next_direction = Direction.UP
elif key == "Down" and self.direction != Direction.UP:
self.next_direction = Direction.DOWN
elif key == "Left" and self.direction != Direction.RIGHT:
self.next_direction = Direction.LEFT
elif key == "Right" and self.direction != Direction.LEFT:
self.next_direction = Direction.RIGHT
elif key.lower() == 'p':
self.toggle_pause()
def toggle_pause(self):
self.is_paused = not self.is_paused
if self.is_paused:
self.pause_button.config(text="Resume")
else:
self.pause_button.config(text="Pause")
self.move_snake()
def start_game(self):
self.start_button.config(state=tk.DISABLED)
self.pause_button.config(state=tk.NORMAL)
self.move_snake()
def game_over(self):
self.is_game_over = True
self.canvas.create_text(
self.canvas_width // 2,
self.canvas_height // 2,
text=f"GAME OVER\nScore: {self.score}",
fill="red",
font=("Arial", 24),
justify=tk.CENTER
)
self.start_button.config(state=tk.NORMAL)
self.pause_button.config(state=tk.DISABLED)
def update_score(self):
self.score_label.config(text=f"Score: {self.score}")
def draw(self):
# Clear canvas
self.canvas.delete(tk.ALL)
# Draw snake
for i, (x, y) in enumerate(self.snake):
color = "lime green" if i == 0 else "green" # Head is different color
self.canvas.create_rectangle(
x * self.cell_size,
y * self.cell_size,
(x + 1) * self.cell_size,
(y + 1) * self.cell_size,
fill=color,
outline="dark green"
)
# Draw food
fx, fy = self.food
self.canvas.create_oval(
fx * self.cell_size,
fy * self.cell_size,
(fx + 1) * self.cell_size,
(fy + 1) * self.cell_size,
fill="red",
outline="dark red"
)
if __name__ == "__main__":
root = tk.Tk()
game = SnakeGame(root)
root.mainloop()
Possible Extensions
Try improving the game by adding:
- Difficulty levels (speed increase)
- Sound effects
- High‑score saving (file or database)
- Walls and obstacles
- Color themes
- Multiplayer snake đź‘€
Common Questions (FAQ)
Q: Why use Tkinter instead of Pygame?
Tkinter is simpler, built‑in, and perfect for learning GUI + logic fundamentals.
Q: Why not use a while loop?
Tkinter uses an event‑driven loop. after() prevents freezing the UI.
Q: How does pause work?
The game simply stops scheduling the next move_snake() call.
Q: Can I turn off wrap‑around walls?
Yes! Replace modulo logic with boundary collision detection.
Final Thoughts
This project teaches real‑world Python skills:
- Structuring programs
- Managing state
- Event‑driven programming
- GUI rendering
If you can build this — you’re officially past beginner level 🎉
Happy coding!