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
- Configure console window size and title
- Hide the console cursor
- Display welcome screen
- Draw the game map
- Initialize the snake body
- Spawn the first food item
Game Loop
- Display help information and current score
- Detect keyboard input
- Update snake position based on direction
- Check for food consumption
- Verify wall and self collisions
- Repeat until game over
Cleanup Phase
- Display game over message with reason
- 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;
}