Building a Classic Snake Game with C and Win32 API

Project Objectives

Develop a console-based Snake game using C on Windows. The implementation includes:

  • Game map rendering
  • Snake movement controlled by arrow keys
  • Food consumption mechanics
  • Collision detection (walls and self)
  • Score tracking system
  • Speed adjustment (acceleration and deceleration)
  • Pause functionality

Essential Technologies

This project utilizes C functions, enumerations, structures, dynamic memory allocation, preprocessor directives, linked lists, and Win32 API functions.

Win32 API Fundamentals

The Windows operating system provides a vast service center through API functions. These functions allow applications to create windows, draw graphics, and utilize peripheral devices. Win32 API specifically refers to the 32-bit platform application programming interface.

Console Configuration

Console applications can be configured using system commands. The system() function in C allows execution of these commands:

#include <stdlib.h>

int main()
{
    system("mode con cols=100 lines=30");
    system("title Snake Game");
    system("pause");
    return 0;
}

Console Coordinate System

The COORD structure represents character positions on the console screen:

#include <windows.h>

int main()
{
    COORD position = { 40, 10 };
    return 0;
}

Standard Handle Retrieval

GetStdHandle retrieves a handle to a standard device (input, output, or error). This handle is essential for manipulating console properties:

#include <windows.h>

int main()
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    return 0;
}

Cursor Visibility Control

The CONSOLE_CURSOR_INFO structure contains cursor size and visibility information. Hiding the cursor improves game visuals:

#include <windows.h>

void HideCursor()
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    
    GetConsoleCursorInfo(hOutput, &cursorInfo);
    cursorInfo.bVisible = FALSE;
    SetConsoleCursorInfo(hOutput, &cursorInfo);
}

Cursor Position Control

SetConsoleCursorPosition sets the cursor position for subsequent text output. A helper function simplifies this operation:

#include <windows.h>

void SetCursorPosition(int xPos, int yPos)
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD position = { xPos, yPos };
    SetConsoleCursorPosition(hOutput, position);
}

int main()
{
    SetCursorPosition(20, 5);
    printf("Game Started");
    return 0;
}

Keyboard Input Detection

GetAsyncKeyState determines whether a key is pressed. A macro simplifies key state checking:

#define IS_KEY_PRESSED(VK_CODE) ((GetAsyncKeyState(VK_CODE) & 0x0001) != 0)

Game Design and Analysis

Map Design

The game uses wide characters for visual elements: for walls, for the snake body, and for food. Wide characters occupy two bytes, requiring careful coordinate calculations.

To support wide characters, include <locale.h> and set the locale:

#include <locale.h>
#include <wchar.h>

int main()
{
    setlocale(LC_ALL, "");
    
    wchar_t wall = L'□';
    wchar_t body = L'●';
    wchar_t food = L'★';
    
    wprintf(L"%lc\n", wall);
    wprintf(L"%lc\n", body);
    wprintf(L"%lc\n", food);
    
    return 0;
}

Coordinate System

X-axis values increase from left to right; Y-axis values increase from top to bottom. Since wide characters occupy two character cells, snake body X-coordinates must be even numbers for proper alignment.

Data Structure Design

Snake Node Structure

Each snake segment is a linked list node storing its position:

typedef struct SnakeSegment {
    int posX;
    int posY;
    struct SnakeSegment* next;
} SnakeSegment;

Game State Enumerations

typedef enum {
    DIR_UP = 1,
    DIR_DOWN,
    DIR_LEFT,
    DIR_RIGHT
} Direction;

typedef enum {
    STATE_RUNNING,
    STATE_EXIT,
    STATE_WALL_COLLISION,
    STATE_SELF_COLLISION
} GameState;

Main Game Structure

typedef struct {
    SnakeSegment* head;
    SnakeSegment* foodPos;
    int totalScore;
    int pointsPerFood;
    int moveDelay;
    GameState currentState;
    Direction currentDirection;
} GameContext;

Game Flow

Initialization Phase

  1. Configure console window size and title
  2. Hide the console cursor
  3. Display welcome screen
  4. Draw the game map
  5. Initialize the snake body
  6. Spawn the first food item

Game Loop

  1. Display help information and current score
  2. Detect keyboard input
  3. Update snake position based on direction
  4. Check for food consumption
  5. Verify wall and self collisions
  6. Repeat until game over

Cleanup Phase

  1. Display game over message with reason
  2. Free all allocated memory for snake nodes

Core Implementation

Position Update Logic

void CalculateNextPosition(SnakeSegment* head, Direction dir, int* nextX, int* nextY)
{
    *nextX = head->posX;
    *nextY = head->posY;
    
    switch (dir) {
        case DIR_UP:
            (*nextY)--;
            break;
        case DIR_DOWN:
            (*nextY)++;
            break;
        case DIR_LEFT:
            *nextX -= 2;
            break;
        case DIR_RIGHT:
            *nextX += 2;
            break;
    }
}

Food Spawning

void SpawnFood(GameContext* game)
{
    SnakeSegment* newFood = (SnakeSegment*)malloc(sizeof(SnakeSegment));
    
    do {
        newFood->posX = (rand() % 28) * 2 + 2;
        newFood->posY = rand() % 25 + 1;
    } while (IsPositionOccupied(game->head, newFood->posX, newFood->posY));
    
    game->foodPos = newFood;
}

Collision Detection

int CheckWallCollision(SnakeSegment* head, int mapWidth, int mapHeight)
{
    return (head->posX <= 0 || head->posX >= mapWidth ||
            head->posY <= 0 || head->posY >= mapHeight);
}

int CheckSelfCollision(SnakeSegment* head)
{
    SnakeSegment* current = head->next;
    while (current != NULL) {
        if (head->posX == current->posX && head->posY == current->posY) {
            return 1;
        }
        current = current->next;
    }
    return 0;
}

Main Entry Point

#include <locale.h>

int main()
{
    setlocale(LC_ALL, "");
    srand((unsigned int)time(NULL));
    
    char choice;
    do {
        GameContext game = {0};
        InitializeGame(&game);
        RunGameLoop(&game);
        CleanupGame(&game);
        
        SetCursorPosition(20, 15);
        printf("Play again? (Y/N): ");
        scanf(" %c", &choice);
    } while (choice == 'Y' || choice == 'y');
    
    return 0;
}

Tags: c programming game development Win32 API Console Application linked lists

Posted on Sun, 06 Sep 2026 16:55:59 +0000 by dayang