Table of contents
1.
Introduction
2.
Prerequisite
3.
Implementation of Snake Game in C:
4.
Snake Implementation Logic
4.1.
Fruit Implementation Logic
4.2.
Boundary Implementation Logic
4.3.
Game Motion Implementation Logic
4.4.
Real Time Response Logic
5.
Snake Game Code in C:
6.
Frequently Asked Questions
6.1.
How can I modify the game speed?
6.2.
Can I add obstacles to the game?
6.3.
How can I enhance the game's visual appearance?
7.
Conclusion
Last Updated: Nov 29, 2024
Easy

Snake Game in C

Author Rinki Deka
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

The snake game is a classic video game that we all might have played in our childhood. Our old Nokia phones were preloaded with this game. In this game, the player controls a snake that moves around a screen, trying to eat food while avoiding obstacles and its own tail. As the snake eats more food, it grows longer, which makes the game progressively more challenging. 

Snake Game in C

In this article, we'll discuss how to create a snake game using the C programming language. We'll cover the prerequisites, implementation logic for the snake, fruit, boundaries, game motion, & real-time response. 

Prerequisite

To implement the snake game in C, you'll need a basic understanding of C programming concepts such as functions, arrays, loops, & user input. You'll also need a C compiler installed on your computer, such as GCC or Clang, to compile and run the code. Additionally, familiarity with a graphics library like SDL or ncurses can be helpful for rendering the game visuals, but we'll focus on the core logic using standard C libraries in this article.

Implementation of Snake Game in C:

To implement the snake game in C, we'll break down the problem into smaller components. First, we'll represent the game grid using a 2D array, where each cell can be empty, occupied by the snake, or contain a fruit. We'll use a struct to store the snake's position, length, & direction of movement. The fruit will be randomly placed on the grid, and the snake will grow longer each time it eats the fruit. The game will end if the snake collides with the boundaries or its own tail.

Let’s look at the steps it might have :
 

1. Initialize the game grid & snake's starting position
 

2. Render the game grid, snake, & fruit on the screen
 

3. Handle user input to change the snake's direction
 

4. Update the snake's position based on its current direction
 

5. Check for collisions with the boundaries, fruit, or snake's own tail
 

6. If the snake eats a fruit, increase its length & randomly place a new fruit
 

7. Repeat steps 3-6 until the game ends

Snake Implementation Logic

The snake is the main character in the game, and we'll represent it using an array of coordinates. Each coordinate represents a segment of the snake's body. The snake's head will be the first element in the array, and its tail will be the last element.

To move the snake, we'll update the coordinates of each segment based on the direction of movement. For example, if the snake is moving right, we'll subtract 1 from the x-coordinate of each segment. If the snake is moving up, we'll add 1 to the y-coordinate of each segment.

For example: 

#define MAX_LENGTH 100


struct Snake {
    int x[MAX_LENGTH];
    int y[MAX_LENGTH];
    int length;
    int direction;
};


void moveSnake(struct Snake* snake) {
    int i;
    for (i = snake->length - 1; i > 0; i--) {
        snake->x[i] = snake->x[i - 1];
        snake->y[i] = snake->y[i - 1];
    }


    switch (snake->direction) {
        case UP:
            snake->y[0]--;
            break;
        case DOWN:
            snake->y[0]++;
            break;
        case LEFT:
            snake->x[0]--;
            break;
        case RIGHT:
            snake->x[0]++;
            break;
    }
}

Fruit Implementation Logic

The fruit is the object that the snake must eat to grow longer. We'll represent the fruit using a simple struct with x and y coordinates.

This is an example of how to define the Fruit struct:

struct Fruit {
    int x;
    int y;
};


To place the fruit randomly on the game grid, we can use the rand() function from the standard library. We'll generate random x and y coordinates within the bounds of the grid and ensure that the fruit doesn't spawn on top of the snake.

Let’s look at a sample function to place the fruit randomly:

