C语言贪吃蛇详解
- 游戏开发
- 2025-07-21 18:14:00

个人简介:双非大二学生
个人博客:Monodye
今日鸡汤:人生就像一盒巧克力,你永远不知道下一块是什么味的
C语言基础刷题:牛客网在线编程_语法篇_基础语法 (nowcoder )
一.贪吃蛇游戏背景 贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。 在编程语⾔的教学中,我们以贪吃蛇为例,从设计到代码实现来提升学⽣的编程能⼒和逻辑能⼒。 二.游戏实现过程大致分为三个大模块:
GameStart完成游戏的初始化打印GameRun游戏运行时各个功能的实现GameEnd游戏结束以后的一些善后工作 2.1游戏功能 实现基本的功能: • 贪吃蛇地图绘制 • 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、减速 • 暂停游戏 2.2需要掌握的知识 C语⾔函数、枚举、结构体、动态 内存管理、预处理指令、链表、Win32 API等。 三.Win32API 3.1Win32API介绍 Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application), 所以便 称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应⽤程序编程接⼝。 3.2控制台程序 平常我们运⾏起来的⿊框程序其实就是控制台程序 我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列 mode con cols=100 lines=30也可以通过命令设置控制台窗口的名字
title 贪吃蛇这些命令我们使用C语言的system便可以实现:
#include <stdio.h> int main() { //设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列 system("mode con cols=100 lines=30"); //设置cmd窗⼝名称 system("title 贪吃蛇"); return 0; }这里我们可以在后面实现的时候加一个getchar(),防止程序运行结束无法确定窗口是否设置成功。
这样就设置好了。
3.3控制台屏幕上的坐标COORD COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕上的坐标 typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;通过这样一个结构体我们就可以给得到控制台上的坐标了。
这里我们可以设置一个坐标
COORD pos = { 10, 15 }; 3.4GetStdHandle GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标 准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。它就像炒菜的手柄,你想获得API函数的操作权就得有一个可以获得他们的手柄。
举个例子:
HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); 3.5GetConsoleCursorInfo 检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息 ,其实就是获得光标的操作权。举个例子:
HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 3.6CONSOLE_CURSOR_INFO 这个结构体,包含有关控制台光标的信息,可以通过它来得到光标的两个参数:可见性,光标所占的百分比, typedef struct _CONSOLE_CURSOR_INFO { DWORD dwSize; BOOL bVisible; } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; 3.7SetConsoleCursorInfo 设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。 举个例子: HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //影藏光标操作 CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 3.8 SetConsoleCursorPosition 设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。 COORD pos = { 10, 5}; HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);这里我们可以利用上面讲到的知识,实现一个设置光标位置的函数:
//设置光标的坐标 void SetPos(short x, short y) { COORD pos = { x, y }; HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos); } 3.9GetAsyncKeyState获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState( int vKey ); 将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。 GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.我们可以将 GetAsyncKeyState的结果与0x1进行&运算这样1代表按键,0代表没按键。
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) 四.GameStart在这个函数里我们主要完成这样几个功能的实现:
设置控制台的信息,窗口的大小,窗口名
隐藏光标
打印欢迎信息
绘制地图
初始化蛇
创建食物
void GameStart(pSnake ps) { //设置控制台的信息,窗口的大小,窗口名 system("mode con cols=100 lines=30"); system("title 贪吃蛇"); //隐藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo); CursorInfo.bVisible = false; SetConsoleCursorInfo(handle, &CursorInfo); //打印欢迎信息 WelcomeToGame(); //绘制地图 GetMap(); //初始化蛇 InitSnake(ps); //创建食物 CreateFood(ps); }这里需要特殊说明一下这里我们打印蛇身的时候使用的是中文符号,可以下载搜狗输入法特殊字符获得,并且,这些中文字符,是普通字符 的二倍大。
五.GameRun用来实现这样几个功能:
打印欢迎信息PrintfHelpInfo();
按键的实现switch语句
蛇的移动SnakeMove();sleep()一下移动一下
void GameRun(pSnake ps) { PrintfHelpInfo(); do { SetPos(62,10); printf("总分:%5d\n",ps->Score); SetPos(62,11); printf("食物的分值:%02d\n",ps->FoodWeight); if (KEY_PRESS(VK_UP) && ps->dir != DOWN) { ps->dir = UP; } else if (KEY_PRESS(VK_DOWN) && ps->dir != UP) { ps->dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT) { ps->dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT) { ps->dir = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { ps->status = ESC; break; } else if (KEY_PRESS(VK_SPACE)) { pause(); } else if (KEY_PRESS(VK_F3)) { if (ps->SleepTime >= 80) { ps->SleepTime -= 30; ps->FoodWeight+= 2; } } else if (KEY_PRESS(VK_F4)) { if (ps->FoodWeight > 2) { ps->SleepTime += 30; ps->FoodWeight -= 2; } } SnakeMove(ps); Sleep(ps->SleepTime); } while (ps->status==OK); //getchar(); }这里是GameRun的基本框架,一些具体的函数嵌套,下文会有详细源码。
六.GameEnd这个函数主要用来进行游戏结束时的一些善后工作,
打印退出游戏的信息
逐个销毁创建的贪吃蛇蛇身节点
释放食物节点指针
void GameEnd(pSnake ps) { SetPos(15,12); switch (ps->status) { case ESC: printf("主动退出游戏,正常退出\n"); break; case KILL_BY_WALL: printf("很遗憾,撞墙了,游戏结束\n"); break; case KILL_BY_SELF: printf("很遗憾,咬到自己了,游戏结束了\n"); break; } PSnakeNode cur = ps->pSnake; PSnakeNode del = NULL; while (cur) { del = cur; cur - cur->next; free(del); } free(ps->pFood); ps->pFood = NULL; } 七.贪吃蛇小项目源码 test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"snake.h" void test() { //创建蛇 Snake snake = {0}; GameStart(&snake); //GameRun(&snake); //GameEnd(&snake); } int main() { //修改适配为中文环境 setlocale(LC_ALL,""); test(); return 0; } Snake.h #define _CRT_SECURE_NO_WARNINGS 1 #include <locale.h> #include<stdio.h> #include"stdlib.h" #include<windows.h> #include<stdbool.h> #define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0) #define WALL L'□' #define BODY L'●' #define Food L'★' #define POS_X 24 #define POS_Y 5 enum GAME_STATUS { OK = 1, ESC, KILL_BY_WALL, KILL_BY_SELF }; enum DIRECTION { UP=1, DOWN, LEFT, RIGHT }; //蛇身节点的定义 typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode, *PSnakeNode; //贪吃蛇 typedef struct Snake { PSnakeNode pSnake; PSnakeNode pFood; int Score; int FoodWeight; int SleepTime; enum GAME_STATUS status; enum DIRECTION dir; }Snake,*pSnake; void GameStart(pSnake ps); void WelcomeToGame(); void GetMap(); void InitSnake(pSnake ps); void CreateFood(pSnake ps); void GameRun(pSnake ps); void PrintfHelpInfo(); void SnakeMove(pSnake ps); int NextIsFood(pSnake ps,PSnakeNode pNext); void EatFood(pSnake ps, PSnakeNode pNext); void NotFood(pSnake ps, PSnakeNode pNext); void killByWall(pSnake ps); void KillBySelf(pSnake ps); void GameEnd(pSnake ps); Snake.c #define _CRT_SECURE_NO_WARNINGS 1 #include"snake.h" void SetPos(int x,int y) { HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x,y }; SetConsoleCursorPosition(handle,pos); } void WelcomeToGame() { SetPos(35,10); printf("欢迎来到贪吃蛇小游戏\n"); SetPos(38,20); system("pause"); system("cls"); //功能介绍界面 SetPos(15,10); printf("用↑.↓.←.→来控制蛇的移动,F3是加速,F4是减速\n"); SetPos(15,11); printf("加速得到更高的分数\n"); SetPos(38, 20); system("pause"); system("cls"); } void GetMap() { //上 SetPos(0,0); int i = 0; for ( i = 0;i <= 56; i += 2) { wprintf(L"%lc", WALL); } //下 SetPos(0,26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (i = 1; i <= 25; i ++) { SetPos(0,i); wprintf(L"%lc", WALL); } //右 for (i = 1; i <= 25; i++) { SetPos(56,i); wprintf(L"%lc", WALL); } } void InitSnake(pSnake ps) { PSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) { cur = (PSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake malloc!"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; //头插法 if (ps->pSnake == NULL) { ps->pSnake = cur; } else { cur->next = ps->pSnake; ps->pSnake = cur; } } cur = ps->pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } ps->dir = RIGHT; ps->FoodWeight = 10; ps->pFood = NULL; ps->Score = 0; ps->SleepTime = 200; ps->status = OK; } void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2; y = rand() % 24 + 1; } while (x%2!=0); PSnakeNode cur = ps->pSnake; while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; } PSnakeNode pFood = (PSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreateFood():malloc"); return; } pFood->x=x; pFood->y=y; ps->pFood = pFood; SetPos(x,y); wprintf(L"%lc",Food); } void GameStart(pSnake ps) { //设置控制台的信息,窗口的大小,窗口名 system("mode con cols=100 lines=30"); system("title 贪吃蛇"); //隐藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo); CursorInfo.bVisible = false; SetConsoleCursorInfo(handle, &CursorInfo); //打印欢迎信息 WelcomeToGame(); //绘制地图 GetMap(); //初始化蛇 InitSnake(ps); //创建食物 CreateFood(ps); } void PrintfHelpInfo() { SetPos(62,15); printf("1.不能穿墙,不能咬到自己\n"); SetPos(62,16); printf("用↑.↓.←.→来控制蛇的移动\n"); SetPos(62,17); printf("F3是加速,F4是减速\n"); SetPos(62,19); printf("版权@Monodye\n"); } void pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextIsFood(pSnake ps,PSnakeNode pNext) { if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) { return 1; } else return 0; } void EatFood(pSnake ps, PSnakeNode pNext) { pNext->next = ps->pSnake; ps->pSnake = pNext; PSnakeNode cur = ps->pSnake; while (cur) { SetPos(cur->x,cur->y); wprintf(L"%lc",BODY); cur = cur->next; } ps->Score += ps->FoodWeight; free(ps->pFood); CreateFood(ps); } void NotFood(pSnake ps, PSnakeNode pNext) { pNext->next = ps->pSnake; ps->pSnake = pNext; PSnakeNode cur = ps->pSnake; while (cur->next->next ) { SetPos(cur->x,cur->y); wprintf(L"%lc",BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); free(cur->next); cur->next = NULL; } void killByWall(pSnake ps) { if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26) { ps->status = KILL_BY_SELF; } } void KillBySelf(pSnake ps) { PSnakeNode cur = ps->pSnake->next; while (cur) { if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y) { ps->status = KILL_BY_SELF; return 0; } cur = cur->next; } } void SnakeMove(pSnake ps) { PSnakeNode pNext=(PSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove():malloc()"); return; } pNext->next = NULL; switch (ps->dir) { case UP: pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y-1; break; case DOWN: pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y +1; break; case LEFT: pNext->x = ps->pSnake->x-2; pNext->y = ps->pSnake->y ; break; case RIGHT: pNext->x = ps->pSnake->x+2; pNext->y = ps->pSnake->y; break; } if (NextIsFood(ps,pNext)) { EatFood(ps,pNext); } else { NotFood(ps,pNext); } killByWall(ps); KillBySelf(ps); } void GameRun(pSnake ps) { PrintfHelpInfo(); do { SetPos(62,10); printf("总分:%5d\n",ps->Score); SetPos(62,11); printf("食物的分值:%02d\n",ps->FoodWeight); if (KEY_PRESS(VK_UP) && ps->dir != DOWN) { ps->dir = UP; } else if (KEY_PRESS(VK_DOWN) && ps->dir != UP) { ps->dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT) { ps->dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT) { ps->dir = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { ps->status = ESC; break; } else if (KEY_PRESS(VK_SPACE)) { pause(); } else if (KEY_PRESS(VK_F3)) { if (ps->SleepTime >= 80) { ps->SleepTime -= 30; ps->FoodWeight+= 2; } } else if (KEY_PRESS(VK_F4)) { if (ps->FoodWeight > 2) { ps->SleepTime += 30; ps->FoodWeight -= 2; } } SnakeMove(ps); Sleep(ps->SleepTime); } while (ps->status==OK); //getchar(); } void GameEnd(pSnake ps) { SetPos(15,12); switch (ps->status) { case ESC: printf("主动退出游戏,正常退出\n"); break; case KILL_BY_WALL: printf("很遗憾,撞墙了,游戏结束\n"); break; case KILL_BY_SELF: printf("很遗憾,咬到自己了,游戏结束了\n"); break; } PSnakeNode cur = ps->pSnake; PSnakeNode del = NULL; while (cur) { del = cur; cur - cur->next; free(del); } free(ps->pFood); ps->pFood = NULL; }