贪吃蛇——c语言版
文章目录
- 演示效果
- 实现的基本功能
- 技术要点
- 源代码
- 实现功能
- GameStart
- 打印欢迎界面和功能介绍
- 绘制地图
- 创建蛇
- 创建食物
- GameRun
- 打印提示信息
- 蛇每走一步
- GameEnd
- 蛇死亡后继续游戏
演示效果
贪吃蛇1.0演示视频
将终端应用程序改为控制台主机
实现的基本功能
- 贪吃蛇地图绘制
- 蛇吃食物的功能(上、下、左、右⽅向键控制蛇的动作)
- 蛇撞墙死亡
- 蛇撞⾃⾝死亡
- 计算得分
- 蛇⾝加速、减速
- 暂停游戏
技术要点
c语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等等……
源代码
snake.h
#pragma once #include #include #include #include #include #include #include #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0) #define WALL L'□' #define BODY L'●' #define FOOD L'★' #define POS_X 24 #define POS_Y 5 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; enum CONDITION { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode,* pSnakeNode; typedef struct Snake { pSnakeNode pSnakebody;//指向蛇头的节点 pSnakeNode pFood;//指向食物的节点 enum DIRECTION Dir;//方向 enum CONDITION Cdt;//状态 int Food_Weight;//食物的权重 int Socre;//总分数 int Sleep_Time;//睡眠时间 }Snake,* pSnake; //初始化 void GameStart(pSnake ps); //定位 void SetPos(short x, short y); //打印欢迎界面和功能介绍 void Welcome_To_Game(); //绘制地图 void CreateMap(); //创建蛇 void InitSnake(pSnake ps); //创建食物 void CreateFood(pSnake ps); //运行游戏 void GameRun(pSnake ps); //打印提示信息 void PrintHelpInfo(); //蛇走的过程 void SnakeMove(pSnake ps); //判断下一个节点是不是食物 bool NextIsFood(pSnakeNode pn,pSnake ps); //如果是食物,就吃掉,然后创建新的食物 void EatFood(pSnakeNode pn,pSnake ps); //如果不是食物,就把最后一个节点释放 void NotFood(pSnakeNode pn, pSnake ps); //判断下一步是否撞墙 void KillByWall(pSnake ps); //判断下一步是否撞到自己 void KillBySelf(pSnake ps); //善后工作 void GameEnd(pSnake ps);
snake.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "Snake.h" void CreateFood(pSnake ps) { int x = 0; int y = 0; //食物的坐标在棋盘内 again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0); //食物不能和蛇身冲突 pSnakeNode cur = ps->pSnakebody; while (cur) { if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } //创建食物 pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode)); if (Food == NULL) { perror("malloc fail"); return; } Food->next = NULL; Food->x = x; Food->y = y; SetPos(x, y); wprintf(L"%lc", FOOD); ps->pFood = Food; } void SetPos(short x, short y) { HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x,y }; SetConsoleCursorPosition(houtput, pos); } void InitSnake(pSnake ps) { ps->pSnakebody = NULL; pSnakeNode cur = NULL; for (int i = 0; i next = NULL; cur->x = POS_X + i * 2; cur->y = POS_Y; //头插 if (ps->pSnakebody == NULL) { ps->pSnakebody = cur; } else { cur->next = ps->pSnakebody; ps->pSnakebody = cur; } } //打印身体 cur = ps->pSnakebody; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //初始化贪吃蛇的数据 ps->Cdt = OK; ps->Dir = RIGHT; ps->Food_Weight = 10; ps->Socre = 0; ps->Sleep_Time = 200;//单位毫秒 } void CreateMap() { int i = 0; //上 for (i = 0; i x && pn->y == ps->pFood->y); } //如果是食物,就吃掉,然后创建新的食物 void EatFood(pSnakeNode pn, pSnake ps) { //头插 ps->pFood->next = ps->pSnakebody; ps->pSnakebody = ps->pFood; //释放下一个位置的节点 free(pn); pn = NULL; //打印 pSnakeNode cur = ps->pSnakebody; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } ps->Socre += ps->Food_Weight; CreateFood(ps); } //如果不是食物,就把最后一个节点释放 void NotFood(pSnakeNode pn, pSnake ps) { //头插 pn->next = ps->pSnakebody; ps->pSnakebody = pn; //最后一个节点,要释放 pSnakeNode cur = ps->pSnakebody; while (cur->next->next)//打印前n个 { 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->pSnakebody->x == 56 || ps->pSnakebody->x == 0 || ps->pSnakebody->y == 0 || ps->pSnakebody->y == 26) { ps->Cdt = KILL_BY_WALL; } } //判断下一步是否撞到自己 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->pSnakebody->next; while (cur) { if (cur->x == ps->pSnakebody->x && cur->y == ps->pSnakebody->y) { ps->Cdt = KILL_BY_SELF; } cur = cur->next; } } //蛇走的过程 void SnakeMove(pSnake ps) { //创建下一个节点 pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNextNode == NULL) { perror("pNextNode malloc fail"); return; } switch (ps->Dir) { case UP://向上 pNextNode->x = ps->pSnakebody->x; pNextNode->y = ps->pSnakebody->y - 1; break; case DOWN://向下 pNextNode->x = ps->pSnakebody->x; pNextNode->y = ps->pSnakebody->y + 1; break; case LEFT://向左 pNextNode->x = ps->pSnakebody->x - 2; pNextNode->y = ps->pSnakebody->y; break; case RIGHT://向右 pNextNode->x = ps->pSnakebody->x + 2; pNextNode->y = ps->pSnakebody->y; break; } //判断下一个节点是不是食物 if (NextIsFood(pNextNode, ps)) { EatFood(pNextNode, ps); } else { NotFood(pNextNode, ps); } KillByWall(ps);//下一步是墙 KillBySelf(ps);//下一步是自己 } //运行游戏 void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(60, 10); printf("食物分数:%2d", ps->Food_Weight); SetPos(60, 11); printf("总分数:%2d", ps->Socre); 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->Cdt = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { pause(); } else if (KEY_PRESS(VK_F1)) { if (ps->Sleep_Time > 80) { ps->Sleep_Time -= 30; ps->Food_Weight += 2; } } else if (KEY_PRESS(VK_F2)) { if (ps->Sleep_Time Sleep_Time += 30; ps->Food_Weight -= 2; } } //让蛇走起来 SnakeMove(ps); Sleep(ps->Sleep_Time); } while (ps->Cdt == OK); } //善后工作 void GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->Cdt) { case END_NORMAL: printf("您主动结束了游戏"); break; case KILL_BY_WALL: printf("您撞到墙死了"); break; case KILL_BY_SELF: printf("您咬到了自己"); } //释放蛇身节点 pSnakeNode cur = ps->pSnakebody; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } }
snaketest.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "Snake.h" void test() { int ch = 0; do { system("cls"); Snake snake; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 14); printf("再来一局吗?(Y/N):"); ch = getchar(); while (getchar() != '\n'); } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); } int main() { //创建贪吃蛇 setlocale(LC_ALL, ""); srand((unsigned int)time(NULL)); test(); return 0; //初始化游戏 //运行游戏 //结束游戏 }
实现功能
在地图上我们打印墙体、蛇、食物都是使用宽字符,普通字符只占字节,宽字符占两个
中文也是两个宽字符,所以我们本地化所有类项即可
setlocale(LC_ALL, "");
再实现功能以前,我们可以将要实现的功能分成三大类:
GameStart(初始化游戏)
GameRun(运行游戏)
GameEnd(游戏善后工作)
GameStart
再绘制地图以前,我们需要设置控制台窗口的长宽:100列,30行
顺便设置窗口的名字为贪吃蛇
void GameStart(pSnake ps) { system("mode con cols=100 lines=30"); system("title 贪吃蛇"); }
隐藏光标并且获得句柄
void GameStart(pSnake ps) { system("mode con cols=100 lines=30"); system("title 贪吃蛇"); //获取标准输出设备的句柄 HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定义光标信息结构体 CONSOLE_CURSOR_INFO CursorInfo; //获取光标信息 GetConsoleCursorInfo(houtput, &CursorInfo); //修改 CursorInfo.bVisible = false; //设置 SetConsoleCursorInfo(houtput, &CursorInfo); }
修改光标位置
void SetPos(short x, short y) { HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x,y }; SetConsoleCursorPosition(houtput, pos); }
打印欢迎界面和功能介绍
前面提到如何定位光标位置,在每一页打印完以后暂停,并且清理该页,才会接着打印下一页
void Welcome_To_Game() { SetPos(40, 14); printf("欢迎来到贪食蛇小游戏"); SetPos(40, 25); system("pause"); system("cls"); SetPos(25, 14); printf("用↑ . ↓ . ← . → 分别控制蛇的移动,F1为加速,"); SetPos(25, 15); printf("F2为减速,加速将得到更高的分数"); SetPos(40, 24); system("pause"); system("cls"); }
绘制地图
因为在后续功能中经常要用到各种图案,我们就统一在头文件里定义好
#define WALL L'□' #define BODY L'●' #define FOOD L'★'
void CreateMap() { int i = 0; //上 for (i = 0; i x = POS_X + i * 2; cur->y = POS_Y; //头插 if (ps->pSnakebody == NULL) { ps->pSnakebody = cur; } else { cur->next = ps->pSnakebody; ps->pSnakebody = cur; } } //打印身体 cur = ps->pSnakebody; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //初始化贪吃蛇的数据 ps->Cdt = OK; ps->Dir = RIGHT; ps->Food_Weight = 10; ps->Socre = 0; ps->Sleep_Time = 200;//单位毫秒 }
创建食物
棋盘大小:
要注意食物的范围
void CreateFood(pSnake ps) { int x = 0; int y = 0; //食物的坐标在棋盘内随机生成 again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0); //食物不能和蛇身冲突 pSnakeNode cur = ps->pSnakebody; while (cur) { if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } //创建食物 pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode)); if (Food == NULL) { perror("malloc fail"); return; } Food->next = NULL; Food->x = x; Food->y = y; SetPos(x, y); wprintf(L"%lc", FOOD); ps->pFood = Food; }
GameRun
打印提示信息
//打印提示信息 void PrintHelpInfo() { SetPos(60, 15); printf("不能穿墙,不能咬到自己"); SetPos(60, 16); printf("用 ↑.↓.←.→ 分别控制蛇的移动,"); SetPos(60, 17); printf("F1为加速,F2为减速,加速将得到更高的分数"); SetPos(60, 18); printf("ESC:退出游戏,SPACE:暂停游戏"); SetPos(60, 20); printf("我爱吃福鼎肉片@版权"); }
蛇每走一步
当蛇正在向某个方向走时,不能操控蛇走相反方向!
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//运行游戏 void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(60, 10); printf("食物分数:%2d", ps->Food_Weight); SetPos(60, 11); printf("总分数:%2d", ps->Socre); 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->Cdt = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { pause(); } else if (KEY_PRESS(VK_F1)) { if (ps->Sleep_Time > 80) { ps->Sleep_Time -= 30; ps->Food_Weight += 2; } } else if (KEY_PRESS(VK_F2)) { if (ps->Sleep_Time Sleep_Time += 30; ps->Food_Weight -= 2; } } //让蛇走起来 SnakeMove(ps); Sleep(ps->Sleep_Time); } while (ps->Cdt == OK);//状态为OK才能走 }
当蛇走向下一个节点时,要分为两种情况
- 下个节点是食物
- 下个节点不是食物
//蛇走的过程 void SnakeMove(pSnake ps) { //创建下一个节点 pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNextNode == NULL) { perror("pNextNode malloc fail"); return; } switch (ps->Dir) { case UP://向上 pNextNode->x = ps->pSnakebody->x; pNextNode->y = ps->pSnakebody->y - 1; break; case DOWN://向下 pNextNode->x = ps->pSnakebody->x; pNextNode->y = ps->pSnakebody->y + 1; break; case LEFT://向左 pNextNode->x = ps->pSnakebody->x - 2; pNextNode->y = ps->pSnakebody->y; break; case RIGHT://向右 pNextNode->x = ps->pSnakebody->x + 2; pNextNode->y = ps->pSnakebody->y; break; } //判断下一个节点是不是食物 if (NextIsFood(pNextNode, ps)) { EatFood(pNextNode, ps); } else { NotFood(pNextNode, ps); } KillByWall(ps);//下一步是墙 KillBySelf(ps);//下一步是自己 }
- 下个节点是食物
先判断一下
//判断下一个节点是不是食物 bool NextIsFood(pSnakeNode pn, pSnake ps) { return (pn->x == ps->pFood->x && pn->y == ps->pFood->y); }
//如果是食物,就吃掉,然后创建新的食物 void EatFood(pSnakeNode pn, pSnake ps) { //头插 ps->pFood->next = ps->pSnakebody; ps->pSnakebody = ps->pFood; //释放下一个位置的节点 free(pn); pn = NULL; //打印 pSnakeNode cur = ps->pSnakebody; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } ps->Socre += ps->Food_Weight; CreateFood(ps); }
为什么释放下一个位置的节点,有些同学有疑问
因为在CreateFood函数中,已经将ps->Food指向了食物节点,所以下一个位置的节点用不上的,当然要释放掉,因此,使用下一个位置的节点,释放食物节点也是可以的。
2. 下一个节点不是食物
//如果不是食物,就把最后一个节点释放 void NotFood(pSnakeNode pn, pSnake ps) { //头插 pn->next = ps->pSnakebody; ps->pSnakebody = pn; //最后一个节点,要释放 pSnakeNode cur = ps->pSnakebody; while (cur->next->next)//打印前n个 { 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->pSnakebody->x == 56 || ps->pSnakebody->x == 0 || ps->pSnakebody->y == 0 || ps->pSnakebody->y == 26) { ps->Cdt = KILL_BY_WALL; } } //判断下一步是否撞到自己 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->pSnakebody->next; while (cur) { if (cur->x == ps->pSnakebody->x && cur->y == ps->pSnakebody->y) { ps->Cdt = KILL_BY_SELF; } cur = cur->next; } }
GameEnd
//善后工作 void GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->Cdt) { case END_NORMAL: printf("您主动结束了游戏"); break; case KILL_BY_WALL: printf("您撞到墙死了"); break; case KILL_BY_SELF: printf("您咬到了自己"); } //释放蛇身节点 pSnakeNode cur = ps->pSnakebody; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } }
蛇死亡后继续游戏
到此还没有结束,如果玩家还想在玩一把呢,总不能关闭应用再重新点开吧
void test() { int ch = 0; do { system("cls"); Snake snake; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 14); printf("再来一局吗?(Y/N):"); ch = getchar(); while (getchar() != '\n');//如果输入yyyy一样可以读取 } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); }
希望这篇博客对你有所帮助!!!如果有不懂或者有错的地方欢迎私信探讨!
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。