Snake Game Implementation in C Language

Introduction to the Game

The Snake game, also known as the Snake and Ladder game, is a classic casual game available across multiple platforms including PC and mobile devices. It's simple yet engaging. Players control the snake's direction to eat food, causing the snake to grow longer. Originally designed as a single-player experience, the game has evolved to include various modes like team battles, bounty hunts, and challenge modes. This article will guide you through creating a basic version of the Snake game using C language.

Technical Overview

This tutorial uses C programming language to build a minimal implementation of the Snake game. The project incorporates fundamental C programming concepts and utilizes Win32 API for enhanced functionality.

Win32 API Overview

API stands for Application Programming Interface, which refers to a collection of functions provided by the Win32 operating system for developers to perform various operations on computer systems, such as creating windows, drawing graphics, and interacting with hardware devices. These functions act like building blocks that can be used to create richly featured and flexible applications. In essence, Win32 API is the programming interface for Microsoft Windows 32-bit platforms.

The following sections explain several Win32 API functions used in this project.

1. Console Commands

After launching the Snake program, a window will appear — this is the console, also referred to as cmd.

We can use console commands to set the console size and title.

mode Command

The mode command allows us to adjust the console window dimensions.

Example code:

system("mode con cols=100 lines=32");

This sets the console width to 100 and height to 32.

Important Note: The aspect ratio of the console is not 1:1, so it's crucial to understand this when designing the game layout.

Note: Using system() requires including <stdlib.h>.

title Command

The title command changes the console window's name.

Example code:

system("title Snake Game");

This updates the console window's title to "Snake Game".

2. GetStdHandle

Retrieves a handle to the specified standard device (standard input, output, or error).

Syntax:

HANDLE WINAPI GetStdHandle(
 _In_ DWORD nStdHandle
);

Parameters include:

Return Value: If successful, returns a handle to the specified device, or a redirected handle set by a previous call to SetStdHandle. Unless the application has set less privileged standard handles via SetStdHandle, the handle has GENERIC_READ and GENERIC_WRITE access permissions.

If the functon fails, the return value is INVALID_HANDLE_VALUE.

If the application has no associated standard handles (such as services running on an interactive desktop), and these handles have not been redirecetd, the return value is NULL.

For more details, see the official documentation on GetStdHandle.

3. GetConsoleCursorInfo

Retrieves information about the cursor's size and visibility for the specified console screen buffer.

Syntax:

