How to create the Snake Game with Python


Remember that snake game from back in the day. Today, we're diving into the world of Python programming to create our very own version of this iconic game.

What is the Snake Game and How it Works

The snake game is a simple yet addictive game where the player controls a snake that moves around a bordered area, gobbling up food (often represented as dots or other objects) to grow longer. The main challenge is to avoid collisions with the walls of the playing area or with the snake's own body. As the snake consumes more food, it grows longer, making it progressively harder to navigate without crashing into something.

Here's a basic rundown of how the game typically works:

Initialization: The game starts with a snake consisting of a single segment and randomly placed food within the play area.

Movement: The snake moves continuously in the direction specified by the player (up, down, left, or right).

Eating: When the snake's head collides with a food item, it "eats" the food, grows longer, and scores points.

Collision Detection: The game checks for collisions between the snake's head and the walls of the play area or its own body. If a collision occurs, the game ends.

Game Over: When the snake collides with a wall or itself, the game ends, and the player's score is displayed.

Project Specifications

Now, let's outline the specifications for building our snake game in Python:

Graphical Interface: We'll use a graphical interface library such as Pygame or Tkinter to create the game window and handle user input.

Snake Movement: Implement logic to control the snake's movement based on user input.

Food Generation: Randomly generate food within the play area for the snake to consume.

Collision Detection: Detect collisions between the snake's head and the walls or its own body.

Score Tracking: Keep track of the player's score based on the number of food items eaten.

Game Over: Display a game over screen when the snake collides with a wall or itself.

Unfortunately, we are going to make use of a blocks for simpliity.

With these specifications in mind, we're ready to dive into coding our very own snake game in Python! Let's get started!

Game Mechanics Explained

Array of Player Information

We'll maintain an array to store information about each segment of the snake's body. Each element in this array will represent a segment and contain the following information:

  • Segment Location: The coordinates (x, y) of the segment on the game grid.
  • Direction: The direction in which the segment should move next.

Segment Movement and Direction

Notice that in snake games different segments of the snake is moving in different directions, in this implementation, each segment can move independently based on the direction assigned to it. This creates a segmented movement effect where different parts of the snake follow the movements of the preceding segments. The animation above clearly depicts this with the use of arrows.

Adding New Segments

When the snake eats food and grows longer, a new segment is added to the end of the snake. The direction of this new segment is set to match the direction of the last segment in the snake's body. This ensures that the new segment follows the movement direction of the rest of the snake.

Trail-like Movement

As the game progresses and the snake moves, each segment follows the direction of the segment before it, creating a trail-like movement effect. This trail effect mimics the behavior of a real snake, where each part of its body follows the movements of the head.

Direction Assignment

With each game tick, the direction assigned to each segment of the snake's body moves down the snake, ensuring that each segment follows the movement of the segment before it. This sequential assignment of directions creates the illusion of a continuous, slithering motion.

Code Explained

First, we need to define some initial variables and import necessary modules. Our game will be 16x12, and each block will have a size of 50x50 pixels. The clock speed variable specifies how fast the game will move.
import pygame
import sys
import random

# Initialize pygame
pygame.init()

# Constants
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

# Variables
block_size = 50
num_blocks_x = 16
num_blocks_y = 12

# Calculate window width and height
SCREEN_WIDTH = block_size * num_blocks_x
SCREEN_HEIGHT = block_size * num_blocks_y

# Clock speed
clock_speed = 10  # frames per second
This method creates grids on the screen to provide visual boundaries and aid in determining the snake's position relative to game objects or borders.
def draw_grid(screen):
    for x in range(0, SCREEN_WIDTH, block_size):
        pygame.draw.line(screen, WHITE, (x, 0), (x, SCREEN_HEIGHT))
    for y in range(0, SCREEN_HEIGHT, block_size):
        pygame.draw.line(screen, WHITE, (0, y), (SCREEN_WIDTH, y))
This method checks if the snake collides with a border. It iterates through each segment of the snake and verifies whether any segment has moved past the borders of the game area using simple conditional statements.
def check_border_collision(player_list):
    for player in player_list:
        if player[0] < 0 or player[0] >= num_blocks_x or player[1] < 0 or player[1] >= num_blocks_y:
            return True
    return False