void placeFruit(struct Fruit* fruit, struct Snake* snake, int gridWidth, int gridHeight) {
    int x, y;
    do {
        x = rand() % gridWidth;
        y = rand() % gridHeight;
    } while (isFruitOnSnake(x, y, snake));
    fruit->x = x;
    fruit->y = y;
}

int isFruitOnSnake(int x, int y, struct Snake* snake) {
    int i;
    for (i = 0; i < snake->length; i++) {
        if (snake->x[i] == x && snake->y[i] == y) {
            return 1;
        }
    }
    return 0;
}

Boundary Implementation Logic

The game boundaries are the edges of the grid that the snake must not cross. If the snake collides with a boundary, the game ends.

To implement the boundary logic, we'll check if the snake's head (x[0], y[0]) has gone beyond the grid dimensions. Here's an example of how to check for boundary collisions:

int isCollision(struct Snake* snake, int gridWidth, int gridHeight) {
    if (snake->x[0] < 0 || snake->x[0] >= gridWidth || snake->y[0] < 0 || snake->y[0] >= gridHeight) {
        return 1;
    }
    return 0;
}


In this function, we check if the x-coordinate of the snake's head is less than 0 (left boundary) or greater than or equal to the grid width (right boundary). Similarly, we check if the y-coordinate is less than 0 (top boundary) or greater than or equal to the grid height (bottom boundary).

If any of these conditions are true, the function returns 1, indicating a collision. Otherwise, it returns 0.

You can call this function in your main game loop to check for collisions and end the game if necessary:

if (isCollision(snake, gridWidth, gridHeight)) {
    gameOver = 1;
}

Game Motion Implementation Logic

To create smooth game motion, we'll update the snake's position and redraw the game grid at regular intervals. To control the game's speed, we can use a timer or a delay function.

Let’s look at an example of how to implement game motion using a delay function:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


void gameLoop(struct Snake* snake, struct Fruit* fruit, int gridWidth, int gridHeight) {
    while (!gameOver) {
        // Clear the screen
        system("clear");


        // Move the snake
        moveSnake(snake);


        // Check for collision with boundaries
        if (isCollision(snake, gridWidth, gridHeight)) {
            gameOver = 1;
            break;
        }


        // Check for collision with fruit
        if (snake->x[0] == fruit->x && snake->y[0] == fruit->y) {
            // Increase snake length
            snake->length++;
            // Place new fruit
            placeFruit(fruit, snake, gridWidth, gridHeight);
        }


        // Draw the game grid, snake, and fruit
        drawGrid(gridWidth, gridHeight);
        drawSnake(snake);
        drawFruit(fruit);


        // Delay to control game speed
        usleep(100000); // Delay for 100 milliseconds
    }
}

 

In this example, we use a while loop to continuously update the game state until the game is over. Inside the loop, we clear the screen, move the snake, check for collisions with boundaries and fruit, and redraw the game elements.

The `usleep()` function is used to introduce a delay between each game update, controlling the game's speed. You can adjust the delay value to make the game faster or slower.

Real Time Response Logic

To make the game interactive, we need to handle user input in real-time. We'll use the `kbhit()` function to check if a key has been pressed and the `getch()` function to retrieve the pressed key.

For example: 

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>


void handleInput(struct Snake* snake) {
    if (kbhit()) {
        switch (getch()) {
            case 'w':
            case 'W':
                if (snake->direction != DOWN)
                    snake->direction = UP;
                break;
            case 's':
            case 'S':
                if (snake->direction != UP)
                    snake->direction = DOWN;
                break;
            case 'a':
            case 'A':
                if (snake->direction != RIGHT)
                    snake->direction = LEFT;
                break;
            case 'd':
            case 'D':
                if (snake->direction != LEFT)
                    snake->direction = RIGHT;
                break;
        }
    }
}


In this example, we define a `handleInput()` function that checks if a key has been pressed using `kbhit()`. If a key is pressed, we use `getch()` to retrieve the pressed key.

We then use a switch statement to check which key was pressed and update the snake's direction accordingly. We also add a check to prevent the snake from reversing its direction (e.g., moving directly from UP to DOWN).