BOOL WINAPI GetConsoleCursorInfo(
 _In_  HANDLE               hConsoleOutput,
 _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

Return Value:

Non-zero if successful.

Zero if failed.

4. CONSOLE_CURSOR_INFO

Contains information about the console cursor.

Syntax:

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;
 BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

Members:

dwSize
Percentage of the character cell filled by the cursor. Values range from 1 to 100. The appearance varies from fully filled cells to a horizontal line at the bottom of the cell.

bVisible
Visibility status of the cursor. Set to TRUE if visible.

To hide the cursor in the game, we set bVisible to false.

CursorInfo.bVisible = false;

5. SetConsoleCursorInfo

Sets the cursor size and visibility for the specified console screen buffer.

Syntax:

BOOL WINAPI SetConsoleCursorInfo(
 _In_       HANDLE              hConsoleOutput,
 _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

Parameters:

hConsoleOutput [in]
Handle to the console screen buffer. Must have GENERIC_READ access permission.

lpConsoleCursorInfo [in]
Pointer to a CONSOLE_CURSOR_INFO structure containing specifications for the cursor.

Return Value:

Non-zero if successful.

Zero if failed.

To hide the cursor, we must first retrieve the current cursor info, modify it, and then apply it:

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
CursorInfo.bVisible = false; 
SetConsoleCursorInfo(hOutput, &CursorInfo);

6. COORD

Defines coordinates for character cells in the console screen buffer. The origin (0,0) is located at the top-left cell of the buffer.

Syntax:

typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;

7. SetConsoleCursorPosition

Sets the cursor position in the specified console screen buffer.

Syntax:

BOOL WINAPI SetConsoleCursorPosition(
 _In_ HANDLE hConsoleOutput,
 _In_ COORD  dwCursorPosition
);

Parameters:

hConsoleOutput [in]
Handle to the console screen buffer. Must have GENERIC_READ access permission.

dwCursorPosition [in]
A COORD structure specifying the new cursor position in characters. Coordinates must be within the console screen buffer boundaries.

Return Value:

Non-zero if successful.

Zero if failed.

8. GetAsyncKeyState

Determines whether a key is currently pressed or released, and whether it was pressed since the last call to GetAsyncKeyState.

Syntax:

SHORT GetAsyncKeyState(
 [in] int vKey
);

Parameters:

[in] vKey

Type: int

Virtual key code. Refer to virtual key codes for more information.

Constants can be used to distinguish between left and right keys.

Return Value:

Type: SHORT

Non-zero if the key was pressed since the last call to GetAsyncKeyState and is currently down. If the most significant bit is set, the key is down. If the least significant bit is set, the key was pressed since the last call.

Returns zero under the following conditions:

  • Current desktop is not active
  • Foreground thread belongs to another process, and desktop does not allow hooks or logging.

These APIs form the foundation for implementing the Snake game.

Complete Snake Game Code

snake.h

#pragma once

#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#include<stdio.h>
#include<time.h>

// Initial snake position
#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'▲'

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )


// Snake node structure
typedef struct SnakeNode
{
	// Body coordinates
	int x;
	int y;

	struct SnakeNode* next;
}SnakeNode;

// Snake state enumeration
enum SNAKE_STATUS
{
	OK,
	KILL_BY_WALL,
	KILL_BY_SELF,
	END_NORMAL
};

// Direction enumeration
enum DIRECTION
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

// Snake data structure
typedef struct Snake
{
	SnakeNode* pSnake;// Head node pointer
	SnakeNode* pFood;// Food node pointer
	enum SNAKE_STATUS pStatus;// Game status
	enum DIRECTION dir;// Movement direction
	int food_weight;// Points per food item
	int score;// Total score
	int sleep_time;// Delay time, affects movement speed
}Snake;

// Initialize game
void GameStart(Snake* snake);

// Set cursor position
void SetPos(short x, short y);

// Display welcome screen
void WelcomeToGame();

// Create game map
void CreatMap();

// Initialize snake body
void InitSnake(Snake* snake);

// Generate food
void CreateFood(Snake* snake);

// Main game loop
void GameRun(Snake* snake);

// Display game information
void GameInfo(Snake* snake);

void GameStop();

void SnakeFast(Snake* snake);
void SnakeSlow(Snake* snake);

// Move snake
void SnakeMode(Snake* snake);

// End game
void GameEnd(Snake* snake);

snake.c

#include"snake.h"

// Set cursor position
void SetPos(short x, short y)
{
	COORD pos = { x, y };
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(hOutput, pos);
}

// Welcome screen
void WelcomeToGame()
{
	SetPos(40, 16);
	wprintf(L"Welcome to Snake Game");
	SetPos(40, 28);
	system("pause");
	system("cls");
	SetPos(35, 15);
	wprintf(L"Use ↑↓←→ to move, F3 to speed up, F4 to slow down");
	SetPos(35, 16);
	wprintf(L"Speed increases score");
	SetPos(40, 28);
	system("pause");
	system("cls");
}

// Create game border
void CreatMap()
{
	// Top wall
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	// Bottom wall
	SetPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	// Left wall
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	// Right wall
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

// Initialize snake
void InitSnake(Snake* snake)
{
	int i = 0;
	for (i = 0; i < 5; i++)// Initial length of 5 segments
	{
		SnakeNode* newSnakeNode = (SnakeNode*)malloc(sizeof(SnakeNode));
		if (newSnakeNode == NULL)
		{
			perror("malloc() fail");
			return;
		}

		// Initialize segment properties
		newSnakeNode->next = NULL;
		newSnakeNode->x = POS_X + i * 2;
		newSnakeNode->y = POS_Y;

		// Insert at head
		if (snake->pSnake == NULL)
		{
			snake->pSnake = newSnakeNode;
		}
		else
		{
			newSnakeNode->next = snake->pSnake;
			snake->pSnake = newSnakeNode;
		}
	}

	// Render initial snake
	SnakeNode* cur = snake->pSnake;
	for (i = 0; i < 5; i++)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	snake->dir = RIGHT;
	snake->food_weight = 10;
	snake->score = 0;
	snake->sleep_time = 200;
	snake->pStatus = OK;
}

// Generate food
void CreateFood(Snake* snake)
{
	int x = 0;
	int y = 0;
	// Create food node
	SnakeNode* food = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("malloc()fail");
		return;
	}

again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	SnakeNode* cur = snake->pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	food->x = x;
	food->y = y;
	food->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);

	snake->pFood = food;
}


void GameStart(Snake* snake)
{
	//0. Set console size, hide cursor
	system("mode con cols=100 lines=32");
	system("title Snake Game");

	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	// Hide cursor
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);
	CursorInfo.bVisible = false; 
	SetConsoleCursorInfo(hOutput, &CursorInfo);

	//1. Welcome screen
	WelcomeToGame();

	//2. Create map
	CreatMap();

	//3. Initialize snake
	InitSnake(snake);
	
	//4. Generate food
	CreateFood(snake);
}

// Display game info
void GameInfo(Snake* snake)
{
	
	SetPos(68, 17);
	printf("F3 Speed Up, F4 Slow Down");
	SetPos(68, 18);
	printf("ESC Exit, SPACE Pause");

}

void GameStop()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void SnakeFast(Snake* snake)
{
	if (snake->sleep_time > 80)
	{
		snake->sleep_time -= 20;
		snake->food_weight += 2;
	}
}

