赞
踩
引言:本篇博客中,我将会使用结构体,链表,WIN32 API等一系列知识完成C语言项目——贪吃蛇的实现。在观看此篇博客之前,请将这些知识所熟悉,不然可能会造成理解困难。
更多有关C语言的知识详解可前往个人主页:计信猫
目录
在贪吃蛇项目中,我们会创建三个文件分别为:test.c,snake.c,snake.h。它们的作用分别为游戏函数的测试,游戏函数的实现,游戏函数的声明和结构体变量的定义。
在snake.h中,我们将会定义与贪吃蛇有关的结构体。
1,贪吃蛇身体的节点:
- //定义蛇身体的节点
- typedef struct snakenode
- {
- //坐标
- int x;
- int y;
- struct snakenode* next;//下一个节点
- }snakenode,*psnakenode;
2,蛇的方向:
- //定义方向
- enum direction
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
3,蛇的状态:
- //蛇的状态
- enum game_state
- {
- OK,//正常状态
- KILL_BY_WALL,//撞到墙
- KILL_BY_SELF,//撞到了自己
- END_NORMAL//正常结束游戏
- };
4,贪吃蛇游戏:
- //贪吃蛇
- typedef struct snake
- {
- psnakenode _psnake;//蛇头节点
- psnakenode _pfood;//食物节点
- enum direction _dir;//蛇的方向
- enum game_state _sta;//游戏状态
- int _food_weight;//一个食物的分数
- int _score;//总成绩
- int _sleep_time;//蛇的速度
- }snake,* psnake;
在snake.c中,我们定义一个GameStart函数来包含开始游戏之前的窗口改变,光标隐藏,欢迎界面与地图打印函数。
在前文WIN32 API中我们已经对窗口信息修改和隐藏光标的函数进行过解释,所以我们这里直接使用:
- void GameStart(psnake ps)
- {
- //设置窗口大小,改变窗口名字
- system("mode con cols=100 lines=30");
- system("title 贪吃蛇");
- //隐藏光标
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取光标句柄
- CONSOLE_CURSOR_INFO cursorinfo;
- GetConsoleCursorInfo(houtput, &cursorinfo);//获取光标信息
- cursorinfo.bVisible = false;//隐藏鼠标
- SetConsoleCursorInfo(houtput, &cursorinfo);//设置光标状态
- }
在欢迎界面与游戏界面中,我们需要将游戏开始信息与游戏信息等打印在指定的位置,这时候我们就需要有特定的函数来确定光标的位置,以此来完成指定位置信息的打印。所以我们如下设计确定光标位置setpos()函数。
- void setpos(int x,int y)
- {
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄
- COORD pos = { x,y };//设定坐标
- SetConsoleCursorPosition(houtput, pos);//设定光标的坐标
- }
如图中的界面就是我们想要达到的欢迎界面效果,所以我们在GameStart()函数中设计一个新函数名为welcometogame()函数,函数内容如下:
- void welcometogame()
- {
- 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");
- }
首先我们使用setpos函数来确定光标的位置,再使用wprintf函数来打印内容,system(“pause”)语句则代表着暂停程序,按任意键表示继续程序,而system(“cls”)语句则代表着将当前控制台的页面全部清空,这样我们就打印出了我们的欢迎界面。
在贪吃蛇游戏中,我们将要创建如图所示的游戏地图:
正如我们前一篇博客所讲到的,我们的控制台界面的各个地点都有着对应的坐标,如下:
而我们所想要设计的地图的大小,就可以通过坐标的范围来圈定。所以我们的地图大小应该如下图坐标所示:
有了坐标,我们就知道了地图的上边需要29个宽字符‘▢’,下边也是一样,而左右两边我们则可以使用setpos函数来移动光标进行左右两边边界的打印。所以我们在GameStart()函数中创建一个creatmap()函数,代码如下:
- void creatmap()
- {
- int i = 0;
- //上
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", L'□');
- }
- //下
- setpos(0, 26);//移动光标以便打印下标
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", L'□');
- }
- //左
- for (i = 1; i <= 25; i++)
- {
- setpos(0, i);//移动光标
- wprintf(L"%lc", L'□');
- }
- //右
- for (i = 1; i <= 25; i++)
- {
- setpos(56, i);//移动光标
- wprintf(L"%lc", L'□');
- }
- system("pause");//按任意键继续
- }
代码一走,则地图创建成功!
我们将蛇想象成我们以前学到的链表,刚开始的蛇身长度为5个节点,所以我们便可以使用头插的办法来完成蛇身的初始化。
但是,在初始化蛇身的时候,我们一定需要注意蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中, 另外⼀半在墙外的现象,坐标不好对齐。如下图所示:
而对于蛇的身体,我们则使用宽字符‘●‘进行表示。为了方便以后更改蛇身体的坐标,墙和蛇的图标,于是我们选择在snake.h中使用define定义:
- #define POS_X 24
- #define POS_Y 5
- #define WALL L'□'
- #define BODY L'●'
最后,我们还不要忘记对蛇的结构体中的其他成员变量的初始化。所以在GameStart()函数中,我们继续定义一个InitSnake()函数来进行对蛇进行初始化。
- void InitSnake(psnake ps)
- {
- int i = 0;
- psnakenode cur = NULL;
- for (i = 0; i < 5; i++)
- {
- cur = (psnakenode)malloc(sizeof(snakenode));//创建节点
- if (cur == NULL)//判断是否申请成功
- {
- perror("InitSnake()::malloc()");
- return;
- }
- cur->next = NULL;
- //设置节点的坐标
- cur->x = POS_X + 2 * i;
- cur->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->_dir = RIGHT;
- ps->_food_weight = 10;
- ps->_score = 0;
- ps->_sleep_time = 200;//单位是毫秒
- ps->_sta = OK;
- getchar();//暂停程序观察效果,不需要观察效果后可将该语句删除
- }
当蛇的初始化完成后,我们就需要完成食物的初始化。但在初始化食物的时候,我们需要注意到以下三点:
1,食物必须出现在墙体之内
2,食物的x坐标一定为2的倍数
3,食物坐标不可以与蛇身的坐标重复
在该函数中,我们也给食物下定义:
#define FOOD L'※'
为了达到坐标随机生成的目的,我们将在主函数中使用srand((unsigned int)time);(需要包含<time.h>)。所以我们的创建食物函数CreatFood()函数的实现如下:
- void CreatFood(psnake ps)
- {
- int x = 0;
- int y = 0;
- again:
- do
- {
- x = rand() % 53 + 2;//使横坐标在2~54之内
- y = rand() % 24 + 1;//使纵坐标在1~25之内
- } while (x % 2 != 0);//若生成的x为奇数,那么就重新生成
- psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合
- while (cur)
- {
- if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合
- {
- goto again;//重新生成
- }
- cur = cur->next;
- }
- //打印食物标号
- psnakenode food = (psnakenode)malloc(sizeof(snakenode));
- if (food == NULL)//防止申请内存失败
- {
- perror("CreatFood()::malloc()");
- return;
- }
- food->x = x;
- food->y = y;
- food->next = NULL;
- setpos(food->x, food->y);
- wprintf(L"%lc", FOOD);
- ps->_pfood = food;
- getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
- }
代码一走,食物生成成功!
在正式开始游戏时,我们需要在游戏地图之外打印一系列信息,以帮助用户了解游戏的玩法。所以我们在GameRun()函数中封装一个PrintHelpInfo()函数来进行游戏帮助信息的打印。
- //打印游戏帮助信息
- void PrintHelpInfo()
- {
- //打印提⽰信息
- setpos(64, 15);
- printf("不能穿墙,不能咬到自己\n");
- setpos(64, 16);
- printf("⽤↑.↓.←.→分别控制蛇的移动\n");
- setpos(64, 17);
- printf("F3 为加速,F4 为减速\n");
- setpos(64, 18);
- printf("ESC :退出游戏.space:暂停游戏.");
- }
代码一走,效果如图:
首先,我们实现一个KEY_PRESS宏来判断某个按键是否被按下:
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
所以,我们便可以在GameStart()函数中使用do-while循环来打印游戏的得分与了解按键按下的情况,以便控制蛇的移动方向。
- //游戏的运行
- void GameRun(psnake ps)
- {
- //打印游戏帮助信息
- PrintHelpInfo();
- do
- {
- //打印游戏得分信息
- setpos(64, 10);
- printf("总得分:%d\n", ps->_score);
- setpos(64, 11);
- printf("当前一个食物的分数:%2d\n", ps->_food_weight);
- //判断按键以控制蛇的移动
- if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下
- {
- 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();//Pause函数执行暂停操作
- }
- else if (KEY_PRESS(VK_ESCAPE))
- {
- //正常退出游戏
- ps->_sta = END_NORMAL;
- }
- else if (KEY_PRESS(VK_F3))
- {
- //加速
- if (ps->_sleep_time > 80)
- {
- ps->_sleep_time -= 30;
- ps->_food_weight += 2;
- }
- }
- else if (KEY_PRESS(VK_F4))
- {
- //减速
- if (ps->_sleep_time < 300)
- {
- ps->_sleep_time += 30;
- ps->_food_weight -= 2;
- }
- }
- SnakeMove(ps);//蛇走一步
- Sleep(ps->_sleep_time);
- } while (ps->_sta == OK);
- }
其中Pause()函数如下:
- //游戏暂停操作
- void Pause()
- {
- while (1)
- {
- Sleep(200);//死循环休眠则视为暂停
- if (KEY_PRESS(VK_SPACE))
- {
- break;//再次按下空格则跳出循环
- }
- }
- }
当我们在前面获取了按键按下的情况,即蛇的状态信息之后,我们就可以使用SnakeMove()函数来对蛇的移动进行控制。
在该函数中,我们就可以再创建一个节点(pNextNode)表示蛇头即将到达的节点位置,而该新节点的位置与当前蛇头的坐标x,y有关:
于是新节点的位置设置如下:
- //蛇走一步
- void SnakeMove(psnake ps)
- {//先创建一个新节点,表示蛇头即将到达的位置
- psnakenode pNextNode = (psnakenode)malloc(sizeof(snakenode));
- if (pNextNode == NULL)
- {
- perror("SnakeMove()::malloc()");
- return;
- }
- 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;
- }
- }
当我们的蛇头移动到下一个坐标的时候,就会产生两种情况:1,下一个坐标为食物。2,下一个坐标不为食物。两种情况应该不同方式来处理,所以我们在SnakeMove()函数中的switch循环之后再定义两个函数来处理不同情况:
- //判断下一个坐标是否为食物
- int NextIsFood(psnakenode pNextNode, psnake ps)
- {
- return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
- }
- //检测下一个坐标是否为食物
- if (NextIsFood(pNextNode, ps))
- {
- EatFood(pNextNode, ps);
- }
- else
- {
- NoFood(pNextNode, ps);
- }
该函数用于处理下一个坐标为食物的情况。该情况下,我们就可以直接将食物节点变为蛇的头节点,然后打印蛇,加分,最后再次创建食物:
- //下一个坐标为食物
- void EatFood(psnakenode pNextNode, psnake ps)
- {
- //使用头插,将食物节点变为蛇头
- ps->_pfood->next = ps->_psnake;
- ps->_psnake = ps->_pfood;
- //释放掉pNextNode
- free(pNextNode);
- pNextNode = NULL;
- //打印出新蛇
- psnakenode cur = ps->_psnake;
- while (cur)
- {
- setpos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- //加分
- ps->_score += ps->_food_weight;
- //再次创建食物
- CreatFood(ps);
- }
该函数用于处理下一个坐标不为食物的情况。该情况下,我们则需要做到的任务如下:
- 1,使用头插法将pNextNode变为新的蛇头
- 2,把最后一个坐标打印为两个空格
- 3,释放掉尾节点
- 4,将倒数第二个节点的next置空
所以我们画图解释:
于是我们使用cur遍历数组,当cur->next->next为NULL时,那么就证明cur来到了倒数第二个节点,此时我们就跳出while循环,之后将cur->next指向的节点处打印两个空格(以覆盖先前打印而留下的蛇身),再将尾节点释放掉,最后将cur->next置空。
- //下一个坐标不是食物
- void NoFood(psnakenode pNextNode, psnake 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;
- }
- //出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格
- setpos(cur->next->x, cur->next->y);
- printf(" ");
- //释放掉尾节点
- free(cur->next);
- //将倒数第二个节点的next置空
- cur->next = NULL;
- }
这样,一个贪吃蛇游戏就初步完成了。
在GameRUN()函数中,我们也需要检测蛇是否在行动的过程中撞到了墙或者撞到了自己。于是我们可以在设计两个函数KillByWall()函数和KillBySelf()函数来进行判断。
关于蛇是否撞墙的判断十分简单。我们只需要判断蛇头的坐标是否与墙的x或者y坐标重合即可。若重合,则撞墙;反之,则未撞墙。
- //检测是否撞墙
- void KillByWall(psnake ps)
- {
- if (ps->_psnake->x == 0 || ps->_psnake->x == 56 ||
- ps->_psnake->y == 0 || ps->_psnake->y == 26)
- {
- ps->_sta = KILL_BY_WALL;
- }
- }
在该函数中,我们只需要从蛇头之后的一个节点遍历蛇身,假如某段蛇身的坐标等于蛇头的坐标,那么就是撞到了自己;反之,则没有撞到自己。
- //检测是否撞到自己
- void KillBySelf(psnake ps)
- {
- psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历
- while (cur)
- {
- if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y)
- {
- ps->_sta = KILL_BY_SELF;
- break;//撞到了自己,退出循环
- }
- cur = cur->next;
- }
- }
有了前文所获取的蛇的结束状态,那我们就可以在GameEnd()函数中打印对应的结束语。
- void GameEnd(psnake ps)
- {
- setpos(24, 12);//设置光标位置,打印结束信息
- switch (ps->_sta)
- {
- case END_NORMAL:
- printf("游戏正常结束\n");
- break;
- case KILL_BY_WALL:
- printf("您撞到了墙,游戏结束\n");
- break;
- case KILL_BY_SELF:
- printf("您撞到了自己,游戏结束\n");
- break;
- }
- }
当然,当我们的游戏结束后,我们使用的蛇身节点也需要被释放,而释放方法非常简单,就是我们之前在链表操作函数中学到的销毁链表操作。
- //游戏结束
- void GameEnd(psnake ps)
- {
- //释放蛇身
- psnakenode cur = ps->_psnake;
- while (cur)
- {
- psnakenode del = cur;
- cur = cur->next;
- free(del);
- del = NULL;
- }
- }
如此,我们的贪吃蛇的主体函数就完成了!!
在这里,我们将实现游戏的多次使用和正常退出,以及项目结束的信息的位置的设置。这一切的操作,都在test.c中进行。
- #include"snake.h"
- void test()
- {
- int ch = 0;
- do
- {
- snake ps = { 0 };
- GameStart(&ps);
- GameRun(&ps);
- GameEnd(&ps);
- setpos(20, 15);
- printf("再来一局吗?(Y/N):");
- ch = getchar();
- getchar();//清理\n
- } while (ch == 'Y' || ch == 'y');
- setpos(0, 27);
- }
- int main()
- {
- srand((unsigned int)time);
- setlocale(LC_ALL, "");//设置本地化以打印宽字符
- test();
- return 0;
- }
- #include"snake.h"
- void test()
- {
- int ch = 0;
- do
- {
- snake ps = { 0 };
- GameStart(&ps);
- GameRun(&ps);
- GameEnd(&ps);
- setpos(20, 15);
- printf("再来一局吗?(Y/N):");
- ch = getchar();
- getchar();//清理\n
- } while (ch == 'Y' || ch == 'y');
- setpos(0, 27);
- }
- int main()
- {
- srand((unsigned int)time);
- setlocale(LC_ALL, "");//设置本地化以打印宽字符
- test();
- return 0;
- }
- #include"snake.h"
- void setpos(int x,int y)
- {
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄
- COORD pos = { x,y };//设定坐标
- SetConsoleCursorPosition(houtput, pos);//设定光标的坐标
- }
- void welcometogame()
- {
- 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");
- }
- void creatmap()
- {
- int i = 0;
- //上
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", L'□');
- }
- //下
- setpos(0, 26);//移动光标以便打印下标
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", L'□');
- }
- //左
- for (i = 1; i <= 25; i++)
- {
- setpos(0, i);//移动光标
- wprintf(L"%lc", L'□');
- }
- //右
- for (i = 1; i <= 25; i++)
- {
- setpos(56, i);//移动光标
- wprintf(L"%lc", L'□');
- }
- }
- void InitSnake(psnake ps)
- {
- int i = 0;
- psnakenode cur = NULL;
- for (i = 0; i < 5; i++)
- {
- cur = (psnakenode)malloc(sizeof(snakenode));//创建节点
- if (cur == NULL)//判断是否申请成功
- {
- perror("InitSnake()::malloc()");
- return;
- }
- cur->next = NULL;
- //设置节点的坐标
- cur->x = POS_X + 2 * i;
- cur->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->_dir = RIGHT;
- ps->_food_weight = 10;
- ps->_score = 0;
- ps->_sleep_time = 200;//单位是毫秒
- ps->_sta = OK;
- }
- void CreatFood(psnake ps)
- {
- int x = 0;
- int y = 0;
- again:
- do
- {
- x = rand() % 53 + 2;//使横坐标在2~54之内
- y = rand() % 24 + 1;//使纵坐标在1~25之内
- } while (x % 2 != 0);//若生成的x为奇数,那么就重新生成
- psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合
- while (cur)
- {
- if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合
- {
- goto again;//重新生成
- }
- cur = cur->next;
- }
- //打印食物标号
- psnakenode food = (psnakenode)malloc(sizeof(snakenode));
- if (food == NULL)//防止申请内存失败
- {
- perror("CreatFood()::malloc()");
- return;
- }
- food->x = x;
- food->y = y;
- food->next = NULL;
- setpos(food->x, food->y);
- wprintf(L"%lc", FOOD);
- ps->_pfood = food;
- //getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
- }
- void GameStart(psnake ps)
- {
- //设置窗口大小,改变窗口名字
- system("mode con cols=100 lines=30");
- system("title 贪吃蛇");
- //隐藏光标
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_CURSOR_INFO cursorinfo;
- GetConsoleCursorInfo(houtput, &cursorinfo);//获取光标信息
- cursorinfo.bVisible = false;//隐藏鼠标
- SetConsoleCursorInfo(houtput, &cursorinfo);//设置光标状态
- //打印欢迎界面
- welcometogame();
- //打印地图
- creatmap();
- //初始化蛇
- InitSnake(ps);
- //创建食物
- CreatFood(ps);
- }
- //游戏暂停操作
- void Pause()
- {
- while (1)
- {
- Sleep(200);//死循环休眠则视为暂停
- if (KEY_PRESS(VK_SPACE))
- {
- break;//再次按下空格则跳出循环
- }
- }
- }
- //打印游戏帮助信息
- void PrintHelpInfo()
- {
- //打印提⽰信息
- setpos(64, 15);
- printf("不能穿墙,不能咬到自己\n");
- setpos(64, 16);
- printf("用↑.↓.←.→分别控制蛇的移动\n");
- setpos(64, 17);
- printf("F3 为加速,F4 为减速\n");
- setpos(64, 18);
- printf("ESC :退出游戏.space:暂停游戏.");
- }
- //判断下一个坐标是否为食物
- int NextIsFood(psnakenode pNextNode, psnake ps)
- {
- return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
- }
- //下一个坐标为食物
- void EatFood(psnakenode pNextNode, psnake ps)
- {
- //使用头插,将食物节点变为蛇头
- ps->_pfood->next = ps->_psnake;
- ps->_psnake = ps->_pfood;
- //释放掉pNextNode
- free(pNextNode);
- pNextNode = NULL;
- //打印出新蛇
- psnakenode cur = ps->_psnake;
- while (cur)
- {
- setpos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- //加分
- ps->_score += ps->_food_weight;
- //再次创建食物
- CreatFood(ps);
- }
- //下一个坐标不是食物
- void NoFood(psnakenode pNextNode, psnake 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;
- }
- //出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格
- setpos(cur->next->x, cur->next->y);
- printf(" ");
- //释放掉尾节点
- free(cur->next);
- //将倒数第二个节点的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->_sta = KILL_BY_WALL;
- }
- }
- //检测是否撞到自己
- void KillBySelf(psnake ps)
- {
- psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历
- while (cur)
- {
- if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y)
- {
- ps->_sta = KILL_BY_SELF;
- break;
- }
- cur = cur->next;
- }
- }
- //蛇走一步
- void SnakeMove(psnake ps)
- {//先创建一个新节点,表示蛇头即将到达的位置
- psnakenode pNextNode = (psnakenode)malloc(sizeof(snakenode));
- if (pNextNode == NULL)
- {
- perror("SnakeMove()::malloc()");
- return;
- }
- 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(psnake ps)
- {
- //打印游戏帮助信息
- PrintHelpInfo();
- do
- {
- //打印游戏得分信息
- setpos(64, 10);
- printf("总得分:%d\n", ps->_score);
- setpos(64, 11);
- printf("当前一个食物的分数:%2d\n", ps->_food_weight);
- //判断按键以控制蛇的移动
- if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下
- {
- 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();//Pause函数执行暂停操作
- }
- else if (KEY_PRESS(VK_ESCAPE))
- {
- //正常退出游戏
- ps->_sta = END_NORMAL;
- }
- else if (KEY_PRESS(VK_F3))
- {
- //加速
- if (ps->_sleep_time > 80)
- {
- ps->_sleep_time -= 30;
- ps->_food_weight += 2;
- }
- }
- else if (KEY_PRESS(VK_F4))
- {
- //减速
- if (ps->_sleep_time < 300)
- {
- ps->_sleep_time += 30;
- ps->_food_weight -= 2;
- }
- }
- SnakeMove(ps);//蛇走一步
- Sleep(ps->_sleep_time);
- } while (ps->_sta == OK);
- }
- //游戏结束
- void GameEnd(psnake ps)
- {
- setpos(24, 12);//设置光标位置,打印结束信息
- switch (ps->_sta)
- {
- case END_NORMAL:
- printf("游戏正常结束\n");
- break;
- case KILL_BY_WALL:
- printf("您撞到了墙,游戏结束\n");
- break;
- case KILL_BY_SELF:
- printf("您撞到了自己,游戏结束\n");
- break;
- }
- //释放蛇身
- psnakenode cur = ps->_psnake;
- while (cur)
- {
- psnakenode del = cur;
- cur = cur->next;
- free(del);
- del = NULL;
- }
- }
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<windows.h>
- #include<stdbool.h>
- #include<locale.h>
- #include<time.h>
- #define POS_X 24
- #define POS_Y 5
- #define WALL L'□'
- #define BODY L'●'
- #define FOOD L'※'
- #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
- //定义蛇身体的节点
- typedef struct snakenode
- {
- //坐标
- int x;
- int y;
- struct snakenode* next;//下一个节点
- }snakenode,*psnakenode;
- //定义方向
- enum direction
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
- //蛇的状态
- enum game_state
- {
- OK,//正常状态
- KILL_BY_WALL,//撞到墙
- KILL_BY_SELF,//撞到了自己
- END_NORMAL//正常结束游戏
- };
- //贪吃蛇
- typedef struct snake
- {
- psnakenode _psnake;//蛇头节点
- psnakenode _pfood;//食物节点
- enum direction _dir;//蛇的方向
- enum game_state _sta;//游戏状态
- int _food_weight;//一个食物的分数
- int _score;//总成绩
- int _sleep_time;//蛇的速度
- }snake,* psnake;
- //设置光标位置
- void setpos(int x, int y);
- //开始游戏
- void GameStart(psnake ps);
- //打印欢迎界面
- void welcometogame();
- //设置光标坐标
- void setpos(int x, int y);
- //打印游戏地图
- void creatmap();
- //蛇的初始化
- void InitSnake(psnake ps);
- //创建食物
- void CreatFood(psnake ps);
- //游戏的运行
- void GameRun(psnake ps);
- //打印游戏帮助信息
- void PrintHelpInfo();
- //游戏暂停操作
- void Pause();
- //蛇走一步
- void SnakeMove(psnake ps);
- //判断下一个坐标是否为食物
- int NextIsFood(psnakenode pNextNode, psnake ps);
- //下一个坐标为食物
- void EatFood(psnakenode pNextNode, psnake ps);
- //下一个坐标不是食物
- void NoFood(psnakenode pNextNode, psnake ps);
- //检测是否撞墙
- void KillByWall(psnake ps);
- //检测是否撞到自己
- void KillBySelf(psnake ps);
- //游戏结束
- void GameEnd(psnake ps);
快去享受游戏吧!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。