C语言写一个贪吃蛇游戏

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 \ void clearScreen() { printf("\\033\[2J\\033\[H"); // ANSI清屏码 // 或 system("clear"); } // 替换Sleep #include \ usleep(speed \* 1000); // 微秒 // 替换_kbhit和_getch #include \ #include \ // 需要额外实现非阻塞输入,建议用ncurses库 推荐: Windows下用Dev-C++或VS Code+MinGW最简单。 Q2:怎么让蛇移动更流畅? 当前方案:Sleep(200) 是卡顿的,因为200ms才刷新一次。 进阶方案:多线程或精确计时 c 复制 // 非阻塞输入 + 定时刷新(伪代码) while(1) { if (时间到了200ms) { moveSnake(); // 自动移动 draw(); } if (有按键) { changeDirection(); // 改变方向 } } Q3:怎么添加更多功能? 表格 功能 实现思路 关卡系统 不同速度、不同地图 障碍物 在地图上固定几个 # 最高分记录 读写文件 fopen("score.txt", "r") 双人模式 两个Snake结构体,两套控制键 六、学习建议:如何吃透这段代码 第一步:抄 把代码完整敲一遍(不要复制粘贴) 确保能编译运行 第二步:改 改 WIDTH 和 HEIGHT,看效果 改 speed 初始值,调整难度 把 @ 改成其他符号 第三步:加 添加暂停功能(已完成) 添加障碍物 添加生命值系统 第四步:创 把围墙改成 #,身体改成 o,更美观 添加颜色(Windows API SetConsoleTextAttribute) 用 EasyX 或 SDL 做图形界面版 七、完整项目结构 plain 复制 snake/ ├── snake.c // 主代码(本文代码) ├── README.md // 项目说明 └── screenshot.png // 运行截图 GitHub 提交信息建议: plain 复制 feat: 基础移动功能 feat: 添加食物和吃食物逻辑 feat: 添加撞墙检测 fix: 修复180度掉头bug docs: 添加代码注释 八、总结 表格 知识点 在本项目中的体现 数组 蛇身坐标存储 结构体 Snake封装 枚举 Direction方向 循环 遍历蛇身、绘制地图 函数 模块化设计 指针 (进阶)可优化为链表实现 200行代码,涵盖了C语言的核心语法。 贪吃蛇是练手经典项目,因为它: ✅ 逻辑清晰(输入→处理→输出) ✅ 实时交互(键盘控制) ✅ 视觉效果(即时反馈) ✅ 可扩展性强( endless optimization )

相关推荐
小猪努力学前端1 天前
基于PixiJS的试玩广告开发-续篇
前端·javascript·游戏
RuoZoe8 天前
重塑WPF辉煌?基于DirectX 12的现代.NET UI框架Jalium
c语言
xiezhr10 天前
米哈游36岁程序员被曝复工当晚猝死出租屋内
游戏·程序员·游戏开发
祈安_12 天前
C语言内存函数
c语言·后端
norlan_jame13 天前
C-PHY与D-PHY差异
c语言·开发语言
czy878747513 天前
除了结构体之外,C语言中还有哪些其他方式可以模拟C++的面向对象编程特性
c语言
悠哉悠哉愿意13 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
m0_5312371713 天前
C语言-数组练习进阶
c语言·开发语言·算法
爱搞虚幻的阿恺13 天前
Niagara粒子系统-超炫酷的闪电特效(加餐 纸牌螺旋上升效果)
游戏·游戏引擎