Making a Nokia Snake Game in Python

nokia snake game

In this tutorial, we are creating a classic Nokia snake game in Python. It has features such as screen wrapping, progressive difficulty (speeds up as you score) & with clean, modular code structure. If you are interested in learning more about game development, you may find my Pong game tutorial, Side-scrolling shooter & racing game tutorial helpful (but they are for Godot engine and not for PyGame). Now lets begin with this tutorial!

Prerequisites: For this tutorial, you need to have basic Python knowledge. You will have to install PyGame. For this, type following in your terminal (or Command Prompt):

pip install pygame

Breakdown of Game Structure

Let’s break down our game into smaller pieces:

  1. Game window setup
  2. Snake movement
  3. Food generation
  4. Collision detection
  5. Scoring system

Now let’s start with the code:

import pygame
import random
from enum import Enum
from typing import List, Tuple

# Initialize Pygame
pygame.init()


# Constants
WINDOW_SIZE = 600          # Total window size in pixels
GRID_SIZE = 20            # Size of each grid square
GRID_COUNT = WINDOW_SIZE // GRID_SIZE  # Number of grid squares in each row/column

# Colors (RGB format)
BLACK = (10, 10, 10)      # Almost black background
RED = (255, 0, 0)         # Snake color
DARK_RED = (200, 0, 0)    # Snake body color
FOOD_COLOR = (255, 50, 50) # Food color

In above code, WINDOW_SIZE determines how big our game window will be. GRID_SIZE defines how big each “cell” in our game is. GRID_COUNT calculates how many cells fit in our window. Colors are defined in RGB format (Red, Green, Blue) from 0-255.

Creating the Direction System

class Direction(Enum):
    """Enum for storing directional constants"""
    UP = 0
    RIGHT = 1
    DOWN = 2
    LEFT = 3

We use an Enum to create a set of named constants. This makes our code more readable and prevents errors. Instead of using numbers, we can use meaningful names like Direction.UP

Creating the Snake Segment Class

class SnakeSegment:
    """Class representing a segment of the snake"""
    def __init__(self, position: Tuple[int, int]):
        self.position = position

    def draw(self, surface: pygame.Surface):
        """Draw the snake segment"""
        rect = pygame.Rect(
            self.position[0] * GRID_SIZE,  # X position
            self.position[1] * GRID_SIZE,  # Y position
            GRID_SIZE - 1,                 # Width
            GRID_SIZE - 1                  # Height
        )
        pygame.draw.rect(surface, RED, rect)

Each segment of our snake is an object of this class. position is a tuple of (x, y) coordinates on our grid. draw method creates a rectangle for each segment. We subtract 1 from GRID_SIZE to create small gaps between segments.

Creating the Food Class

class Food:
    """Class representing the food"""
    def __init__(self):
        self.position = self.generate_position()

    def generate_position(self) -> Tuple[int, int]:
        """Generate a random position for the food"""
        return (
            random.randint(0, GRID_COUNT - 1),
            random.randint(0, GRID_COUNT - 1)
        )

    def draw(self, surface: pygame.Surface):
        """Draw the food"""
        rect = pygame.Rect(
            self.position[0] * GRID_SIZE,
            self.position[1] * GRID_SIZE,
            GRID_SIZE - 1,
            GRID_SIZE - 1
        )
        pygame.draw.rect(surface, FOOD_COLOR, rect)

Food appears at random positions on the grid. generate_position creates random coordinates within our game bounds. Food is drawn similarly to snake segments but with a different color.

Creating the Snake Class

