摘要:本游戏从零开始实现贪吃蛇大作战,玩家需要控制一条蛇不断进食,同时避免触碰自身或游戏边界。游戏采用经典的贪吃蛇玩法,通过键盘控制蛇的移动方向,不断吞噬食物来增长身体长度。游戏具有高度的互动性和趣味性,能够锻炼玩家的反应能力和策略思维。实现过程中需要掌握游戏编程基础,包括数据结构、算法、图形渲染等技术。
个人主页:星纭-CSDN博客
系列文章专栏 : C语言
踏上取经路,比抵达灵山更重要!一起努力一起进步!
有关Win32API的知识点在上一篇文章:WIN32 API
目录
一.地图
1.控制台基本介绍
2.宽字符
1.本地化
2.类项
3.setlocale函数
4.打印宽字符
3.地图坐标
二.游戏的结构设计
1.基础结构
2.游戏流程(未完成)
3.核心逻辑实现
1.游戏开始
1.控制台设置
2.欢迎界面
3.创建地图
4.初始化蛇
5.创建食物
4.游戏运行
1.打印信息
2.判断按键
3.蛇的移动
5.游戏结束
一.地图
这个游戏中,我们是通过控制台来完成的。首先就是需要完成这个地图。
1.控制台基本介绍
接下来介绍有关控制台窗口的一些知识点,当运行程序,需要在控制台上输出信息,打印的时候,第一个字符是在控制台最左上角打印的。我们将这个位置的坐标当作(0,0).
横向的坐标轴称为x轴,从左向右依次增长,纵向的坐标轴是y轴,从上到下依次增长。
2.宽字符
普通的字符是占一个字节的,宽字符是占用两个字节的。
打印字符,不难发现,两个英文字符的宽度是等于一个中文字符的宽度的。 而且一个英文字符的高大概是其宽的两倍。
1.本地化
提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。
在标准中,以来地区的部分有以下几项:
- 数字量的格式
- 货币量的格式
- 字符集
- 日期和时间的表示形式
2.类项
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:
- • LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
- • LC_CTYPE:影响字符处理函数的⾏为。
- • LC_MONETARY:影响货币格式。
- • LC_NUMERIC:影响 printf() 的数字格式。
- • LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
- • LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
参考:setlocale,_wsetlocale | Microsoft Learn
3.setlocale函数
char *setlocale( int category, const char *locale );
这个函数用于修改当前地区,可针对一个类项修改,也可以针对所有类型。
第一个参数是前面说明的类项目中的一个,那么每次都只会影响一个类项,如果第一个参数是LC_ALL就会影响所有的类项。
C标准给第二个参数仅仅定义了两种取值:“C”正常模式和“”本地模式
在任意程序开始执行之前,都会隐藏执行调用:
setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。 当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤""作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。 ⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
setlocale(LC_ALL, " ");//切换到本地环境
4.打印宽字符
如果想要在屏幕上打印宽字符,那么该如何打印呢?
为了区分宽字符与普通字符,宽字符的字面量前面必须加上前缀 L ,否则C语言会把字面量当作窄字符类型来处理。
同时还需要另一个函数wprintf函数来打印宽字符,用法与printf一样。宽字符的占位符为%lc,宽字符串的占位符为%ls.
setlocale(LC_ALL, ""); wprintf(L"%lc\n", L'□'); wprintf(L"%lc\n", L'●'); wprintf(L"%lc\n", L'★');
打印一个宽字符选哟占用两个字符的位置,那么我们在贪吃蛇中使用宽字符,就需要处理好地图上坐标的位置的计算。
3.地图坐标
我们假设实现一个三十行六十列的地图,在围绕地图画出墙。
#define WALL L'□' //墙 #define BODY L'●' //蛇身 #define FOOD L'★' //食物
需要打印的东西总共有三个分别是墙,蛇身,食物。为了方便,我们define。
1.蛇身与食物
初始化状态,假设蛇的其实长度是5,蛇的每一个节点都是BODY.并且在一个固定的位置开始生成。
从该图不难发现,蛇与食物的每一个节点的坐标都应该是偶数,不能是技术,否则就就不对其了,而且会出现身体一半在墙体之内,一半在墙体之外的现象。
二.游戏的结构设计
1.基础结构
在游戏运行过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表来储存蛇的信息,那么蛇的每一个节点其实就是链表的每一个节点,每一个节点就需要记录好蛇身节点的位置,以及下一个节点的位置。
typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode,*pSnakeNode;
为了方便管理这个游戏,我们在封装一个Snake的结构来维护整个贪吃蛇。
//蛇的运动方向:上下左右 enum DIRECTION { UP = 1, DOWN, RIGHT, LEFT }; //游戏运行状态:正常运行,撞墙,撞自己,非正常结束。 //非正常结束:比如按Esc退出游戏。 enum GAME_STATUS { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; typedef struct GreedySnake { pSnakeNode _pSnake; pSnakeNode _pFood; enum DIRECTION _Dir; enum GAME_STATUS _Status; int _Score; int _FoodWeight; int _SleepTime;//可以理解为蛇的运行速度。 }GSnake,*pGSnake;
2.游戏流程(未完成)
3.核心逻辑实现
程序开始就设置程序⽀持本地模式,然后进⼊游戏的主逻辑。 主逻辑分为3个过程:
- • 游戏开始(GameStart)完成游戏的初始化
- • 游戏运⾏(GameRun)完成游戏运⾏逻辑的实现
- • 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
1.游戏开始
这个模块需要完成游戏的初始化任务。
- 控制台的设置
- 欢迎界面
1.控制台设置
//一.设置控制台以及光标 //设置控制台窗口的大小 system("mode con cols=100 lines=30"); //设置控制台窗口的名字 system("title 贪吃蛇"); //获得标准输出设备的句柄 HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info = { 0 }; //获取和houput句柄相关的控制台上的光标的信息,存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info); //修改光标信息 cursor_info.bVisible = false;//可见度 //设置和houtput句柄相关的控制台上的光标的信息 SetConsoleCursorInfo(houtput, &cursor_info);
在这里需要更改控制台窗口的名字,以及将光标不可见。
2.欢迎界面
1.首先需要完成坐标定位函数,这样方便我们在特定的位置打印信息
void SetPos(int x, int y) { HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos); }
2.然后就是欢迎界面以及游戏规则的讲解
void WelcomeToGame() { SetPos(36,13); printf("欢迎来到星纭的贪吃蛇小游戏"); SetPos(40,25); system("pause"); system("cls"); SetPos(15,10); printf("游戏规则介绍:"); SetPos(24,13); wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n"); SetPos(36,16); printf("加速可以获得更更高的分数"); SetPos(40,25); system("pause"); system("cls"); }
3.创建地图
创建地图就是将地图打印出来,因为是宽字符打印,所以使用wprintf函数进行打印。
void CreateMap() { int i = 0; //上面的墙 for (i = 0; i y = POS_Y; 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->_SleepTime = 200; ps->_Score = 0; ps->_Dir = RIGHT; ps->_Status = OK; ps->_FoodWeight = 10; }
5.创建食物
void CreateFood(pGSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 55 + 2; y = rand() % 29 + 1; } while (x % 2 != 0); pSnakeNode cur = ps->_pSnake; while (cur) { if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); pFood->x = x; pFood->y = y; SetPos(x, y); wprintf(L"%lc",FOOD); ps->_pFood = pFood; };
4.游戏运行
游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64,15)
根据游戏状态检测游戏是否继续,如果状态时OK,游戏继续,其他状态游戏结束。
并且根据游戏的过程中,按键的情况来确定蛇的下一步方向,或者是否加速减速,暂停或者退出游戏。
- • 上:VK_UP
- • 下:VK_DOWN
- • 左:VK_LEFT
- • 右:VK_RIGHT
- • 空格:VK_SPACE
- • ESC:VK_ESCAPE
- • F3:VK_F3
- • F4:VK_F4
这是所需的虚拟按键
1.打印信息
void PrintHelpInfo() { SetPos(60,10); wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动方向"); SetPos(70,12); wprintf(L"按F3加速,F4减速"); SetPos(66,14); printf("加速可以获得更更高的分数"); }
在控制台窗口中,打印游戏规则以及分数。
2.判断按键
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 1) ? 1 : 0)
3.蛇的移动
先创建下⼀个节点,根据移动⽅向和蛇头的坐标,蛇移动到下⼀个位置的坐标。 确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是⻝物就做吃⻝物处理 (EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。 蛇⾝移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从⽽影 响游戏的状态。
int NextisFood(pSnakeNode pNextNode, pGSnake ps) { if (ps->_pFood->x == pNextNode->x && ps->_pFood->y == pNextNode->y) { return 1; } return 0; } void EatFood(pSnakeNode pNextNode, pGSnake ps) { ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood; free(pNextNode); pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", L'●'); cur = cur->next; } ps->_Score += ps->_FoodWeight; CreateFood(ps); } void NoFood(pSnakeNode pNextNode, pGSnake ps) { pNextNode->next = ps->_pSnake; ps->_pSnake = pNextNode; 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(pGSnake ps) { if ( (ps->_pSnake->x == 0) || (ps->_pSnake->x == 58) || (ps->_pSnake->y == 0) || (ps->_pSnake->y == 29) ) { ps->_Status = KILL_BY_WALL; return 1; } return 0; } void KillBySelf(pGSnake ps) { pSnakeNode cur = ps->_pSnake->next; while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_pSnake = KILL_BY_SELF; break; } cur = cur->next; } } void SnakeMove(pGSnake ps) { pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode)); switch (ps->_Dir) { case UP: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y - 1; break; case DOWN: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y + 1; break; case LEFT: pNextNode->x = ps->_pSnake->x - 2; pNextNode->y = ps->_pSnake->y; break; case RIGHT: pNextNode->x = ps->_pSnake->x + 2; pNextNode->y = ps->_pSnake->y; break; } if (NextisFood(pNextNode,ps)) { EatFood(pNextNode, ps); } else { NoFood(pNextNode, ps); } KillByWall(ps); KillBySelf(ps); }
5.游戏结束
游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇⾝节点。
void GameEnd(pGSnake ps) { system("cls"); SetPos(32, 12); switch (ps->_Status) { case END_NORMAL: printf("游戏已结束。"); break; case KILL_BY_WALL: printf("蛇撞墙!游戏结束。"); break; case KILL_BY_SELF: printf("蛇撞到自己!游戏结束。"); break; } printf("总得分:%d", ps->_Score); // pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } SetPos(0, 26); }
三.代码
greedysnake.h
#pragma once #include #include #include #include #include #include #define WALL L'□' //墙 #define BODY L'●' //蛇 #define FOOD L'★' //食物 typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode, * pSnakeNode; //蛇的运动方向:上下左右 enum DIRECTION { UP = 1, DOWN, RIGHT, LEFT }; //游戏运行状态:正常运行,撞墙,撞自己,非正常结束。 //非正常结束:比如按Esc退出游戏。 enum GAME_STATUS { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; typedef struct GreedySnake { pSnakeNode _pSnake; pSnakeNode _pFood; enum DIRECTION _Dir; enum GAME_STATUS _Status; int _Score; int _FoodWeight; int _SleepTime;//可以理解为蛇的运行速度。 }GSnake,*pGSnake; #define POS_X 10 //起始位置x #define POS_Y 5 //起始位置y //游戏开始 void GameStart(pGSnake ps); void WelcomeToGame(); void CreateMap(); void InitSnake(pGSnake ps); void CreateFood(pGSnake ps); //游戏运行 void GameRun(pGSnake ps); void PrintHelpInfo(); #define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 1) ? 1 : 0) void SnakeMove(pGSnake ps); int NextisFood(pSnakeNode pNextNode, pGSnake ps); void EatFood(pSnakeNode pNextNode, pGSnake ps); void NoFood(pSnakeNode pNextNode, pGSnake ps); void KillByWall(pGSnake ps); void KillBySelf(pGSnake ps); void GameEnd(pGSnake ps);
greedysnake.c
#define _CRT_SECURE_NO_WARNINGS #include"greedysnake.h" void SetPos(int x, int y) { HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos); } void WelcomeToGame() { SetPos(36, 13); printf("欢迎来到星纭的贪吃蛇小游戏"); SetPos(40, 25); system("pause"); system("cls"); SetPos(15, 10); printf("游戏规则介绍:"); SetPos(24, 13); wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n"); SetPos(36, 16); printf("加速可以获得更更高的分数"); SetPos(40, 25); system("pause"); system("cls"); } void CreateMap() { int i = 0; //上面的墙 for (i = 0; i y = POS_Y; 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->_SleepTime = 200; ps->_Score = 0; ps->_Dir = RIGHT; ps->_Status = OK; ps->_FoodWeight = 10; } void CreateFood(pGSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 47 + 6; y = rand() % 25 + 3; } while (x % 2 != 0); pSnakeNode cur = ps->_pSnake; while (cur) { if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); pFood->x = x; pFood->y = y; SetPos(x, y); wprintf(L"%lc", FOOD); ps->_pFood = pFood; }; void GameStart(pGSnake ps) { //一.设置控制台以及光标 //设置控制台窗口的大小 system("mode con cols=100 lines=36"); //设置控制台窗口的名字 system("title 贪吃蛇"); //获得标准输出设备的句柄 HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info = { 0 }; //获取和houput句柄相关的控制台上的光标的信息,存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info); //修改光标信息 cursor_info.bVisible = false;//可见度 //设置和houtput句柄相关的控制台上的光标的信息 SetConsoleCursorInfo(houtput, &cursor_info); //二.打印欢迎界面 WelcomeToGame(); //三.创建地图 CreateMap(); //四.初始化蛇 InitSnake(ps); //五.随机生成食物 CreateFood(ps); }; void PrintHelpInfo() { SetPos(60, 10); wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动方向"); SetPos(70, 12); wprintf(L"按F3加速,F4减速"); SetPos(66, 14); printf("加速可以获得更更高的分数"); } void pause() { while (1) { Sleep(1); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextisFood(pSnakeNode pNextNode, pGSnake ps) { if (ps->_pFood->x == pNextNode->x && ps->_pFood->y == pNextNode->y) { return 1; } return 0; } void EatFood(pSnakeNode pNextNode, pGSnake ps) { ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood; free(pNextNode); pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", L'●'); cur = cur->next; } ps->_Score += ps->_FoodWeight; CreateFood(ps); } void NoFood(pSnakeNode pNextNode, pGSnake ps) { pNextNode->next = ps->_pSnake; ps->_pSnake = pNextNode; 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(pGSnake ps) { if ( (ps->_pSnake->x == 0) || (ps->_pSnake->x == 58) || (ps->_pSnake->y == 0) || (ps->_pSnake->y == 29) ) { ps->_Status = KILL_BY_WALL; return 1; } return 0; } void KillBySelf(pGSnake ps) { pSnakeNode cur = ps->_pSnake->next; while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_Status = KILL_BY_SELF; break; } cur = cur->next; } } void SnakeMove(pGSnake ps) { pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode)); switch (ps->_Dir) { case UP: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y - 1; break; case DOWN: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y + 1; break; case LEFT: pNextNode->x = ps->_pSnake->x - 2; pNextNode->y = ps->_pSnake->y; break; case RIGHT: pNextNode->x = ps->_pSnake->x + 2; pNextNode->y = ps->_pSnake->y; break; } if (NextisFood(pNextNode, ps)) { EatFood(pNextNode, ps); } else { NoFood(pNextNode, ps); } KillByWall(ps); KillBySelf(ps); } void GameRun(pGSnake ps) { //打印帮助信息 PrintHelpInfo(); //循环 do { SetPos(66, 8); printf("游戏得分:%4d", ps->_Score); printf("食物重量:%4d", 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_SPACE)) { pause(); } else if (KEY_PRESS(VK_ESCAPE)) { ps->_Status = END_NORMAL; break; } else if (KEY_PRESS(VK_F3)) { if (ps->_SleepTime > 80) { ps->_SleepTime -= 30; ps->_FoodWeight += 2; } } else if (KEY_PRESS(VK_F4)) { if (ps->_SleepTime _SleepTime += 30; ps->_FoodWeight -= 2; } } Sleep(ps->_SleepTime); SnakeMove(ps); } while (ps->_Status == OK); } void GameEnd(pGSnake ps) { system("cls"); SetPos(32, 12); switch (ps->_Status) { case END_NORMAL: printf("游戏已结束。"); break; case KILL_BY_WALL: printf("蛇撞墙!游戏结束。"); break; case KILL_BY_SELF: printf("蛇撞到自己!游戏结束。"); break; } printf("总得分:%d", ps->_Score); pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } SetPos(0, 26); }
源.c
#define _CRT_SECURE_NO_WARNINGS #include"greedysnake.h" int main() { srand((unsigned int)time(NULL)); setlocale(LC_ALL, ""); GSnake snake = { 0 }; //游戏初始化 GameStart(&snake); //游戏运行中 GameRun(&snake); GameEnd(&snake); SetPos(0, 33); system("pause"); return 0; }
还没有评论,来说两句吧...