目录
简述
我们要实现一个在控制台上的贪吃蛇,成品是下图这种。
先调整环境
我们可以看到我在控制台上有贪吃蛇名字,如何实现呢?
首先我们要将我们的vs终端改为Windows控制台主机。步骤如下
1)先鼠标右击上图终端的白色边框,当出现选项时。2)左击属性
3)点终端在有下面选择Windows控制台主机。最后如下图
后续就通过函数来调整环境,在后续一步一步讲解
本地化
C语言为适应国际化就提供了本地模式和标准模式
setlocale函数
用此函数修改模式
改为本地模式
setlocale(LC_ALL," ");
改为标准模式
setlocale(LC_ALL,"C");
宽字符的打印
使用wprintf函数,该函数打印的字符占两个空间,它和printf使用区别在于要在双引号前加上L。
//打印一个字符cwprintf(L"%c",L'c');
但在我写的代码中就算使用了wprintf还是只占一个空间,因为我的控制台环境没改。
将此处改为宋体就好。
创建蛇,用链表实现
当我们要实现这样一个贪吃蛇时,我们先要考虑用什么数据结构来实现,在这我们使用链表来搞。
可以看到我们蛇是一个一个节点连出来的。所以我们也将蛇的节点封装起来。
封装节点
那在节点中我们存储什么东西呢?在我们上述的终端中我们是可以用坐标把每一个地方表示出来的。所以我们就在节点中存储每一个节点的坐标和下一个节点的地址。因此代码实现就如下
//蛇的节点、坐标,指针typedefstructSnakeNode{int x;int y;//坐标信息structSnakeNode* next;//下一个节点}SnakeNode;typedef SnakeNode* pSnakeNode;
封装蛇
那我们要实现一个蛇,我们就要考虑里面要包含哪些成员。首先我们肯定要有1.找到蛇身的蛇头指针,2.一个蛇运动的方向也需要,3.蛇每移动一下的状态也会有不同,4.从最后的原型图可以看出我们还有总分,5.一个食物的分,7,食物的分又是根据速度来的,速度我们就用当前代码休眠时间来表示,休眠时间约大速度越慢,8.食物我们也储存在里面。
蛇的方向和蛇的状态有很多种,我们就用enum枚举出来。
//方向上下左右enumDIRECTION{
UP =1,
DOWN,
LEFT,
RIGHT
};//蛇的状态,正常,撞墙死,撞自己死,正常退出enumGAME_STATUS{
NORMAL,
KILL_BY_WALL,
KILL_BY_SELF,
EXIT_NORMAL
};//贪吃蛇对象typedefstructSnake{
pSnakeNode psnake;//蛇头
pSnakeNode pfood;//食物enumDIRECTION direction;//蛇的方向enumGAME_STATUS status;//蛇的状态int food_wight;//一个食物分数int sorce;//总分数int sleep_time;//休息时间}Snake;typedef Snake* pSnake;
游戏初始化
初始化游戏函数
//初始化游戏voidGameStart(pSnake ps);
在这个函数中我们需要将1.控制台界面大小安排好,2.光标隐藏起来,3. 打印环境界面和功能介绍,4. 绘制地图,5. 创建蛇,6. 创建食物。
对界面的操作详见我上一篇关于Win32API的文章
Win32API的文章
具体实现如下:
voidGameStart(pSnake ps){//1.修改界面大小system("mode con cols=150 lines=30");system("title 贪吃蛇");//2.隐藏光标
HANDLE hOutPut =GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo ;GetConsoleCursorInfo(hOutPut,&CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;SetConsoleCursorInfo(hOutPut,&CursorInfo);//隐藏控制台光标信息//3. 打印环境界面和功能介绍WelcomeToGame();//4. 绘制地图CreateMap();//5. 创建蛇IniteSnake(ps);//6. 创建食物IniteFood(ps);}
光标设置函数
我们封装一个函数来设置光标坐标,以便后续使用。
voidSetPos(short x,short y){//获取一个句柄
HANDLE hOutPut =GetStdHandle(STD_OUTPUT_HANDLE);//坐标
COORD pos ={ x,y };//设置光标坐标SetConsoleCursorPosition(hOutPut, pos);}
打印环境界面和功能介绍函数
我们在这个函数中打印欢迎界面和游戏玩法。
voidWelcomeToGame(){SetPos(40,14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42,20);system("pause");system("cls");SetPos(25,14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(25,15);wprintf(L"加速能够得到更高的分数\n");SetPos(42,20);system("pause");system("cls");}
绘制地图函数
我们绘制一个80行27列的地图。我们先将上下两行边界绘制出来,在绘制左右两个边界,注意左右时我们要从1到25最前面的已经绘制好了。还有每次绘制的其实光标位置一定要计算好根据自己设置的地图大小。
voidCreateMap(){SetPos(0,0);int i =0;for(i =0; i <80; i++){wprintf(L"%lc", WALL);}//下SetPos(0,26);for(i =0; i <80; i++){wprintf(L"%lc", WALL);}for( i =1; i <=25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for( i =1; i <=25; i++){SetPos(79, i);wprintf(L"%lc", WALL);}system("pause");}
创建蛇
在这个函数中我们初始蛇有5个节点,用malloc申请空间,然后用头插法将节点联系起来,然后打印。在将蛇的状态等成员设置。在创建蛇时因为我是字符只占一个空间,我就随便可以创建,但是有占两个时要在设置x坐标是下一个是加2。
voidIniteSnake(pSnake ps){
pSnakeNode node =NULL;//初始蛇身有5个节点for(int i =0; i <5; i++){
node =(pSnakeNode)malloc(sizeof(SnakeNode));if(node ==NULL){perror("IniteSnake malloc");exit(1);}//设置坐标
node->x = POS_X + i;
node->y = POS_Y;
node->next =NULL;//头插if(ps->psnake ==NULL)
ps->psnake = node;else{
node->next = ps->psnake;
ps->psnake = node;}}//打印蛇身
node = ps->psnake;while(node){SetPos(node->x, node->y);wprintf(L"%c",BODY);
node = node->next;}//设置蛇状态
ps->direction = RIGHT;
ps->sleep_time =300;
ps->status = NORMAL;
ps->sorce =0;
ps->food_wight =30;}
创建食物
我们创建一个食物节点,通过随机数生成坐标,但是要该坐标不能是墙,不能是蛇身。最后打印出来。在创建食物时因为我是字符只占一个空间,我就不用考虑蛇是否可以吃到食物,但是有占两个时要在设置食物x坐标要和蛇节点吻合,可以让蛇蛇吃到。
voidIniteFood(pSnake ps){
pSnakeNode food =(pSnakeNode)malloc(sizeof(SnakeNode));if(food ==NULL){perror("IniteFood malloc");exit(1);}//随机生成食物坐标,判断与蛇是否重合int x =0;int y =0;
again:
x =rand()%78+1;
y =rand()%23+1;
pSnakeNode cur = ps->psnake;while(cur){if(x == cur->x && y == cur->y){goto again;}
cur = cur->next;}
food->x = x;
food->y = y;
food->next =NULL;
ps->pfood = food;//打印食物SetPos(food->x, food->y);wprintf(L"%c", FOOD);}
游戏运行
voidGameRun(pSnake ps){do{SetPos(90,10);//右侧打印帮助信息PrintInfo(ps);//获取键盘信息//移动方向if(KEY_PRESS(VK_UP)&& ps->direction != DOWN)
ps->direction = UP;elseif(KEY_PRESS(VK_DOWN)&& ps->direction != UP)
ps->direction = DOWN;elseif(KEY_PRESS(VK_RIGHT)&& ps->direction != LEFT)
ps->direction = RIGHT;elseif(KEY_PRESS(VK_LEFT)&& ps->direction != RIGHT)
ps->direction = LEFT;//加减速elseif(KEY_PRESS(VK_F3)){if(ps->food_wight <=50&& ps->food_wight >=10){
ps->sleep_time -=10;
ps->food_wight +=2;}}elseif(KEY_PRESS(VK_F4)){if(ps->food_wight <=50&& ps->food_wight >=10){
ps->sleep_time +=10;
ps->food_wight -=2;}}//暂停elseif(KEY_PRESS(VK_SPACE))pause();//退出elseif(KEY_PRESS(VK_ESCAPE)){
ps->status = EXIT_NORMAL;break;}//判断分数超出限制调回if(ps->food_wight >50){
ps->food_wight =50;}elseif(ps->food_wight <10){
ps->food_wight =10;}//蛇移动Sleep(ps->sleep_time);SnakeMove(ps);}while(ps->status == NORMAL);}
打印帮助信息函数
我们在游戏界面右边打印帮助信息。打印完帮助信息后我们在通过获取键盘信息来判断下一步应该怎么做。
voidPrintInfo(pSnake ps){printf("当前总分:%d", ps->sorce);SetPos(90,11);printf("当前一个食物分数: %2d", ps->food_wight);SetPos(90,12);printf("不能穿墙,不能咬到自己");SetPos(90,13);printf("空格键暂停,esc键退出");SetPos(90,14);printf("用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速");SetPos(90,15);printf("加速减速最多10次,加速一次食物分数加2,减速一次食物分数减2");}
暂停函数
当我们接收键盘信息为空格暂停时,我们调用该函数,该函数本质就是死循环睡眠。再次接收到空格时我们就恢复。
voidpause(){while(1){Sleep(300);if(KEY_PRESS(VK_SPACE))break;}}
蛇移动函数
我们根据蛇移动方向来判断下一步在哪,其实就是将下一个位置处创建结点当头结点。但是我们需要判断移动后头结点位置是食物,还是墙,还是正常位置。然后看是否需要释放最后一个节点。
voidSnakeMove(pSnake ps){//创建一个节点
pSnakeNode node =(pSnakeNode)malloc(sizeof(SnakeNode));if(node ==NULL){perror("SnakeMove malloc");exit(1);}
node->next =NULL;//通过switch case来将下一个节点坐标赋值switch(ps->direction){case UP:
node->x = ps->psnake->x;
node->y = ps->psnake->y -1;break;case DOWN:
node->x = ps->psnake->x;
node->y = ps->psnake->y +1;break;case RIGHT:
node->x = ps->psnake->x +1;
node->y = ps->psnake->y;break;case LEFT:
node->x = ps->psnake->x -1;
node->y = ps->psnake->y;break;}
pSnakeNode tempNode = node;//下一个节点是食物if(node->x == ps->pfood->x && node->y == ps->pfood->y){EatFood(ps, node);}//不是食物else{NoFood(ps,node);}//判断移动后是否死来if(NextIsSelf(ps,tempNode)==1){
ps->status = KILL_BY_SELF;}elseif(tempNode->x ==0|| tempNode->x ==79|| tempNode->y ==0|| tempNode->y ==27){
ps->status = KILL_BY_WALL;}}
下一个是食物
当下个是食物我们就不用对最后一个节点释放。吃掉之后释放掉在创建下一个食物。
voidEatFood(pSnake ps,pSnakeNode node){//头插
node->next = ps->psnake;
ps->psnake = node;//打印蛇
pSnakeNode cur = ps->psnake;
ps->sorce += ps->food_wight;while(cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);
cur = cur->next;}//释放食物free(ps->pfood);IniteFood(ps);}
下一个不是食物
下一个不是食物蛇往下一步走后需要释放掉最后一个节点。
voidNoFood(pSnake ps, pSnakeNode node){//头插
node->next = ps->psnake;
ps->psnake = node;//打印蛇
pSnakeNode cur = ps->psnake;while(cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);
cur = cur->next;}
cur = ps->psnake;while(cur->next->next){
cur = cur->next;}
pSnakeNode temp = cur->next;SetPos(temp->x, temp->y);printf(" ");
cur->next =NULL;free(temp);}
游戏结束
在游戏结束后通过蛇状态来打印死因,然后释放掉所有空间。
voidGameOver(pSnake ps){SetPos(40,15);if(ps->status == KILL_BY_SELF)printf("你撞到自己死了");elseif(ps->status == KILL_BY_WALL)printf("你撞到墙死了");elseif(ps->status == EXIT_NORMAL)printf("你正常退出了");
pSnakeNode cur = ps->psnake;while(cur){
ps->psnake = cur->next;free(cur);
cur = ps->psnake;}free(ps->pfood);}
源码
snake.h
#pragmaonce#include<windows.h>#include<stdbool.h>#include<stdlib.h>#include<stdio.h>#include<locale.h>#include<time.h>#defineWALLL'█'#definePOS_X25#definePOS_Y5#defineBODYL'○'#defineFOODL'♥'#defineKEY_PRESS(VK)((GetAsyncKeyState(VK)&1)?1:0)//方向上下左右enumDIRECTION{
UP =1,
DOWN,
LEFT,
RIGHT
};//蛇的状态,正常,撞墙死,撞自己死,正常退出enumGAME_STATUS{
NORMAL,
KILL_BY_WALL,
KILL_BY_SELF,
EXIT_NORMAL
};//蛇的节点、坐标,指针typedefstructSnakeNode{int x;int y;//坐标信息structSnakeNode* next;//下一个节点}SnakeNode;typedef SnakeNode* pSnakeNode;//贪吃蛇对象typedefstructSnake{
pSnakeNode psnake;//蛇头
pSnakeNode pfood;//食物enumDIRECTION direction;//蛇的方向enumGAME_STATUS status;//蛇的状态int food_wight;//一个食物分数int sorce;//总分数int sleep_time;//休息时间}Snake;typedef Snake* pSnake;//设置光标坐标voidSetPos(short x,short y);//初始化游戏voidGameStart(pSnake ps);//打印欢迎界面voidWelcomeToGame();//绘制地图x 80, y 27voidCreateMap();//5. 创建蛇voidIniteSnake(pSnake ps);//6. 创建食物voidIniteFood(pSnake ps);//游戏进行页面voidGameRun(pSnake ps);//不是食物voidNoFood(pSnake ps, pSnakeNode node);//吃食物voidEatFood(pSnake ps, pSnakeNode node);//蛇移动voidSnakeMove(pSnake ps);//游戏结束voidGameOver(pSnake ps);
snake.c
#define_CRT_SECURE_NO_WARNINGS1;#include"snake.h"//设置光标坐标voidSetPos(short x,short y){//获取一个句柄
HANDLE hOutPut =GetStdHandle(STD_OUTPUT_HANDLE);//坐标
COORD pos ={ x,y };//设置光标坐标SetConsoleCursorPosition(hOutPut, pos);}//3. 打印环境界面和功能介绍voidWelcomeToGame(){SetPos(40,14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42,20);system("pause");system("cls");SetPos(25,14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(25,15);wprintf(L"加速能够得到更高的分数\n");SetPos(42,20);system("pause");system("cls");}//4. 绘制地图voidCreateMap(){SetPos(0,0);int i =0;for(i =0; i <80; i++){wprintf(L"%lc", WALL);}//下SetPos(0,26);for(i =0; i <80; i++){wprintf(L"%lc", WALL);}for( i =1; i <=25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for( i =1; i <=25; i++){SetPos(79, i);wprintf(L"%lc", WALL);}system("pause");}//5. 创建蛇voidIniteSnake(pSnake ps){
pSnakeNode node =NULL;//初始蛇身有5个节点for(int i =0; i <5; i++){
node =(pSnakeNode)malloc(sizeof(SnakeNode));if(node ==NULL){perror("IniteSnake malloc");exit(1);}//设置坐标
node->x = POS_X + i;
node->y = POS_Y;
node->next =NULL;//头插if(ps->psnake ==NULL)
ps->psnake = node;else{
node->next = ps->psnake;
ps->psnake = node;}}//打印蛇身
node = ps->psnake;while(node){SetPos(node->x, node->y);wprintf(L"%c",BODY);
node = node->next;}//设置蛇状态
ps->direction = RIGHT;
ps->sleep_time =300;
ps->status = NORMAL;
ps->sorce =0;
ps->food_wight =30;}//6. 创建食物voidIniteFood(pSnake ps){
pSnakeNode food =(pSnakeNode)malloc(sizeof(SnakeNode));if(food ==NULL){perror("IniteFood malloc");exit(1);}//随机生成食物坐标,判断与蛇是否重合int x =0;int y =0;
again:
x =rand()%78+1;
y =rand()%23+1;
pSnakeNode cur = ps->psnake;while(cur){if(x == cur->x && y == cur->y){goto again;}
cur = cur->next;}
food->x = x;
food->y = y;
food->next =NULL;
ps->pfood = food;//打印食物SetPos(food->x, food->y);wprintf(L"%c", FOOD);}//初始化游戏voidGameStart(pSnake ps){//1.修改界面大小system("mode con cols=150 lines=30");system("title 贪吃蛇");//2.隐藏光标
HANDLE hOutPut =GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo ;GetConsoleCursorInfo(hOutPut,&CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;SetConsoleCursorInfo(hOutPut,&CursorInfo);//隐藏控制台光标信息//3. 打印环境界面和功能介绍WelcomeToGame();//4. 绘制地图CreateMap();//5. 创建蛇IniteSnake(ps);//6. 创建食物IniteFood(ps);}//打印帮助信息voidPrintInfo(pSnake ps){printf("当前总分:%d", ps->sorce);SetPos(90,11);printf("当前一个食物分数: %2d", ps->food_wight);SetPos(90,12);printf("不能穿墙,不能咬到自己");SetPos(90,13);printf("空格键暂停,esc键退出");SetPos(90,14);printf("用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速");SetPos(90,15);printf("加速减速最多10次,加速一次食物分数加2,减速一次食物分数减2");}//暂停voidpause(){while(1){Sleep(300);if(KEY_PRESS(VK_SPACE))break;}}//判断下一个是不是自己intNextIsSelf(pSnake ps, pSnakeNode node){
pSnakeNode cur = ps->psnake->next;while(cur){if(cur->x == node->x && cur->y == node->y)return1;
cur = cur->next;}return0;}//吃食物voidEatFood(pSnake ps,pSnakeNode node){//头插
node->next = ps->psnake;
ps->psnake = node;//打印蛇
pSnakeNode cur = ps->psnake;
ps->sorce += ps->food_wight;while(cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);
cur = cur->next;}//释放食物free(ps->pfood);IniteFood(ps);}//不是食物voidNoFood(pSnake ps, pSnakeNode node){//头插
node->next = ps->psnake;
ps->psnake = node;//打印蛇
pSnakeNode cur = ps->psnake;while(cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);
cur = cur->next;}
cur = ps->psnake;while(cur->next->next){
cur = cur->next;}
pSnakeNode temp = cur->next;SetPos(temp->x, temp->y);printf(" ");
cur->next =NULL;free(temp);}//蛇移动voidSnakeMove(pSnake ps){//创建一个节点
pSnakeNode node =(pSnakeNode)malloc(sizeof(SnakeNode));if(node ==NULL){perror("SnakeMove malloc");exit(1);}
node->next =NULL;//通过switch case来将下一个节点坐标赋值switch(ps->direction){case UP:
node->x = ps->psnake->x;
node->y = ps->psnake->y -1;break;case DOWN:
node->x = ps->psnake->x;
node->y = ps->psnake->y +1;break;case RIGHT:
node->x = ps->psnake->x +1;
node->y = ps->psnake->y;break;case LEFT:
node->x = ps->psnake->x -1;
node->y = ps->psnake->y;break;}
pSnakeNode tempNode = node;//下一个节点是食物if(node->x == ps->pfood->x && node->y == ps->pfood->y){EatFood(ps, node);}//不是食物else{NoFood(ps,node);}//判断移动后是否死来if(NextIsSelf(ps,tempNode)==1){
ps->status = KILL_BY_SELF;}elseif(tempNode->x ==0|| tempNode->x ==79|| tempNode->y ==0|| tempNode->y ==27){
ps->status = KILL_BY_WALL;}}//游戏进行页面voidGameRun(pSnake ps){do{SetPos(90,10);//右侧打印帮助信息PrintInfo(ps);//获取键盘信息//移动方向if(KEY_PRESS(VK_UP)&& ps->direction != DOWN)
ps->direction = UP;elseif(KEY_PRESS(VK_DOWN)&& ps->direction != UP)
ps->direction = DOWN;elseif(KEY_PRESS(VK_RIGHT)&& ps->direction != LEFT)
ps->direction = RIGHT;elseif(KEY_PRESS(VK_LEFT)&& ps->direction != RIGHT)
ps->direction = LEFT;//加减速elseif(KEY_PRESS(VK_F3)){if(ps->food_wight <=50&& ps->food_wight >=10){
ps->sleep_time -=10;
ps->food_wight +=2;}}elseif(KEY_PRESS(VK_F4)){if(ps->food_wight <=50&& ps->food_wight >=10){
ps->sleep_time +=10;
ps->food_wight -=2;}}//暂停elseif(KEY_PRESS(VK_SPACE))pause();//退出elseif(KEY_PRESS(VK_ESCAPE)){
ps->status = EXIT_NORMAL;break;}//判断分数超出限制调回if(ps->food_wight >50){
ps->food_wight =50;}elseif(ps->food_wight <10){
ps->food_wight =10;}//蛇移动Sleep(ps->sleep_time);SnakeMove(ps);}while(ps->status == NORMAL);}//游戏结束voidGameOver(pSnake ps){SetPos(40,15);if(ps->status == KILL_BY_SELF)printf("你撞到自己死了");elseif(ps->status == KILL_BY_WALL)printf("你撞到墙死了");elseif(ps->status == EXIT_NORMAL)printf("你正常退出了");
pSnakeNode cur = ps->psnake;while(cur){
ps->psnake = cur->next;free(cur);
cur = ps->psnake;}free(ps->pfood);}
test.c
#define_CRT_SECURE_NO_WARNINGS1;#include"snake.h"voidtest(){int n =4;srand((unsignedint)time(NULL));do{//创建贪吃蛇
Snake snake ={0};//初始化游戏GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏-善后工作GameOver(&snake);SetPos(10,10);//system("cls");printf("是否再来一局Y/N: ");
n =getchar();while(getchar()!='\n');system("cls");}while(n =='Y'|| n =='y');SetPos(0,29);}intmain(){//调节为本地状态setlocale(LC_ALL,"");test();return0;}
版权归原作者 鸽鸽程序猿 所有, 如有侵权,请联系我们删除。