class Snake:
    """Class representing the snake"""
    def __init__(self):
        self.reset()

    def reset(self):
        """Reset the snake to initial state"""
        self.direction = Direction.RIGHT
        self.segments = [
            SnakeSegment((GRID_COUNT // 2, GRID_COUNT // 2))
        ]
        self.growing = False

The snake starts in the middle of the screen. Initially moves right. Contains a list of segments (starting with one). growing flag determines if the snake should grow after eating.

Now let’s add movement and control methods:

def update(self) -> bool:
    """Update snake position and return True if game should continue"""
    head = self.segments[0].position
    
    # Calculate new head position
    new_head = list(head)
    if self.direction == Direction.UP:
        new_head[1] -= 1
    elif self.direction == Direction.DOWN:
        new_head[1] += 1
    elif self.direction == Direction.LEFT:
        new_head[0] -= 1
    elif self.direction == Direction.RIGHT:
        new_head[0] += 1

    # Wrap around screen
    new_head[0] = new_head[0] % GRID_COUNT
    new_head[1] = new_head[1] % GRID_COUNT
    
    # Check self-collision
    if tuple(new_head) in [segment.position for segment in self.segments]:
        return False

    # Add new head
    self.segments.insert(0, SnakeSegment(tuple(new_head)))
    
    # Remove tail if not growing
    if not self.growing:
        self.segments.pop()
    else:
        self.growing = False
        
    return True

update moves the snake by adding a new head and removing the tail. Screen wrapping is handled by using modulo (%). Returns False if snake collides with itself.

Creating the Main Game Class

class Game:
    """Main game class"""
    def __init__(self):
        self.screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
        pygame.display.set_caption('Nokia Snake Game')
        self.clock = pygame.time.Clock()
        self.snake = Snake()
        self.food = Food()
        self.score = 0
        self.game_speed = 10

Creates the game window. Initializes snake and food. Sets up game speed and scoring.

Add the game loop methods:

def handle_input(self):
    """Handle keyboard input"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            return False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                self.snake.change_direction(Direction.UP)
            elif event.key == pygame.K_DOWN:
                self.snake.change_direction(Direction.DOWN)
            elif event.key == pygame.K_LEFT:
                self.snake.change_direction(Direction.LEFT)
            elif event.key == pygame.K_RIGHT:
                self.snake.change_direction(Direction.RIGHT)
    return True

def run(self):
    """Main game loop"""
    running = True
    while running:
        running = self.handle_input()
        self.update()
        self.draw()
        self.clock.tick(self.game_speed)

handle_input processes keyboard events. run is the main game loop that keeps everything running. clock.tick controls game speed.

Running the Game

Finally, add this at the bottom of your file:

if __name__ == "__main__":
    game = Game()
    game.run()
    pygame.quit()

Expanding the Game

For your own exercise, try adding following features on your own.

  • High score system
  • Different types of food
  • Sound effects
  • Menu system

Full Source Code

import pygame
import random
from enum import Enum
from typing import List, Tuple

# Initialize Pygame
pygame.init()

# Constants
WINDOW_SIZE = 600
GRID_SIZE = 20
GRID_COUNT = WINDOW_SIZE // GRID_SIZE

# Colors
BLACK = (10, 10, 10)  # Very dark grey, almost black like Nokia screen
RED = (255, 0, 0)
DARK_RED = (200, 0, 0)  # For snake body
FOOD_COLOR = (255, 50, 50)

class Direction(Enum):
    """Enum for storing directional constants"""
    UP = 0
    RIGHT = 1
    DOWN = 2
    LEFT = 3

class SnakeSegment:
    """Class representing a segment of the snake"""
    def __init__(self, position: Tuple[int, int]):
        self.position = position

    def draw(self, surface: pygame.Surface):
        """Draw the snake segment"""
        rect = pygame.Rect(
            self.position[0] * GRID_SIZE,
            self.position[1] * GRID_SIZE,
            GRID_SIZE - 1,
            GRID_SIZE - 1
        )
        pygame.draw.rect(surface, RED, rect)

class Food:
    """Class representing the food"""
    def __init__(self):
        self.position = self.generate_position()

    def generate_position(self) -> Tuple[int, int]:
        """Generate a random position for the food"""
        return (
            random.randint(0, GRID_COUNT - 1),
            random.randint(0, GRID_COUNT - 1)
        )

    def draw(self, surface: pygame.Surface):
        """Draw the food"""
        rect = pygame.Rect(
            self.position[0] * GRID_SIZE,
            self.position[1] * GRID_SIZE,
            GRID_SIZE - 1,
            GRID_SIZE - 1
        )
        pygame.draw.rect(surface, FOOD_COLOR, rect)

class Snake:
    """Class representing the snake"""
    def __init__(self):
        self.reset()

    def reset(self):
        """Reset the snake to initial state"""
        self.direction = Direction.RIGHT
        self.segments = [
            SnakeSegment((GRID_COUNT // 2, GRID_COUNT // 2))
        ]
        self.growing = False

    def update(self) -> bool:
        """Update snake position and return True if game should continue"""
        # Get the current head position
        head = self.segments[0].position
        
        # Calculate new head position based on direction
        new_head = list(head)
        if self.direction == Direction.UP:
            new_head[1] -= 1
        elif self.direction == Direction.DOWN:
            new_head[1] += 1
        elif self.direction == Direction.LEFT:
            new_head[0] -= 1
        elif self.direction == Direction.RIGHT:
            new_head[0] += 1

        # Wrap around screen
        new_head[0] = new_head[0] % GRID_COUNT
        new_head[1] = new_head[1] % GRID_COUNT
        
        # Check for collision with self
        if tuple(new_head) in [segment.position for segment in self.segments]:
            return False

        # Add new head
        self.segments.insert(0, SnakeSegment(tuple(new_head)))
        
        # Remove tail if not growing
        if not self.growing:
            self.segments.pop()
        else:
            self.growing = False
            
        return True

    def draw(self, surface: pygame.Surface):
        """Draw the snake"""
        for segment in self.segments:
            segment.draw(surface)

    def grow(self):
        """Mark the snake to grow on next update"""
        self.growing = True

    def change_direction(self, new_direction: Direction):
        """Change the snake's direction if valid"""
        opposite_directions = {
            Direction.UP: Direction.DOWN,
            Direction.DOWN: Direction.UP,
            Direction.LEFT: Direction.RIGHT,
            Direction.RIGHT: Direction.LEFT
        }
        if new_direction != opposite_directions.get(self.direction):
            self.direction = new_direction

class Game:
    """Main game class"""
    def __init__(self):
        self.screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
        pygame.display.set_caption('Nokia Snake Game')
        self.clock = pygame.time.Clock()
        self.snake = Snake()
        self.food = Food()
        self.score = 0
        self.game_speed = 10

    def handle_input(self):
        """Handle keyboard input"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    self.snake.change_direction(Direction.UP)
                elif event.key == pygame.K_DOWN:
                    self.snake.change_direction(Direction.DOWN)
                elif event.key == pygame.K_LEFT:
                    self.snake.change_direction(Direction.LEFT)
                elif event.key == pygame.K_RIGHT:
                    self.snake.change_direction(Direction.RIGHT)
        return True

    def update(self):
        """Update game state"""
        if not self.snake.update():
            self.reset_game()
            return

        # Check for food collision
        if self.snake.segments[0].position == self.food.position:
            self.snake.grow()
            self.food.position = self.food.generate_position()
            self.score += 1
            # Increase game speed every 5 points
            if self.score % 5 == 0:
                self.game_speed += 1

    def draw(self):
        """Draw the game state"""
        self.screen.fill(BLACK)
        self.snake.draw(self.screen)
        self.food.draw(self.screen)
        pygame.display.flip()

    def reset_game(self):
        """Reset the game state"""
        self.snake.reset()
        self.food = Food()
        self.score = 0
        self.game_speed = 10

    def run(self):
        """Main game loop"""
        running = True
        while running:
            running = self.handle_input()
            self.update()
            self.draw()
            self.clock.tick(self.game_speed)

if __name__ == "__main__":
    game = Game()
    game.run()
    pygame.quit()

Thank you for reading <3

Leave a Reply

Your email address will not be published. Required fields are marked *