Implementing the Snake Game in C with Console Graphics

Game Architecture and State Management

Developing a console-based Snake game in C requires careful management of game state, including the snake's position, length, and direction. Unlike simpler programs, this game relies on a continuous loop that handles rendering, input processing, and logic updates. To encapsulate the game's properties, a structured approach using a GameState structure clarifies data management compared to loose global variables. The core logic involves tracking the snake's head coordinates and an array of coordinates representing the tail segments.

Rendering the Game Board

The visual representation is achieved by manipulating the console cursor to redraw the scene at a fixed rate. The rendering function clears the screen and iterates through a grid defined by specific width and height constants. During this iteration, the logic checks each coordinate: if it matches the wall boundary, a wall character is drawn; if it matches the snake's head or body segments, the respective symbols are printed. This process must occur efficiently to prevent flickering, although standard console output often incurs some performance overhead.

Input Handling and Movement Logic

Capturing user input without halting the program execution necessitates non-blocking input functions. On Windows environments, _kbhit() checks if a keyboard key has been pressed, allowing the game loop to proceed smoothly if no input is detected. The logic updates the snake's velocity vector based on key presses (W, A, S, D). Crucially, the movement logic must prevent the snake from reversing directly into itself—for instance, disallowing a 'Left' move while currently moving 'Right'.

The tail movement is simulated by shifting the coordinate values of each segment. The position of the last segment is discarded unless the snake consumes food. Upon consuming food, the snake's length increases, the score is updated, and a new food coordinate is generated randomly within the grid boundaries.

Complete Source Code

The following implementation demonstrates these concepts using the windows.h library for sleep functionality and conio.h for keyboard input.

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

#define MAP_WIDTH 20
#define MAP_HEIGHT 20
#define MAX_LENGTH 100

typedef struct {
    int posX, posY;
} Coordinate;

typedef struct {
    Coordinate head;
    Coordinate body[MAX_LENGTH];
    int length;
    Coordinate food;
    int score;
    int isRunning;
    int velocityX, velocityY; // Direction vectors: -1, 0, 1
} GameContext;

GameContext game;

void InitializeGame() {
    game.isRunning = 1;
    game.head.posX = MAP_WIDTH / 2;
    game.head.posY = MAP_HEIGHT / 2;
    game.length = 0;
    game.score = 0;
    game.velocityX = 0;
    game.velocityY = 0;
    game.food.posX = rand() % MAP_WIDTH;
    game.food.posY = rand() % MAP_HEIGHT;
}

void DrawScene() {
    system("cls");
    
    // Draw Top Border
    for (int i = 0; i < MAP_WIDTH + 2; i++) printf("#");
    printf("\n");

    for (int i = 0; i < MAP_HEIGHT; i++) {
        printf("#"); // Left Border
        for (int j = 0; j < MAP_WIDTH; j++) {
            int printed = 0;

            // Draw Head
            if (j == game.head.posX && i == game.head.posY) {
                printf("O");
                printed = 1;
            }

            // Draw Food
            if (!printed && j == game.food.posX && i == game.food.posY) {
                printf("F");
                printed = 1;
            }

            // Draw Tail
            if (!printed) {
                for (int k = 0; k < game.length; k++) {
                    if (game.body[k].posX == j && game.body[k].posY == i) {
                        printf("o");
                        printed = 1;
                        break;
                    }
                }
            }

            if (!printed) printf(" ");
        }
        printf("#\n"); // Right Border
    }

    // Draw Bottom Border
    for (int i = 0; i < MAP_WIDTH + 2; i++) printf("#");
    printf("\n");

    printf("Score: %d\n", game.score);
}

void ProcessInput() {
    if (_kbhit()) {
        switch (_getch()) {
            case 'w':
                if (game.velocityY != 1) { game.velocityX = 0; game.velocityY = -1; }
                break;
            case 's':
                if (game.velocityY != -1) { game.velocityX = 0; game.velocityY = 1; }
                break;
            case 'a':
                if (game.velocityX != 1) { game.velocityX = -1; game.velocityY = 0; }
                break;
            case 'd':
                if (game.velocityX != -1) { game.velocityX = 1; game.velocityY = 0; }
                break;
            case 'x':
                game.isRunning = 0;
                break;
        }
    }
}

void UpdateLogic() {
    // Shift tail segments
    Coordinate prevSeg = game.body[0];
    Coordinate currentSeg;
    
    // Update the first tail segment to old head position
    if (game.length > 0) {
        game.body[0] = game.head;
    }

    for (int i = 1; i < game.length; i++) {
        currentSeg = game.body[i];
        game.body[i] = prevSeg;
        prevSeg = currentSeg;
    }

    // Update Head Position
    game.head.posX += game.velocityX;
    game.head.posY += game.velocityY;

    // Boundary Wrapping
    if (game.head.posX >= MAP_WIDTH) game.head.posX = 0;
    else if (game.head.posX < 0) game.head.posX = MAP_WIDTH - 1;
    
    if (game.head.posY >= MAP_HEIGHT) game.head.posY = 0;
    else if (game.head.posY < 0) game.head.posY = MAP_HEIGHT - 1;

    // Self Collision Detection
    for (int i = 0; i < game.length; i++) {
        if (game.body[i].posX == game.head.posX && game.body[i].posY == game.head.posY) {
            game.isRunning = 0;
        }
    }

    // Food Consumption
    if (game.head.posX == game.food.posX && game.head.posY == game.food.posY) {
        game.score += 10;
        game.food.posX = rand() % MAP_WIDTH;
        game.food.posY = rand() % MAP_HEIGHT;
        game.length++;
    }
}

int main() {
    InitializeGame();

    while (game.isRunning) {
        DrawScene();
        ProcessInput();
        UpdateLogic();
        Sleep(120); // Control game speed
    }
    
    return 0;
}

Tags: c programming game development Console Application Snake Game Windows API

Posted on Sat, 29 Aug 2026 16:49:53 +0000 by Tracer