C语言实现贪吃蛇:从零开始,200行代码搞定经典游戏
------适合新手的完整教程,含详细注释和逐步讲解
一、先看效果
plain
复制
==============================
* 贪吃蛇游戏 *
==============================
Score: 0 Length: 3
##############################
@
*
*
$
##############################
WASD移动 P暂停 ESC退出
运行效果:
@ 代表蛇头
* 代表蛇身
$ 代表食物
代表围墙
二、核心思路:把问题变简单
很多新手觉得贪吃蛇难,是因为想一次性搞定所有功能。正确的做法是:拆成小步骤
表格
步骤 任务 难度
1 画出游戏地图(围墙) ⭐
2 让蛇出现在地图上 ⭐⭐
3 让蛇动起来(自动前进) ⭐⭐
4 用键盘控制方向 ⭐⭐
5 吃到食物变长 ⭐⭐⭐
6 判断撞墙/撞自己 ⭐⭐⭐
记住:每次只测试一个功能,没问题了再进行下一步!
三、完整代码(可直接运行)
c
复制
/*
* C语言贪吃蛇完整代码
* 编译环境:Windows (使用conio.h)
* 编译命令:gcc snake.c -o snake.exe
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h> // Windows下获取键盘输入
#include <windows.h> // Windows下Sleep函数
// ========== 游戏参数配置 ==========
#define WIDTH 20 // 游戏区域宽度
#define HEIGHT 20 // 游戏区域高度
#define MAX_LEN 100 // 蛇的最大长度
// ========== 方向枚举 ==========
typedef enum {
UP = 0,
DOWN,
LEFT,
RIGHT,
STOP // 初始状态/暂停
} Direction;
// ========== 蛇的结构体 ==========
typedef struct {
int x[MAX_LEN]; // 每一节身体的x坐标
int y[MAX_LEN]; // 每一节身体的y坐标
int length; // 当前长度
Direction dir; // 当前移动方向
} Snake;
// ========== 全局变量 ==========
Snake snake; // 蛇
int foodX, foodY; // 食物坐标
int score; // 分数
int gameOver; // 游戏结束标志
int speed = 200; // 移动速度(毫秒,越小越快)
// ========== 函数声明 ==========
void initGame(); // 初始化游戏
void draw(); // 绘制画面
void input(); // 处理输入
void logic(); // 游戏逻辑
void spawnFood(); // 生成食物
void gameOverScreen(); // 游戏结束画面
// ========== 主函数 ==========
int main() {
initGame();
while (!gameOver) {
draw(); // 1. 画出来
input(); // 2. 读键盘
logic(); // 3. 算下一步
Sleep(speed); // 4. 等待(控制速度)
}
gameOverScreen();
return 0;
}
// ========== 初始化游戏 ==========
void initGame() {
// 初始化蛇:从屏幕中间开始,长度3,向右移动
snake.length = 3;
snake.dir = RIGHT;
// 蛇头在中间,身体在左边
for (int i = 0; i < snake.length; i++) {
snake.x[i] = WIDTH / 2 - i; // x坐标递减(向左排)
snake.y[i] = HEIGHT / 2; // y坐标相同(横着排)
}
score = 0;
gameOver = 0;
spawnFood(); // 生成第一个食物
}
// ========== 生成食物 ==========
void spawnFood() {
// 随机生成,但不能生成在蛇身上
int valid;
do {
valid = 1;
foodX = rand() % (WIDTH - 2) + 1; // 1 ~ WIDTH-2(避开围墙)
foodY = rand() % (HEIGHT - 2) + 1; // 1 ~ HEIGHT-2
// 检查是否和蛇身重叠
for (int i = 0; i < snake.length; i++) {
if (snake.x[i] == foodX && snake.y[i] == foodY) {
valid = 0; // 重叠了,重新生成
break;
}
}
} while (!valid);
}
// ========== 绘制画面 ==========
void draw() {
// 清屏(Windows系统)
system("cls");
// 打印标题和分数
printf("==============================\n");
printf("* 贪吃蛇游戏 *\n");
printf("==============================\n");
printf("Score: %d Length: %d\n\n", score, snake.length);
// 打印游戏区域
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
// 1. 打印围墙(边界)
if (x == 0 || x == WIDTH - 1 || y == 0 || y == HEIGHT - 1) {
printf("#");
}
// 2. 打印蛇头
else if (x == snake.x[0] && y == snake.y[0]) {
printf("@");
}
// 3. 打印蛇身
else {
int isBody = 0;
for (int i = 1; i < snake.length; i++) {
if (x == snake.x[i] && y == snake.y[i]) {
printf("*");
isBody = 1;
break;
}
}
// 4. 打印食物
if (!isBody) {
if (x == foodX && y == foodY) {
printf("$");
}
// 5. 打印空白
else {
printf(" ");
}
}
}
}
printf("\n");
}
printf("\nWASD移动 P暂停 ESC退出");
}
// ========== 处理键盘输入 ==========
void input() {
// _kbhit() 检查是否有按键按下(不阻塞)
if (_kbhit()) {
// _getch() 读取按键(不显示在屏幕上)
char key = _getch();
// 防止180度掉头(不能直接反向)
switch (key) {
case 'w':
case 'W':
if (snake.dir != DOWN) snake.dir = UP;
break;
case 's':
case 'S':
if (snake.dir != UP) snake.dir = DOWN;
break;
case 'a':
case 'A':
if (snake.dir != RIGHT) snake.dir = LEFT;
break;
case 'd':
case 'D':
if (snake.dir != LEFT) snake.dir = RIGHT;
break;
case 'p':
case 'P':
// 暂停功能
printf("\n\n游戏暂停,按任意键继续...");
_getch();
break;
case 27: // ESC键
gameOver = 1;
break;
}
}
}
// ========== 游戏核心逻辑 ==========
void logic() {
// 1. 计算新蛇头的位置
int newX = snake.x[0];
int newY = snake.y[0];
switch (snake.dir) {
case UP: newY--; break;
case DOWN: newY++; break;
case LEFT: newX--; break;
case RIGHT: newX++; break;
default: return; // STOP状态不动
}
// 2. 检查是否撞墙
if (newX <= 0 || newX >= WIDTH - 1 || newY <= 0 || newY >= HEIGHT - 1) {
gameOver = 1;
return;
}
// 3. 检查是否撞到自己(从第4节开始检查,前3节不可能撞到)
for (int i = 3; i < snake.length; i++) {
if (newX == snake.x[i] && newY == snake.y[i]) {
gameOver = 1;
return;
}
}
// 4. 检查是否吃到食物
int ateFood = (newX == foodX && newY == foodY);
// 5. 移动蛇身(从尾巴开始,每一节移到前一节的位置)
// 如果没吃到食物,尾巴位置会变成空白(蛇移动了)
// 如果吃到食物,尾巴保留(蛇变长了)
if (!ateFood) {
// 没吃到:正常移动,尾巴消失
for (int i = snake.length - 1; i > 0; i--) {
snake.x[i] = snake.x[i-1];
snake.y[i] = snake.y[i-1];
}
} else {
// 吃到了:长度+1,新尾巴先不赋值(后面统一处理)
snake.length++;
for (int i = snake.length - 1; i > 0; i--) {
snake.x[i] = snake.x[i-1];
snake.y[i] = snake.y[i-1];
}
score += 10;
spawnFood(); // 生成新食物
// 加速(可选)
if (speed > 50) speed -= 5;
}
// 6. 移动蛇头
snake.x[0] = newX;
snake.y[0] = newY;
}
// ========== 游戏结束画面 ==========
void gameOverScreen() {
system("cls");
printf("\n\n");
printf(" ============================\n");
printf(" G A M E O V E R \n");
printf(" ============================\n");
printf("\n");
printf(" 最终得分: %d\n", score);
printf(" 蛇长度: %d\n", snake.length);
printf("\n");
printf(" 按任意键退出...\n");
printf(" ============================\n");
_getch();
}
四、代码详解:重点难点逐个击破
4.1 蛇身怎么表示?------数组的巧妙用法
c
复制
int x[MAX_LEN]; // 存每一节的x坐标
int y[MAX_LEN]; // 存每一节的y坐标
关键理解:
x[0], y[0] 永远是蛇头
x[1], y[1] 是第一节身体
x[length-1], y[length-1] 是尾巴
移动原理(没吃到食物时):
plain
复制
移动前: [H][1][2][3][T] H=头, T=尾, 数字=身体
移动后: [H][1][2][3] T消失,新H出现
代码实现:从尾巴开始,每个点移到前一个点的位置
for (i = 尾巴; i > 0; i--) {
x[i] = x[i-1]; // 第i节变成第i-1节的位置
y[i] = y[i-1];
}
// 最后单独处理蛇头
x[0] = 新位置;
4.2 吃到食物怎么变长?
秘诀:先不删尾巴!
c
复制
if (吃到食物) {
length++; // 长度+1
// 移动时,尾巴位置没人覆盖,自然就变长了!
}
4.3 为什么不能180度掉头?
c
复制
case 'w':
if (snake.dir != DOWN) snake.dir = UP; // 正在向下时,不能向上
否则会出现这种情况:
plain
复制
@\]\[\*\]\[\*\] 蛇向右走
↓ 按左键(180度掉头)
\[\*\]\[@\]\[\*\] 蛇头撞到自己的身体,游戏结束!
4.4 食物生成算法
c
复制
do {
foodX = rand() % (WIDTH - 2) + 1; // 1到WIDTH-2
} while (食物在蛇身上);
取模运算解释:
rand() % 18 → 0\~17
+ 1 → 1\~18(避开0和19的围墙)
五、常见问题 FAQ
Q1:Linux/Mac 怎么编译?
Linux/Mac 没有 conio.h 和 windows.h,需要替换:
c
复制
// 替换清屏
#include \