Build a classic Snake Game in C for the console using arrays, game loops, keyboard input handling, and collision detection. Complete source code with explanation.
Building a Snake Game is one of the most fun C programming projects. It teaches you game loop design, real-time input handling, collision detection, and array manipulation. This implementation runs in the terminal/console and works on Windows (using conio.h) and can be adapted for Linux.
Game Features
- Snake moves continuously in the current direction
- Eat food to grow longer and increase score
- Game over on wall collision or self-collision
- Score tracking with increasing difficulty
- Simple console-based graphics using characters
Game Design
┌──────────────────────────┐
│ │
│ * │ * = Food
│ │
│ OOOOO │ O = Snake body
│ O │ @ = Snake head
│ @ │
│ │
│ Score: 5 Speed: Fast │
└──────────────────────────┘
Complete Source Code (Windows)
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
#define MAX_LENGTH 100
typedef struct {
int x, y;
} Point;
typedef enum { UP, DOWN, LEFT, RIGHT } Direction;
// Game state
Point snake[MAX_LENGTH];
int snakeLength;
Point food;
Direction dir;
int score;
int gameOver;
int speed;
// Move cursor to position (for redrawing)
void gotoxy(int x, int y) {
COORD coord = {x, y};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
// Hide cursor for cleaner display
void hideCursor() {
CONSOLE_CURSOR_INFO info = {1, FALSE};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
}
void initGame() {
snakeLength = 3;
snake[0] = (Point){WIDTH / 2, HEIGHT / 2};
snake[1] = (Point){WIDTH / 2 - 1, HEIGHT / 2};
snake[2] = (Point){WIDTH / 2 - 2, HEIGHT / 2};
dir = RIGHT;
score = 0;
gameOver = 0;
speed = 150; // milliseconds between frames
srand((unsigned)time(NULL));
food.x = rand() % (WIDTH - 2) + 1;
food.y = rand() % (HEIGHT - 2) + 1;
}
void drawBorder() {
system("cls");
for (int i = 0; i < WIDTH; i++) printf("#");
printf("\n");
for (int y = 1; y < HEIGHT - 1; y++) {
for (int x = 0; x < WIDTH; x++) {
if (x == 0 || x == WIDTH - 1)
printf("#");
else
printf(" ");
}
printf("\n");
}
for (int i = 0; i < WIDTH; i++) printf("#");
printf("\n");
printf("Score: %d | Use WASD to move | Q to quit\n", score);
}
void draw() {
// Draw food
gotoxy(food.x, food.y);
printf("*");
// Draw snake head
gotoxy(snake[0].x, snake[0].y);
printf("@");
// Draw snake body
for (int i = 1; i < snakeLength; i++) {
gotoxy(snake[i].x, snake[i].y);
printf("O");
}
// Update score display
gotoxy(0, HEIGHT);
printf("Score: %d | Length: %d | Speed: %dms ", score, snakeLength, speed);
}
void input() {
if (_kbhit()) {
char key = _getch();
switch (key) {
case 'w': case 'W': if (dir != DOWN) dir = UP; break;
case 's': case 'S': if (dir != UP) dir = DOWN; break;
case 'a': case 'A': if (dir != RIGHT) dir = LEFT; break;
case 'd': case 'D': if (dir != LEFT) dir = RIGHT; break;
case 'q': case 'Q': gameOver = 1; break;
}
}
}
void spawnFood() {
int valid;
do {
valid = 1;
food.x = rand() % (WIDTH - 2) + 1;
food.y = rand() % (HEIGHT - 2) + 1;
// Make sure food doesn't spawn on snake
for (int i = 0; i < snakeLength; i++) {
if (snake[i].x == food.x && snake[i].y == food.y) {
valid = 0;
break;
}
}
} while (!valid);
}
void update() {
// Clear tail position
gotoxy(snake[snakeLength - 1].x, snake[snakeLength - 1].y);
printf(" ");
// Move body (shift all segments)
for (int i = snakeLength - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
// Move head in current direction
switch (dir) {
case UP: snake[0].y--; break;
case DOWN: snake[0].y++; break;
case LEFT: snake[0].x--; break;
case RIGHT: snake[0].x++; break;
}
// Check wall collision
if (snake[0].x <= 0 || snake[0].x >= WIDTH - 1 ||
snake[0].y <= 0 || snake[0].y >= HEIGHT - 1) {
gameOver = 1;
return;
}
// Check self collision
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
gameOver = 1;
return;
}
}
// Check food collision
if (snake[0].x == food.x && snake[0].y == food.y) {
score += 10;
snakeLength++;
if (snakeLength >= MAX_LENGTH) snakeLength = MAX_LENGTH - 1;
// Increase speed every 50 points
if (speed > 50 && score % 50 == 0) speed -= 10;
spawnFood();
}
}
int main() {
hideCursor();
initGame();
drawBorder();
while (!gameOver) {
input();
update();
if (!gameOver) draw();
Sleep(speed);
}
// Game Over screen
gotoxy(WIDTH / 2 - 5, HEIGHT / 2);
printf("GAME OVER!");
gotoxy(WIDTH / 2 - 8, HEIGHT / 2 + 1);
printf("Final Score: %d", score);
gotoxy(0, HEIGHT + 2);
printf("Press any key to exit...\n");
_getch();
return 0;
}
Linux/macOS Version (using ncurses)
For cross-platform compatibility, here's the key input handling for Linux:
// Linux version - compile with: gcc -o snake snake.c -lncurses
#include <ncurses.h>
#include <unistd.h> // for usleep()
void initScreen() {
initscr();
noecho();
curs_set(0);
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE); // Non-blocking input
timeout(100);
}
void linuxInput() {
int ch = getch();
switch (ch) {
case KEY_UP: if (dir != DOWN) dir = UP; break;
case KEY_DOWN: if (dir != UP) dir = DOWN; break;
case KEY_LEFT: if (dir != RIGHT) dir = LEFT; break;
case KEY_RIGHT: if (dir != LEFT) dir = RIGHT; break;
case 'q': gameOver = 1; break;
}
}
// Use mvprintw(y, x, "text") instead of gotoxy+printf
// Use usleep(speed * 1000) instead of Sleep(speed)
// Call endwin() before exit
How the Game Loop Works
| Update Game State | |
|---|
| - Move snake | |
| - Check collisions | |
| - Check food eaten | |
| Draw Frame | |
Key Programming Concepts
| Concept | Usage in Game |
|---|
| Arrays | Snake body segments stored in array |
| Enum | Direction (UP, DOWN, LEFT, RIGHT) |
| Struct | Point (x, y coordinates) |
| Game loop | Continuous input→update→draw cycle |
| Collision detection | Coordinate comparison |
| Non-blocking input | _kbhit() / nodelay() |
| Cursor control | gotoxy() / ncurses |
Possible Enhancements
- Levels – Different maps with obstacles
- High scores – Save best scores to file
- Colors – Use ANSI codes or ncurses colors
- Power-ups – Speed boost, invincibility
- Two-player – Split keyboard controls
- Wraparound – Snake appears on opposite wall
Interview Questions
Q: How do you handle the snake growing when it eats food? Don't move the last segment – skip the tail erasure for one frame. The new segment naturally appears because the body shift copies the second-to-last position.
Q: How do you prevent the snake from reversing direction? Check before changing direction: if current direction is RIGHT, don't allow LEFT (immediate reversal would cause self-collision).
Q: How would you implement a pause feature? On 'P' key press, enter a blocking getch() loop. The game loop freezes until another key is pressed.
Summary
The Snake Game project teaches fundamental game programming concepts: the game loop pattern, real-time input handling, collision detection, state management with arrays, and console graphics. It's a satisfying project that produces a playable game while reinforcing C programming skills including structures, enums, and system-specific API calls.