【C 语言实战】从零实现控制台贪吃蛇游戏,附完整代码与思路解析 [特殊字符]

大家好!今天带大家用 C 语言开发经典的控制台贪吃蛇游戏 🎮。这个项目非常适合刚学完 C 语言基础(数组、循环、函数)的同学练手,既能巩固知识点,又能体验从 0 到 1 做项目的成就感。话不多说,咱们直接开干!

一、项目准备:环境与工具 🛠️

  • 编译环境:Dev-C++、VS2022、GCC(任选其一,控制台程序即可)
  • 核心头文件
    • stdio.h:控制台输入输出
    • conio.h:获取键盘按键(_kbhit ()、_getch () 函数,VS 需加下划线)
    • windows.h:控制窗口大小、延迟(Sleep () 函数)
    • time.h:生成随机数(用于食物位置)
  • 核心思路:用二维数组模拟游戏地图,通过键盘控制蛇的移动方向,检测碰撞(边界、自身、食物),实现得分与游戏结束逻辑。

二、核心功能拆解与代码实现 🚀

1. 定义全局变量:存储游戏状态 📦

首先定义游戏所需的全局变量,比如蛇的坐标、食物位置、得分、方向等,避免频繁传参:’

2. 初始化游戏:开局配置 🔧

游戏启动时需要初始化蛇的初始位置、食物位置、得分等,用init_game()函数实现:


// 初始化游戏

void init_game() {

// 1. 初始化蛇:初始长度2,位置在地图中间偏左

snake.len = 2;

snake.x[0] = WIDTH / 2; // 蛇头x坐标

snake.y[0] = HEIGHT / 2; // 蛇头y坐标

snake.x[1] = WIDTH / 2 - 1; // 蛇身x坐标(在蛇头左边)

snake.y[1] = HEIGHT / 2; // 蛇身y坐标

// 2. 初始化食物(随机位置,不能在蛇身上)

srand((unsigned int)time(NULL)); // 设置随机数种子

food_x = rand() % (WIDTH - 2) + 1; // 食物x:1~WIDTH-2(避开边界)

food_y = rand() % (HEIGHT - 2) + 1; // 食物y:1~HEIGHT-2

// 3. 初始化得分

score = 0;

}

3. 绘制游戏界面:控制台渲染 🎨

用draw_map()函数绘制地图边界、蛇身、食物和得分,每次蛇移动后都要重新绘制:


// 绘制游戏地图

void draw_map() {

// 清屏(避免画面闪烁,控制台常用技巧)

system("cls");

// 1. 绘制上边界(用"■"符号,更清晰)

for (int i = 0; i

printf("■");

}

printf("\n");

// 2. 绘制中间区域(行:HEIGHT-2行,列:WIDTH列)

for (int i = 0; i 2; i++) {

for (int j = 0; j < WIDTH; j++) {

// 绘制左边界

if (j == 0) {

printf("■");

continue;

}

// 绘制右边界

if (j == WIDTH - 1) {

printf("■");

continue;

}

// 绘制蛇头(用"●"区分蛇身)

if (j == snake.x[0] && i == snake.y[0]) {

printf("●");

continue;

}

// 绘制蛇身(用"○")

int is_snake_body = 0;

for (int k = 1; k ++) {

if (j == snake.x[k] && i == snake.y[k]) {

printf("○");

is_snake_body = 1;

break;

}

}

if (is_snake_body) continue;

// 绘制食物(用"★")

if (j == food_x && i == food_y) {

printf("★");

continue;

}

// 空白区域(除了以上元素,都打印空格)

printf(" ");

}

printf("\n");

}

// 3. 绘制下边界

for (int i = 0; i ++) {

printf("■");

}

printf("\n");

// 4. 显示得分和操作提示

printf("当前得分:%d | 操作:W(上) S(下) A(左) D(右) | 退出:ESC\n", score);

}

4. 控制蛇的移动:键盘交互 ⌨️

用control_dir()函数监听键盘按键,修改蛇的移动方向(注意:不能反向移动,比如向右时不能直接向左):