This method handles two scenarios: collision with itself or eating a fruit. If the snake collides with itself, the method returns True, indicating the end of the game. Otherwise, it checks if the snake has eaten a fruit. If so, it finds a new random location for the fruit and adds a new segment to the snake's body at the lower end. This new segment follows the direction of the last segment, ensuring continuity of movement. The method then returns False to indicate that the snake did not collide with itself.
def handle_game_object_collision(game_objects):
    # Collision detection for player colliding with itself
    player_positions = [(pos[0], pos[1]) for pos in game_objects["player"]]

    for pos in player_positions[1:]:
        if player_positions[0] == pos:
            return True

    # Check for collision between player and fruits
    for fruit in ["fruit_1", "fruit_2"]:
        if game_objects["player"][0][:2] == game_objects[fruit]:
            # If player touches the fruit, generate a new random position for the fruit
            game_objects[fruit] = [random.randint(0, num_blocks_x - 1), random.randint(0, num_blocks_y - 1)]

            # Add a new list to player's list with the same direction as the last element
            last_direction = game_objects["player"][-1][2]
            new_player_pos = list(game_objects["player"][-1][:2])
            if last_direction == 0:
                # Move down
                new_player_pos[1] += 1
            elif last_direction == 1:
                # Move up
                new_player_pos[1] -= 1
            elif last_direction == 2:
                # Move right
                new_player_pos[0] += 1
            elif last_direction == 3:
                # Move left
                new_player_pos[0] -= 1
            game_objects["player"].append(new_player_pos + [last_direction])

    return False
This method maps keyboard inputs (WASD keys) to numerical values from 0 to 3, corresponding to the directions (up, down, left, right). For example, pressing 'S' returns 2, indicating a downward direction.
def check_input_direction():
    direction = None  # Reset direction to None at the beginning of each iteration
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w:
                direction = 0  # Up
            elif event.key == pygame.K_s:
                direction = 1  # Down
            elif event.key == pygame.K_a:
                direction = 2  # Left
            elif event.key == pygame.K_d:
                direction = 3  # Right
            elif event.key == pygame.K_ESCAPE:
                direction = 4  # Right

    return direction
When the snake changes direction, only the head immediately changes direction, and the change gradually propagates down the length of the snake. This method updates the direction of each segment of the snake after every game tick, ensuring that each segment moves in its assigned direction until it reaches the point of direction change.
def handle_snake_rotation(game_objects):
    for i in reversed(range(1, len(game_objects["player"]))):
        game_objects["player"][i][2] = game_objects["player"][i - 1][2]
These methods collectively manage various aspects of the game, including collision detection, input handling, and directional movement of the snake, contributing to the overall functionality and gameplay experience.

Finally, we combine all these methods to execute our program. We draw the player and fruits on the screen, set the clock speed, and handle game-ending collisions. We move each segment of the snake based on its assigned direction. All of these actions are carried out within the game loop.
def main():
    # Set up the display
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Pygame Grid")

    # Clock object to control frame rate
    clock = pygame.time.Clock()

    # Font for displaying text
    font = pygame.font.SysFont(None, 30)

    # Dictionary to store game objects
    game_objects = {
        "player": [[1, 1, None]],  # Initial position of player at (1, 1) with no direction
        "fruit_1": [5, 5],  # Initial position of fruit_1 at (5, 5)
        "fruit_2": [10, 10]  # Initial position of fruit_2 at (10, 10)
    }

    running = True
    end_game = False

    while running:
        direction = check_input_direction()

        if direction == 4:
            break

        # Set direction of the first element of player if direction is not None
        if direction is not None:
            game_objects["player"][0][2] = direction

        # Move player based on direction
        for player in game_objects["player"]:
            if end_game:
                break

            dir_player = player[2]
            if dir_player == 0:
                # Move player up
                player[1] -= 1
            elif dir_player == 1:
                # Move player down
                player[1] += 1
            elif dir_player == 2:
                # Move player left
                player[0] -= 1
            elif dir_player == 3:
                # Move player right
                player[0] += 1

        collision = handle_game_object_collision(game_objects) or check_border_collision(game_objects["player"])
        handle_snake_rotation(game_objects)

        screen.fill(BLACK)
        draw_grid(screen)

        # Draw player
        first = True
        for pos in game_objects["player"]:
            color = (100, 0, 255) if first else (255, 0, 0)
            pygame.draw.rect(screen, color, (pos[0] * block_size, pos[1] * block_size, block_size, block_size))

            first = False

        # Display "Collided" text if collision detected
        if collision:
            text = font.render("Game Ended", True, WHITE)
            screen.blit(text, (10, 10))
            end_game = True

        # Draw fruits
        for fruit in ["fruit_1", "fruit_2"]:
            pygame.draw.rect(screen, (0, 255, 0), (
                game_objects[fruit][0] * block_size, game_objects[fruit][1] * block_size, block_size, block_size))

        pygame.display.flip()

        # Cap the frame rate
        clock.tick(clock_speed)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()

Comments