用c语言在window环境的控制台中模拟写出贪吃蛇小游戏。
一.要实现的基本的功能
如下:
贪吃蛇的地图绘制
贪吃蛇吃食物的功能(方向控制蛇的动作)
贪吃蛇撞墙死亡判断
贪吃蛇撞自己死亡判断
计算得分
贪吃蛇的速度
暂停游戏
二 贪吃蛇的铺垫准备:
1.控制台的窗口大小控制
system("mode con cols=250 lines=80"); cols (列) lines(行);
2.控制台屏幕上的坐标COORD
一般来说,控制台的输出一般是从左上角开始的,但是贪吃蛇的食物应该设置成随机出现,那我们需要先了解一下控制台屏幕上的坐标COORD:
COORD是windowsAPI中的定义的一个结构体,表示一个字符在控制台屏幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。
COORD类型的声明:
typedef struct _COORD{SHORT X;SHORT Y;}COORD,_PCOORD;
给坐标赋值:
COORD pos={x,y};
3.控制台光标隐藏
在一般的情况下,程序结束后,控制台上总有一个光标在闪烁
我们需要把它给隐藏掉。
//光标影藏掉
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标的操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
这段代码的功能是为了把光标给隐藏掉。
4.获取按键GetAsynKeyState
这个函数是获取按键的情况。(用与获取贪吃蛇的方向控制)
GetAsynKeyState的函数原型如下:
SHORT GetAsynKeyState(int vKey);
将键盘上的每个键的虚拟键值传递给函数,函数通过返回值来分辨按键状态。
GetAsynKeyState的返回值是short类型,在上一次调用GetAsynKeyState函数后,如果返回的16位short数据中,最高位是1,说明按键的状态按下,如果是0,说明按键的状态是抬起;如果最低位置为1则说明该按键被安国,否则为0。
为了代码的简易性,这里把该函数给定义成宏来使用更简便:
# define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
这里的意思是说,当键盘是按过状态的时候,返回1,没按下过的状态返回值为0
这里用一个例子来简单说明一下:
如图:当我按下键盘1的时候,第1个if判断返回值为0,所以为假,跳转到第二个if条件判断
第2个if里的判断为真,,所以打印了1.。。。
当我再按下键盘2的时候,第1个和第2个if条件判断都为假,跳转到第三个if条件判断才为真,打印2.。。。
当按下除了0;1;2;3这四个键之外的所有键,均为判断为假,不输出任何信息;
5.设置程序适应本地环境
setlocale(LC_ALL, ""); //点击跳转至setlocale函数讲解。
6.食物刷新的随机坐标
srand((unsigned int)time(NULL)); //点击跳转至srand函数讲解。
三.贪吃蛇游戏的理解
1.贪吃蛇在c语言中可以理解为一条链表,通过不断的移动链表的方向与位置,把某个坐标食物“吃”掉,成为链表的一份子,在链表中的结构如下:
//贪吃蛇结点的描述
typedef struct SnakeNode
{//坐标int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
2.游戏中我们还需要时刻通过键盘的按键来控制蛇的走向,所以这个时候,还需要一个变量状态来控制着蛇的方向,所以要枚举一个方向DIRECTION
enum DIRECTION
{UP = 1, //上DOWN, //下LEFT, //左RIGHT //右
};
3.在这个过程中,我们需要绘制一个范围地图,比如:墙,或者障碍体,蛇每一次移动,都需要判断链表的头结点(贪吃蛇的头部)是否越界,或者触碰到障碍体与蛇头触碰到自己的身体而死掉,还要枚举一个GAME_STATUS来记录游戏输掉与结束的原因。
enum GAME_STATUS
{OK,//正常运行END_NORMAL,//按ESC退出KILL_BY_WALL,//触碰到墙体KILL_BY_SELF //触碰到身体
};
4.如果想功能丰富些,还可以自定义添加得分机制,和蛇的移动速度自定义调节之类,添加闯关功能,添加障碍体等等。
通过上面分析,我们可以得知贪吃蛇大致的结构:
//贪吃蛇的结构
typedef struct Snake
{pSnakeNode _pSnake;//指向贪吃蛇头结点的指针pSnakeNode _pFood;//指向食物结点的指针int _Score;//贪吃蛇累计的总分int _FoodWeight;//一个食物的分数int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢enum DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
四.贪吃蛇的代码:
接下来就是关于游戏的代码写法了。
void test()
{int ch = 0;do{Snake snake = { 0 };//创建了贪吃蛇//1. 游戏开始 - 初始化游戏GameStart(&snake);//2. 游戏运行 - 游戏的正常运行过程GameRun(&snake);//3. 游戏结束 - 游戏善后(释放资源)GameEnd(&snake);SetPos(20, 18);printf("再来一局吗?(Y/N):");ch = getchar();getchar();// 清理掉\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置程序适应本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test(); return 0;
}
先来一个大概的游戏入口test函数
test()函数里包含了贪吃蛇的游戏内容
总体分为三部分:
//1. 游戏开始 - 初始化游戏
GameStart(&snake);
//2. 游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3. 游戏结束 - 游戏善后(释放资源)
GameEnd(&snake);
这三个函数里面各分别还需要实现一些小功能的函数
这里先实现一下 GameStart(&snake)这个函数的各个功能并讲解:
1. GameStart(&snake) :
void GameStart(pSnake ps)
{//控制台窗口的设置system("mode con cols=250 lines=80"); //控制台的窗口大小system("title 梁丶贪吃蛇"); //控制台上面的名字命名//光标影藏掉HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印游戏玩法界面WelComeToGame();//创建地图CreateMap();//初始化贪食蛇InitSnake(ps);//创建食物CreateFood(ps);
}
1.1 //打印游戏玩法界面——WelComeToGame();
void WelComeToGame()
{//定位光标SetPos(115, 35);printf("欢迎来到贪吃蛇小游戏");SetPos(115, 36);system("pause");//pause是暂停system("cls");SetPos(100, 35);printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");SetPos(115, 36);system("pause");system("cls");
}
这里的函数功能实现的效果如下:
SetPos()为自己自定义的函数,他的功能是指定输出的位置坐标,
//设置光标的坐标 1
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}
简单来说就是在指定的位置输出信息。
1.2 //打印墙体——void CreateMap();
如图红色标记的就是所谓的墙体,这里的墙体显示是使用多个 ”口“ 符号来简单设置的。
要在控制台上打印该”墙体“需要用到之前的setpos()函数,
简单的理解就是使用for循环在每个坐标的位置打印一个”口“;
//打印地图
void CreateMap()
{ //上SetPos(0, 0);int i = 0;for (i = 0; i <= 200; i += 2) //这里的墙体大小随意改,不一定要200,合适就好{wprintf(L"%lc", WALL); //注:#define WALL L'□'} //下SetPos(0, 70);for (i = 0; i <= 200; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i < 70; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 70; i++){SetPos(200, i);wprintf(L"%lc", WALL);}
}
1.3 //初始化贪食蛇——InitSnake(ps);
设置完地图,现在要打造一下蛇的身体了
//初始化贪吃蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL; //游戏开始的时候,我们默认一下蛇的身体有五个节点for (int i = 0; i < 5; i++) //在此循环创建五个节点,并相连成链表{cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc err...");return;}cur->x = POS_X + i * 2; //pos_x和pos_y为贪吃蛇头的节点位置,cur->y = POS_Y; //在头文件里会define一下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"%1c", BODY); //注:#define BODY L'●'cur = cur->next;}ps->_Status = OK; //游戏的状态:正常、退出、撞墙、吃到自己ps->_Score = 0; //贪吃蛇累计的总分ps->_pFood = NULL; //指向食物结点的指针ps->_SleepTime = 200; //每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢ps->_FoodWeight = 10; //一个食物的分数ps->_Dir = RIGHT; //描述蛇的方向
}
这里是把游戏的初期给初始化
如图中的位置,贪吃蛇的位置不一定是固定的,也可自己更改调试。
1.4 //创建食物——CreateFood(ps);
把贪吃蛇初始化后,接下来就要把食物给创建出来了。
void CreateFood(pSnake ps)
{int x = 0; //创建两个坐标变量x与y;int y = 0;
tmp:do{x = rand() % 195 + 4; //因为我的创建的墙体宽度是200,所以为了保证食物坐标在墙体内y = rand() % 66 + 2; //所以需要%195,y坐标同理(要注意墙体的位置,不要与墙体重叠)} while (x % 2 != 0); //这里的判断结束条件是因为单个墙体”口“的宽度是2,注意~//不能与蛇身重复pSnakeNode cur = ps->_pSnake; //创建一个新节点赋予蛇的头结点while (cur) //要遍历一下该节点的坐标与蛇的身体坐标有没有重叠{ //重叠的话就用goto语句重新生成一个食物节点坐标if (cur->x == x && cur->y == y) {goto tmp;}cur = cur->next;}//当没有重叠的时候,创建一个新节点,赋予x和y的坐标值建立新节点并打印食物标志☆pSnakeNode set = (pSnakeNode)malloc(sizeof(SnakeNode));if (set == NULL){perror("creatrfood():err...");return;}set->x = x;set->y = y;ps->_pFood = set;//打印食物SetPos(x, y);wprintf(L"%lc", FOOD); 打印食物 “☆”
}
2.GameRun(&snake);
这里就需要运用到GetAsynKeyState(文章铺垫有讲解)
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo(); //这里是为了贴心点,打印一下玩法信息讲解。do{SetPos(205, 10); //这些坐标位置是需要自己去选取合适的位置printf("得分:%05d", ps->_Score);SetPos(205, 12);printf("每个食物的分数:%2d", 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 = END_NORMAL;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->_SleepTime < 320){ps->_SleepTime += 30;ps->_FoodWeight -= 2;}}Sleep(ps->_SleepTime); //系统库函数sleep(),休眠毫秒数SnakeMove(ps); } while (ps->_Status == OK);//循环条件为ps->status,当这里的状态为ok的时候,游戏正常继续运行,//当为其他状态的时候,说明游戏已经结束(输了),结束循环break。选择继续或下一把!}
2.1 //打印帮助信息——PrintHelpInfo();
文本打印的位置可以靠setpos(x,y)函数来更改合适的位置。
//打印帮助信息
void PrintHelpInfo()
{SetPos(205, 35);printf("1.不能撞墙,不能咬到自己");SetPos(205, 37);printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");SetPos(205, 39);printf("3.F3加速,F4减速");SetPos(205, 41);printf("4.ESC-退出, 空格-暂停游戏");SetPos(205, 43);printf("凉凉~");
}
2.2 //解除暂停状态——void Pause()
void Pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}
这里使用一个死循环,当再次按下空格键的时候,便结束循环。
2.2 //控制蛇的方向并判断食物——void SnakeMove(pSnake ps)
void SnakeMove(pSnake ps)
{pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("snakemove( ):err...");return;}cur->next = NULL; //创建一个新节点,用于存储蛇的下一个方向移动的信息。switch (ps->_Dir) //根据蛇的方向信息判断下一个节点蛇的位置从而更改蛇节点的坐标x和y{case UP:cur->x = ps->_pSnake->x;cur->y = ps->_pSnake->y - 1;break;case DOWN:cur->x = ps->_pSnake->x;cur->y = ps->_pSnake->y + 1;break;case LEFT:cur->x = ps->_pSnake->x - 2;cur->y = ps->_pSnake->y;break;case RIGHT:cur->x = ps->_pSnake->x + 2;cur->y = ps->_pSnake->y;break;}//判断蛇头到达的坐标处是否是食物if (NextIsFood(ps, cur)){//吃掉食物EatFood(ps, cur);//CreateFood(ps);}else{//不吃食物NoFood(ps, cur);}//蛇是否撞墙KillByWall(ps);//蛇是否自杀KillBySelf(ps);
}
2.2.1 //判断蛇头到达的坐标处是否是食物——NextIsFood(ps, cur)
int NextIsFood(pSnake ps, pSnakeNode pnext)
{if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y){return 1;}else{return 0;}
}
用食物节点与新节点cur比较。
这里分两种情况:
1,当坐标的x与y相等时,这时候就说明贪吃蛇”吃到“食物为真
返回 1 .并执行下一步EatFood( )
吃到食物——EatFood()
//吃掉食物
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"%1c", BODY);cur = cur->next;}//free掉食物节点;free(ps->_pFood);ps->_Score += ps->_FoodWeight;//CreateFood(ps);CreateFood(ps);
}
2. 反之没有”吃到“食物为假,并返回 0 不执行EatFood(),代表这个位置没吃到食物,继续往下走。
没有吃到食物——void NoFood ( )
//不吃食物
void NoFood(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"%1c", BODY);cur = cur->next;}SetPos(cur->next->x, cur->next->y);printf(" ");//pSnakeNode fp = cur->next;//cur->next = NULL;free(cur->next);cur->next = NULL;
}
判断完是否吃到食物后,就需要判断贪吃蛇本体是否撞墙,或者撞到自己的身体部分了
判断蛇是否撞墙——KillByWall(ps);
//蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 ||ps->_pSnake->x == 200 ||ps->_pSnake->y == 0 ||ps->_pSnake->y == 69)ps->_Status = KILL_BY_WALL; //当if为真的时候,把status状态更改为kill_by——wall//打印游戏结束后的判断条件
}
蛇是否自杀——void KillBySelf(pSnake ps)
//蛇是否自杀
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){ps->_Status = KILL_BY_SELF;}cur = cur->next; //遍历节点,当与身体相撞时更改status状态;}
}
3.GameEnd(pSnake ps)
当编程走到这说明游戏已经结束。
现在要打印一下输赢状态与获取分数等数据信息并释放一下蛇的节点内存
void GameEnd(pSnake ps)
{SetPos(20, 12);switch (ps->_Status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("自杀了,游戏结束\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;}//释放蛇身的结点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps->_pSnake = NULL;}
贪吃蛇小游戏就结束了!
游戏比较简单,还可以添加许多玩法。
增加障碍物
循环地图
添加生命次数
等。。。。。。。
以下为全代码:
//#pragma once#include <locale.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'#define POS_X 24
#define POS_Y 5#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};enum GAME_STATUS
{OK,//正常运行END_NORMAL,//按ESC退出KILL_BY_WALL,KILL_BY_SELF
};//贪吃蛇结点的描述
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 DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;//游戏开始 - 完成游戏的初始化动作 1
void GameStart(pSnake ps);//定位坐标
void SetPos(short x, short y);//游戏开始的欢迎界面 1
void WelComeToGame();//打印地图 1
void CreateMap();//初始化贪吃蛇 1
void InitSnake(pSnake ps);//创建食物 1
void CreateFood(pSnake ps);//游戏的正常运行 1
void GameRun(pSnake ps);//打印帮助信息 1
void PrintHelpInfo();//游戏暂定和恢复 1
void Pause();//蛇的移动 1
void SnakeMove(pSnake ps);//判断蛇头到达的坐标处是否是食物 1
int NextIsFood(pSnake ps, pSnakeNode pnext);//吃掉食物 1
void EatFood(pSnake ps, pSnakeNode pnext);//不吃食物 1
void NoFood(pSnake ps, pSnakeNode pnext);//蛇是否撞墙
void KillByWall(pSnake ps);//蛇是否自杀
void KillBySelf(pSnake ps);//游戏结束后的善后处理
void GameEnd(pSnake ps);#define _CRT_SECURE_NO_WARNINGS 1#include "sneke.h"//设置光标的坐标 1
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}void WelComeToGame()
{//定位光标SetPos(115, 35);printf("欢迎来到贪吃蛇小游戏");SetPos(115, 36);system("pause");//pause是暂停system("cls");SetPos(100, 35);printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");SetPos(115, 36);system("pause");system("cls");
}//打印地图
void CreateMap()
{//上SetPos(0, 0);int i = 0;for (i = 0; i <= 200; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 70);for (i = 0; i <= 200; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i < 70; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 70; i++){SetPos(200, i);wprintf(L"%lc", WALL);}
}//初始化贪吃蛇
void InitSnake(pSnake ps)
{//pSnake cur = (pSnake)malloc(sizeof(Snake));pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc err...");return;}cur->x = POS_X + i * 2;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"%1c", BODY);cur = cur->next;}ps->_Status = OK;ps->_Score = 0;ps->_pFood = NULL;ps->_SleepTime = 200;ps->_FoodWeight = 10;ps->_Dir = RIGHT;
}void CreateFood(pSnake ps)
{int x = 0;int y = 0;
tmp:do{x = rand() % 195 + 4;y = rand() % 66 + 2;} while (x % 2 != 0);//不能与蛇身重复pSnakeNode cur = ps->_pSnake;while (cur){if (cur->x == x && cur->y == y){goto tmp;}cur = cur->next;}pSnakeNode set = (pSnakeNode)malloc(sizeof(SnakeNode));if (set == NULL){perror("creatrfood():err...");return;}set->x = x;set->y = y;ps->_pFood = set;//打印食物SetPos(x, y);wprintf(L"%lc", FOOD);
}void GameStart(pSnake ps)
{//控制台窗口的设置system("mode con cols=250 lines=80");system("title 梁丶贪吃蛇");//光标影藏掉HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界面WelComeToGame();//创建地图CreateMap();//初始化贪食蛇InitSnake(ps);//创建食物CreateFood(ps);
}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"%1c", BODY);cur = cur->next;}//free掉食物节点;free(ps->_pFood);ps->_Score += ps->_FoodWeight;//CreateFood(ps);CreateFood(ps);
}//不吃食物
void NoFood(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"%1c", BODY);cur = cur->next;}SetPos(cur->next->x, cur->next->y);printf(" ");//pSnakeNode fp = cur->next;//cur->next = NULL;free(cur->next);cur->next = NULL;
}//蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 ||ps->_pSnake->x == 200 ||ps->_pSnake->y == 0 ||ps->_pSnake->y == 69)ps->_Status = KILL_BY_WALL;
}//蛇是否自杀
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){ps->_Status = KILL_BY_SELF;}cur = cur->next;}
}void SnakeMove(pSnake ps)
{pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("snakemove( ):err...");return;}cur->next = NULL;switch (ps->_Dir){case UP:cur->x = ps->_pSnake->x;cur->y = ps->_pSnake->y - 1;break;case DOWN:cur->x = ps->_pSnake->x;cur->y = ps->_pSnake->y + 1;break;case LEFT:cur->x = ps->_pSnake->x - 2;cur->y = ps->_pSnake->y;break;case RIGHT:cur->x = ps->_pSnake->x + 2;cur->y = ps->_pSnake->y;break;}//判断蛇头到达的坐标处是否是食物if (NextIsFood(ps, cur)){//吃掉食物EatFood(ps, cur);//CreateFood(ps);}else{//不吃食物NoFood(ps, cur);}//蛇是否撞墙KillByWall(ps);//蛇是否自杀KillBySelf(ps);
}void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{SetPos(205, 10);printf("得分:%05d", ps->_Score);SetPos(205, 12);printf("每个食物的分数:%2d", 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 = END_NORMAL;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->_SleepTime < 320){ps->_SleepTime += 30;ps->_FoodWeight -= 2;}}Sleep(ps->_SleepTime);SnakeMove(ps);} while (ps->_Status == OK);}
//打印帮助信息
void PrintHelpInfo()
{SetPos(205, 35);printf("1.不能撞墙,不能咬到自己");SetPos(205, 37);printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");SetPos(205, 39);printf("3.F3加速,F4减速");SetPos(205, 41);printf("4.ESC-退出, 空格-暂停游戏");SetPos(205, 43);printf("凉凉~");
}
void GameEnd(pSnake ps)
{SetPos(20, 12);switch (ps->_Status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("自杀了,游戏结束\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;}//释放蛇身的结点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps->_pSnake = NULL;}#define _CRT_SECURE_NO_WARNINGS 1#include "sneke.h"void test()
{int ch = 0;do{Snake snake = { 0 };//创建了贪吃蛇//1. 游戏开始 - 初始化游戏GameStart(&snake);//2. 游戏运行 - 游戏的正常运行过程GameRun(&snake);//3. 游戏结束 - 游戏善后(释放资源)GameEnd(&snake);SetPos(20, 18);printf("再来一局吗?(Y/N):");ch = getchar();getchar();// 清理掉\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置程序适应本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}