// 控制蛇的移动方向

void control_dir() {

// _kbhit():检测是否有按键按下(非阻塞)

if (_kbhit()) {

// _getch():获取按键(无需按回车,控制台常用)

switch (_getch()) {

case 'w':

case 'W':

if (dir != 2) dir = 1; // 向上:不能从向下反向

break;

case 's':

case 'S':

if (dir != 1) dir = 2; // 向下:不能从向上反向

break;

case 'a':

case 'A':

if (dir != 4) dir = 3; // 向左:不能从向右反向

break;

case 'd':

case 'D':

if (dir != 3) dir = 4; // 向右:不能从向左反向

break;

case 27: // ESC键的ASCII码是27

game_state = 0; // 按下ESC,游戏结束

break;

}

}

}

5. 蛇的移动逻辑:更新坐标 🐾

蛇移动的核心是 “蛇头向前走,蛇身跟着前一个节点走”,如果吃到食物则长度 + 1,否则蛇尾删除:


// 蛇的移动逻辑

void snake_move() {

// 1. 蛇身跟随:从尾到头,每个节点 = 前一个节点的坐标

for (int i = snake.len - 1; i > 0; i--) {

snake.x[i] = snake.x[i - 1];

snake.y[i] = snake.y[i - 1];

}

// 2. 蛇头根据方向移动(x/y坐标变化)

switch (dir) {

case 1: // 上:y坐标-1

snake.y[0]--;

break;

case 2: // 下:y坐标+1

snake.y[0]++;

break;

case 3: // 左:x坐标-1

snake.x[0]--;

break;

case 4: // 右:x坐标+1

snake.x[0]++;

break;

}

// 3. 检测是否吃到食物(蛇头坐标 == 食物坐标)

if (snake.x[0] == food_x && snake.y[0] == food_y) {

score += 10; // 得分+10

snake.len++; // 蛇长度+1

// 重新生成食物(避免在蛇身上)

while (1) {

int is_food_on_snake = 0;

// 随机生成新食物位置

food_x = rand() % (WIDTH - 2) + 1;

food_y = rand() % (HEIGHT - 2) + 1;

// 检查食物是否在蛇身上

for (int i = 0; i ++) {

if (snake.x[i] == food_x && snake.y[i] == food_y) {

is_food_on_snake = 1;

break;

}

}

if (!is_food_on_snake) break; // 不在蛇身上则退出循环

}

}

}

6. 碰撞检测:游戏结束条件 ❌

蛇撞边界或撞自身时,游戏结束,用check_collision()函数实现:


// 碰撞检测(边界、自身)

void check_collision() {

// 1. 撞边界:蛇头超出1~WIDTH-2 或 1~HEIGHT-2

if (snake.x[0] snake.x[0] >= WIDTH - 1 ||

snake.y[0] [0] >= HEIGHT - 1) {

game_state = 0;

printf("\n游戏结束!撞墙了~最终得分:%d\n", score);

}

// 2. 撞自身:蛇头坐标 == 任意蛇身坐标

for (int i = 1; i ++) {

if (snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i]) {

game_state = 0;

printf("\n游戏结束!撞到自己了~最终得分:%d\n", score);

break;

}

}

}

7. 主函数:串联所有模块 🔗

主函数负责调用初始化、绘制、控制、移动、碰撞检测,用循环实现游戏主逻辑:


int main() {

// 1. 初始化游戏

init_game();

// 2. 游戏主循环(game_state=1时运行)

while (game_state) {

draw_map(); // 绘制界面

control_dir(); // 监听键盘控制

snake_move(); // 蛇移动

check_collision(); // 碰撞检测

Sleep(200); // 延迟200ms(控制游戏速度,值越小越快)

}

// 3. 游戏结束,等待按键退出(避免窗口一闪而过)

printf("按任意键退出...\n");

_getch();

return 0;

}

三、运行效果与优化建议 ✨

