贪吃蛇小游戏的实现
- 讲解
- 1.Win32 API介绍
- 1.1控制台程序(system())
- 1.2控制台屏幕上的坐标CDDRD
- 1.3 GetStdHandle
- 1.4 GetConsoleCursorInfo
- 1.5 SetConsoleCursorInfo
- 1.6 SetConsoleCursorPostion
- 1.7 GetAsyncKeyState
- 2.游戏设计
- 2.1地图
- 2.2蛇身和食物
- 2.3数据结构设计
- 2.4游戏流程设计
- 3.核心逻辑实现分析
- 3.1游戏主逻辑
- 3.2 GameStart
- 3.3 GameRun
- 3.4 GameEnd
- 完整代码
- test.c
- snake.h
- snake.c
- 补充改进
实现贪吃蛇游戏的一些基本功能:地图绘制、蛇吃食物、死亡功能、加速减速、计分、暂停等。
讲解
1.Win32 API介绍
应用程序编程接口。
1.1控制台程序(system())
mode:设置控制台窗口的长宽。
mode con cols=100 lines=30
title:设置控制台窗口的名字。
title 贪吃蛇
1.2控制台屏幕上的坐标CDDRD
定义了x和y的一个结构体,表示一个字符在控制台屏幕缓冲区上的坐标,(0,0)位于顶部左侧单元格。
COORD pos = { 10,15 };//eg
1.3 GetStdHandle
用于从一个特定的标准设备中取得一个句柄,使用这个句柄可以操作设备。(类似于鼠标)
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
1.4 GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
CONSOLE_CURSOR_INFO是一个有关控制台光标的信息的结构体。包含两个属性:
- dwSize:由光标填充的字符单元格的百分比。
- bVisible:游标的可见性(布尔值)。
1.5 SetConsoleCursorInfo
设置指定控制台屏幕缓冲区的光标的大小和可见性。
SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
1.6 SetConsoleCursorPostion
设置指定控制台屏幕缓冲区中的光标位置。
COORD pos = {10,5};SetConsoleCursorPostion(houtput, pos);
1.7 GetAsyncKeyState
获取按键情况。
如果返回值的最低位是1则说明该按键被按过,否则为0。
#define KEY_PRESS(VK) ((GetAsycKeyState(VK) & 0x1) ? 1:0)
传递的参数为虚拟键码,可查表进行传值。
2.游戏设计
2.1地图
宽字符
大小:两个字节(传统字符),类型:wchar_t,头文件:<locale.h>
setlocale函数
setlocale(LC_ALL,"C");//正常模式setlocale(LC_ALL," ");//切换到本地环境
宽字符的打印
注意宽字符在定义和打印时均要在前面加上大写符号“L”,用“%lc”打印。
由图可解释,第一次打印时,长和宽设置一样的数字为什么不是正方形,这里宽也就是行(y)的数字设置时注意×2才等于另一个数值。
假设实现一个27行、58列的棋盘,外面一圈是墙体。
2.2蛇身和食物
蛇身:初始化定义蛇长度为5(宽字符),所以必须是2的倍数;
食物:随机生成,也得是2的倍数,不能和蛇的身体重合。
2.3数据结构设计
蛇每吃一个食物,身体就会长长一节,互相连接着,可用链表表示存储蛇的信息。
//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,* pSnakeNode;//typedef struct SnakeNode* pSnakeNode;//指向蛇身的指针
//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//休息时间,时间越短,速度越快;时间越长,速度越慢
}Snake,* pSnake;
//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};
2.4游戏流程设计
主要分为游戏开始、游戏运行、游戏结束三部分。
GameStart:
- 设置游戏窗口的大小
- 设置窗口的名字
- 隐藏屏幕光标
- 打印欢迎界面
- 创建地图
- 初始化蛇身
- 创建食物
GameRun: - 右侧打印帮助信息
- 打印当前已获得分数和每个食物的分数
- 获取按键情况
1.根据蛇头的坐标和方向,计算下一个节点的坐标
2.判断下一个节点是否是食物
3.是食物就吃掉
4.不是食物,往前一步,尾巴删除一节
4.判断是否撞墙
5.判断是否撞上自己 - 根据按键情况移动蛇
- 循环,直到游戏是结束状态
GameEnd: - 告知游戏结束的原因
- 释放蛇身节点
接下来我将根据上述逻辑开始实现每个部分的功能。
3.核心逻辑实现分析
3.1游戏主逻辑
#include<stdio.h>void test()
{int ch = 0;srand((unsigned int)time(NULL));do{system("cls");Snake snake = {0};GameStart(&snake);GameRun(&snake);GameEnd(&snake);SetPos(20,15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n}while(ch == 'Y');SetPos(0,27);
}int main()
{//修改当前地区为本地模式,为了支持中文宽字符的打印setlocale(LC_ALL," ");//测试逻辑test();return 0;
}
上述游戏的主要三个部分的函数都采用了传址调用,才能影响变量,函数使用一级指针接收地址,两个结构体指针在定义时已经设置好,则在下面的函数需要时直接使用。
3.2 GameStart
- 设置游戏窗口的大小
- 设置窗口的名字
- 隐藏屏幕光标
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();//打印地图CreateMap();//初始化蛇InitSnake(ps);//创造第一个食物CreateFood(ps);
}
- 打印欢迎界面
void WelcomeToGame()
{SetPos(40,15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40,25);system("pause");system("cls");SetPos(25,12);printf("用 ↑ ↓ ← → 分别控制蛇的移动,F3为加速,F4为减速\n");SetPos(25,13);printf("加速将能得到更高的分数。\n");SetPos(40,25);system("pause");system("cls");
}
注意:以上坐标位置的设置均是为了观感整洁好看,可自行调整。
- 创建地图
算好坐标(易错点)打印墙体。
void CreateMap()
{int i = 0;//上SetPos(0,0);for(i=0;i<58;i+=2){wprintf(L"%c",WALL);}//下SetPos(0,26);for(i=0;i<58;i+=2){wprintf(L"%c",WALL);}//左for(i=1;i<26;i++){SetPos(0,i);wprintf(L"%c",WALL);}//右for(i=1;i<26;i++){SetPos(56,i);wprintf(L"%c",WALL);}
}
- 初始化蛇身
蛇最开始长度为5节,创建完节点后依次打印出来。
初始位置:(24,5);
游戏状态:OK;
移动速度:200毫秒;
初试成绩:0;
每个食物的分数:10
void InitSnake(pSnake ps)
{pSnakeNode = NULL;int i = 0;//头插法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 + i * 2;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->_sleep_time = 200;ps->_score = 0;ps->_status = OK;ps->_dir = RIGHT;ps->_food_weight = 10;
}
- 创建第一个食物
随机生成 , 2的倍数 , 不能和蛇身重复 , 打印
void CreateFood(pSnake ps)
{int x = 0;int y = 0;
again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐do{x = rand()%53+2;//2~54y = rand()%25+1;//1~25}while(x % 2 != 0)//如果不符合2的倍数的要求则继续生成pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针//食物不能和蛇身冲突while(cur){if(cur->x == x && cur->y == y){goto again;}cur = cur->next;}pSnake pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建食物if(pFood == NULL){perror("CreateFood::malloc()");return;}else{pFood->x = x;pFood->y = y;SetPos(pFood->x,pFood->y);wprintf(L"%c",FOOD);ps->_pFood = pFood;}
}
3.3 GameRun
- 虚拟按键:
上:VK_UP
下:VK_DOWN
左:VK_LEFT
右:VK_RIGHT
空格:VK_SPACE
ESC:VK_ESCAPE
F3:VK_F3
F4:VK_F4
void GameRun()
{//打印右侧提示信息PrintHelpInfo();do//由于do-while语句最后执行判断会先执行其中的打印语句故选用{SetPos(64,10);printf("得分:%d",ps->_score);printf("每个食物得分:%d分",ps->_food_weight);if(KEY_PRESS(VK_UP) && ps->_dir != DOWN)//只有当发出向上走的命令并且蛇本来不是往下走时,执行向上的操作//这里不能写成"本来往下,但是往上指令所以不执行"的语句,else还有很多情况{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->_sleep_time >= 100){ps->_sleep_time -= 20;ps->_food_weight += 1;}}else if(KEY_PRESS(VK_F4)){if(ps->_sleep_time <= 300){ps->_sleep_time += 20;ps->_food_weight -=1;}}//蛇每次移动之间要有休眠的时间Sleep(ps->_sleep_time);SnakeMove(ps);}while(ps->_status == OK);
}
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 :暂停游戏\n");SetPos(64, 20);printf("星光熠熠");
- 蛇身移动
先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
确定了下一个位置后,看下一个位置是否是食物,是食物就在蛇头前面加一个节点,如果不是食物就吃掉食物并释放最后一个节点(空格覆盖食物)。
蛇身移动后,判断此移动是否会造成撞墙或者撞自己,从而更新游戏的状态。
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//如果不是食物{NotFood(pNextNode,ps);}KillByWall(ps);KillBySelf(ps);
}
//pn是下一个节点的地址
//ps维护蛇的指针
int NextIsFood(pSnakeNode pn,pSnake ps)
{return (pn->x == ps->_pFood->x) && (pn->y == ps->_pFood->y);
}
void EatFood(pSnakeNode pn,pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;//打印蛇pSnakeNode cur = ps->_pSnake;while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}ps->_score += ps->_food_weight;//释放食物节点free(ps->_pFood);//创建新的食物CreateFood(ps);
}
不是食物的易错点:释放最后一个节点后,还需将指向最后一个节点的指针改为NULL,也就是最后一个节点的前一个节点。
void NotFood(pSnakeNode pn,pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;//打印蛇pSnakeNode cur = ps->_pSnake;while(cur->next->next){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}//最后一个位置打印空格,然后释放节点SetPos(cur->next->x,cur->next->y);printf(" ");free(cur->next);cur->next = NULL;
}
int KillByWall(pSnake ps)
{if((ps->_pSnake->x == 0)||(ps->_pSnake->x == 56)||(ps->_pSnake->y == 0)||(ps->_pSnake->y == 26)){ps->_status = KILL_BY_WALL;return 1;}return 0;
}
int 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;return 1;}cur = cur->next;}return 0;
}
3.4 GameEnd
当游戏状态不再是OK的时候,要告知游戏结束的原因,并且释放蛇身节点。
void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24,12);switch(ps->_status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while(cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}
完整代码
一共分为三个文件。
test.c
#include"snake.h"//完成的是游戏的测试逻辑
void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1. 打印环境界面//2. 功能介绍//3. 绘制地图//4. 创建蛇//5. 创建食物//6. 设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏 - 善后工作GameEnd(&snake);SetPos(20, 15);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;
}
snake.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<locale.h>
#include<time.h>
#include<Windows.h>
#include<stdbool.h>#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//类型的声明//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//typedef struct SnakeNode* pSnakeNode;//指向蛇身的指针//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//休息时间,时间越短,速度越快;时间越长,速度越慢
}Snake, * pSnake;//函数的声明//定位光标位置
void SetPos(short x, short y);//游戏的初始化
void GameStart(pSnake ps);//欢迎界面的打印
void WelcomeToGame();//创建地图
void CreateMap();//初始化蛇身
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏运行的逻辑
void GameRun(pSnake ps);//蛇的移动-走一步
void SnakeMove(pSnake ps);//判断下一个坐标是否是食物
int 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
#include "snake.h"void SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;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 CreateMap()
{//上int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i < 29; 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(56, i);wprintf(L"%lc", WALL);}}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->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 300;//单位是毫秒ps->_status = OK;}void CreateFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数//x:2~54//y: 1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和蛇的身体坐标冲突pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物的节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);//定位位置wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}void GameStart(pSnake ps)
{//0. 先设置窗口的大小,再光标隐藏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);//设置控制台光标状态//1. 打印环境界面和功能介绍WelcomeToGame();//2. 绘制地图CreateMap();//3. 创建蛇InitSnake(ps);//4. 创建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L"按F3加速,F4减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");SetPos(64, 18);wprintf(L"%ls", L"星光熠熠");
}#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)//按过void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}//int NextIsFood(pSnakeNode pn, pSnake ps)
//{
// if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
// return 1;
// else
// return 0;
//}int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);//注意这里的pn指向的是蛇身
}void EatFood(pSnakeNode pn, pSnake ps)
{//头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点(pn本来指向的是蛇头的下一个节点,现在吃了食物释放掉这个节点)free(pn);pn = NULL;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}void NotFood(pSnakeNode pn, pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//把最后一个结点打印成空格SetPos(cur->next->x, cur->next->y);printf(" ");//释放最后一个结点free(cur->next);//把倒数第二个节点的地址置为NULLcur->next = NULL;
}void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = 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->_status = 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{NotFood(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){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;}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->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"您主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"您撞到墙上,游戏结束\n");break;case KILL_BY_SELF:wprintf(L"您撞到了自己,游戏结束\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}
补充改进
一些可以创新的点:
- 穿墙设定;
- 食物分类;
- 多个实物;
- 蛇碰到食物的一半也会吃掉…
(请自行补充)…