void SnakeSlow(Snake* snake)
{
	if (snake->sleep_time < 280)
	{
		snake->sleep_time += 20;
		snake->food_weight -= 2;
	}
}

int NextIsFood(Snake* snake, SnakeNode* NextNode)
{
	return snake->pFood->x == NextNode->x && snake->pFood->y == NextNode->y;
}

void EatFood(Snake* snake, SnakeNode* NextNode)
{
	NextNode->next = snake->pSnake;
	snake->pSnake = NextNode;

	SnakeNode* cur = snake->pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	free(snake->pFood);
	snake->pFood = NULL;

	snake->score += snake->food_weight;
	CreateFood(snake);
}

void NextNoFood(Snake* snake, SnakeNode* NextNode)
{
	NextNode->next = snake->pSnake;
	snake->pSnake = NextNode;

	SnakeNode* cur = snake->pSnake;
	while (cur->next->next)
	{
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;

	cur = snake->pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
}

// Wall collision
void kill_by_wall(Snake* snake)
{
	if (snake->pSnake->x == 0 || snake->pSnake->x == 56 
		|| snake->pSnake->y == 0 || snake->pSnake->y == 27)
	{
		snake->pStatus = KILL_BY_WALL;
	}
}

// Self collision
void kill_by_self(Snake* snake)
{
	SnakeNode* cur = snake->pSnake->next;
	while (cur)
	{
		if (cur->x == snake->pSnake->x && cur->y == snake->pSnake->y)
		{
			snake->pStatus = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMode(Snake* snake)
{
	SnakeNode* NextNode = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (NextNode == NULL)
	{
		perror("malloc()fail");
		return;
	}

	switch(snake->dir)
	{
	case UP:
		NextNode->x = snake->pSnake->x;
		NextNode->y = snake->pSnake->y - 1;
		break;
	case DOWN:
		NextNode->x = snake->pSnake->x;
		NextNode->y = snake->pSnake->y + 1;
		break;
	case LEFT:
		NextNode->x = snake->pSnake->x - 2;
		NextNode->y = snake->pSnake->y;
		break;
	case RIGHT:
		NextNode->x = snake->pSnake->x + 2;
		NextNode->y = snake->pSnake->y;
		break;
	}

	if (NextIsFood(snake, NextNode))
	{
		EatFood(snake, NextNode);
	}
	else
	{
		NextNoFood(snake, NextNode);
	}
	
	// Wall collision check
	kill_by_wall(snake);

	// Self collision check
	kill_by_self(snake);
}


// Run game loop
void GameRun(Snake* snake)
{
	// Show game info
	GameInfo(snake);
	do
	{
		SetPos(68, 14);
		printf("Points per food: %2d", snake->food_weight);
		SetPos(68, 15);
		printf("Total Score: %d", snake->score);
		if (KEY_PRESS(VK_UP) && snake->dir != DOWN)
		{
			snake->dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && snake->dir != UP)
		{
			snake->dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && snake->dir != RIGHT)
		{
			snake->dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && snake->dir != LEFT)
		{
			snake->dir = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			snake->pStatus = END_NORMAL;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			GameStop();
		}
		else if (KEY_PRESS(VK_F3))
		{
			SnakeFast(snake);
		}
		else if (KEY_PRESS(VK_F4))
		{
			SnakeSlow(snake);
		}

		// Move snake
		SnakeMode(snake);
		Sleep(snake->sleep_time);

	} while (snake->pStatus == OK);
}

// End game cleanup
void GameEnd(Snake* snake)
{
	SetPos(25, 14);
	switch (snake->pStatus)
	{
	case END_NORMAL:
		printf("Game exited normally");
		break;
	case KILL_BY_SELF:
		printf("Collided with self, game over");
		break;
	case KILL_BY_WALL:
		printf("Hit wall, game over");
		break;
	}

	SnakeNode* cur = snake->pSnake;
	while (cur)
	{
		SnakeNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
}

test.c


#include <locale.h>
#include  "snake.h"

test()
{
	int ch = 0;
	do
	{
		system("cls");
		// Create snake instance
		Snake snake = { 0 };

		// Initialize game
		GameStart(&snake);
		// Includes:
		// 1. Welcome screen
		// 2. Map creation
		// 3. Snake initialization
		// 4. Food generation


		// Run game
		GameRun(&snake);

		// Cleanup
		GameEnd(&snake);
		SetPos(25, 23);
		system("pause");
		SetPos(25, 24);
		printf("Play Again? (Y/N):");
		ch = getchar();
		while(getchar()!='\n');
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 28);
}


int main()
{
	// Localize
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));

	test();

	return 0;
}

Note: Localization settings with setlocale()

Usage reference: setlocale - C++ Reference (cplusplus.com)

Since wide characters are used for display, localization must be configured.

Tags: snake-game c-language win32-api console-programming gaming

Posted on Mon, 02 Nov 2026 16:09:40 +0000 by jester626