1. 预期效果

  • 控制台显示 20x10 的地图,蛇头(●)、蛇身(○)、食物(★)清晰可见;
  • 按 W/S/A/D 控制方向,ESC 退出游戏;
  • 吃到食物得分 + 10,蛇变长;
  • 撞边界或自身时,显示最终得分并退出。

2. 优化方向(进阶练习)

  1. 调整游戏速度:得分越高,Sleep () 延迟越小,难度递增;

// 示例:根据得分动态调整速度

int speed = 200 - (score / 10) * 10;

if (speed < 50) speed = 50; // 最低速度限制

Sleep(speed);

  1. 增加关卡:比如每得 100 分进入下一关,速度加快;
  1. 自定义地图:用文件读取地图,添加障碍物;
  1. 记录最高分:将最高分存入文件,下次启动时显示。

四、常见问题解决 ❓

  1. VS 编译报错 “_kbhit 未定义”:VS 中需用_kbhit()和_getch()(加下划线),Dev-C++ 无需;
  1. 画面闪烁严重:除了system("cls"),还可以用 Windows API 的控制台缓冲区刷新(更高级);

// 示例:用API刷新缓冲区,减少闪烁

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos = {0, 0};

SetConsoleCursorPosition(hOut, pos); // 光标移到左上角,覆盖旧内容

  1. 食物生成在蛇身上:代码中已加 “食物不在蛇身上” 的判断,若仍有问题,检查循环逻辑。

话题

#C 语言实战 #贪吃蛇游戏 #控制台程序 #C 语言项目 #编程入门

用windows api 做的贪吃蛇 #include #include"resource.h" #include"Node.h" #include #include TCHAR szAppname[] = TEXT("Snack_eat"); #define SIDE (x_Client/80) #define x_Client 800 #define y_Client 800 #define X_MAX 800-20-SIDE //点x的范围 #define Y_MAX 800-60-SIDE //点y的范围 #define TIME_ID 1 #define SECOND 100 #define NUM_POINT 10 //点的总个数 #define ADD_SCORE 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口类 HACCEL hAccel;//加速键句柄 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口的水平垂直尺寸被改变时,窗口被重绘 wndclass.lpfnWndProc = WndProc; //窗口过程为WndProc函数 wndclass.cbClsExtra = 0; //预留额外空间 wndclass.cbWndExtra = 0; //预留额外空间 wndclass.hInstance = hInstance; //应用程序的实例句柄,WinMain的第一个参数 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //设置图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //载入预定义的鼠标指针 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置画刷 wndclass.lpszMenuName = szAppname; //设置菜单 wndclass.lpszClassName = szAppname; //设置窗口类的名字 if (!RegisterClass(&wndclass))//注册窗口类 { MessageBox(NULL, TEXT("这个程序需要windows NT!"), szAppname, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppname, TEXT("Snack_eat"),//CreateWindow函数调用时,WndProc将受到WM_CREATE WS_OVERLAPPEDWINDOW&~WS_THICKFRAME& ~WS_MAXIMIZEBOX,//普通的层叠窗口&禁止改变大小&禁止最大化 CW_USEDEFAULT, //初始x坐标(默认) CW_USEDEFAULT, //初始y坐标 x_Client, //初始x方向尺寸 770 y_Client, //初始y方向尺寸 750 NULL, //父窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 WinMain函数中第二个参数 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口,iCmdShow是WinMain的第四个参数,决定窗口在屏幕中的初始化显示形式,例:SW_SHOWNORMAL表示正常显示 UpdateWindow(hwnd);//使窗口客户区重绘,通过向WndProc发送一条WM_PAINT消息而完成的 hAccel = LoadAccelerators(hInstance, szAppname);//加载加速键 while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }/* while (GetMessage(&msg, NULL, 0, 0))//GetMessage函数从消息队列中得到消息,填充msg。如果msg.message等于WM_QUIT,返回0,否则返回非0 { TranslateMessage(&msg);//将msg返回给windows已进行某些键盘消息的转换 DispatchMessage(&msg);//将msg再次返回给windows }*/ return msg.wParam;//msg.wParam是PostQuitMessage函数的参数值,通常是0 } ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值