You can call the `handleInput()` function in your game loop to handle user input in real-time:

void gameLoop(struct Snake* snake, struct Fruit* fruit, int gridWidth, int gridHeight) {
    while (!gameOver) {
        // ...

        // Handle user input
        handleInput(snake);

        // ...
    }
}

Snake Game Code in C:

Now that we've discussed all the key components of the snake game let's put everything together into a complete C program.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <unistd.h>


#define MAX_LENGTH 100
#define GRID_WIDTH 20
#define GRID_HEIGHT 20


enum Direction {
    UP,
    DOWN,
    LEFT,
    RIGHT
};


struct Snake {
    int x[MAX_LENGTH];
    int y[MAX_LENGTH];
    int length;
    enum Direction direction;
};


struct Fruit {
    int x;
    int y;
};


void initSnake(struct Snake* snake) {
    snake->length = 1;
    snake->x[0] = GRID_WIDTH / 2;
    snake->y[0] = GRID_HEIGHT / 2;
    snake->direction = RIGHT;
}


void placeFruit(struct Fruit* fruit, struct Snake* snake) {
    // ... (Code for placing fruit randomly)
}


void moveSnake(struct Snake* snake) {
    // ... (Code for moving the snake)
}


int isCollision(struct Snake* snake) {
    // ... (Code for checking collisions)
}


void handleInput(struct Snake* snake) {
    // ... (Code for handling user input)
}


void drawGrid() {
    // ... (Code for drawing the game grid)
}


void drawSnake(struct Snake* snake) {
    // ... (Code for drawing the snake)
}


void drawFruit(struct Fruit* fruit) {
    // ... (Code for drawing the fruit)
}


int main() {
    struct Snake snake;
    struct Fruit fruit;
    int gameOver = 0;


    initSnake(&snake);
    placeFruit(&fruit, &snake);


    while (!gameOver) {
        system("clear");
        handleInput(&snake);
        moveSnake(&snake);


        if (isCollision(&snake)) {
            gameOver = 1;
        }


        if (snake.x[0] == fruit.x && snake.y[0] == fruit.y) {
            snake.length++;
            placeFruit(&fruit, &snake);
        }


        drawGrid();
        drawSnake(&snake);
        drawFruit(&fruit);
        usleep(100000);
    }


    printf("Game Over! Your score: %d\n", snake.length - 1);
    return 0;
}
You can also try this code with Online C Compiler
Run Code


This is a complete snake game implementation in C. The code includes all the necessary functions and structures we discussed earlier.

In the `main()` function, we initialize the snake and fruit, and then enter the game loop. Inside the loop, we handle user input, move the snake, check for collisions and fruit eating, and redraw the game elements.

Once the game is over, we print the final score (the snake's length minus 1) and exit the program.

Just remember that some functions like `placeFruit()`, `drawGrid()`, `drawSnake()`, and `drawFruit()` are not fully implemented here and would require additional code based on your specific requirements and chosen graphics library.

Frequently Asked Questions

How can I modify the game speed?

To change the game speed, adjust the delay value in the `usleep()` function call in the game loop. A smaller value will make the game faster, while a larger value will slow it down.

Can I add obstacles to the game?

Yes, you can add obstacles by creating a new struct for obstacles and modifying the collision detection logic to check for collisions with obstacles in addition to boundaries and the snake's own tail.

How can I enhance the game's visual appearance?

To improve the game's visuals, you can use a graphics library like SDL or ncurses to render the game elements with colors, textures, or images instead of simple ASCII characters.

Conclusion

In this article, we discussed the implementation of a classic snake game in C. We covered the essential components, including the snake's movement, fruit placement, collision detection, boundary handling, game motion, & real-time user input. By breaking down the problem into smaller parts & using basic C programming concepts, we created a functional snake game that you can now run & enjoy. With the knowledge gained from this article, you can further enhance the game by adding new features, improving the visuals, or optimizing the code.

You can also check out our other blogs on Code360.

Live masterclass