赞
踩
#include<stdio.h> #include<locale.h> #include<windows.h> #include<stdbool.h> #include<stdlib.h> #include<time.h> // 全局变量,保存原始的文本属性 WORD originalAttributes; #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, 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;//游戏的运行状态:正常、按ESC退出、撞墙死、自杀 int _HighScore;// 历史最高分 const char* _FilePath;// 历史最高分记录文件路径 }Snake,*pSnake; //游戏开始,完成游戏的初始化 void GameStart(pSnake ps); //设置光标坐标 void SetPos(short x, short y); //设置贪吃蛇颜色 void color(int c); //恢复原始的文本颜色属性 void restoreOriginalColor(); //打印欢迎界面 void WelComeToGame(); //创建地图 void CreateMap(); //初始化贪食蛇结构 void InitSnake(pSnake ps); //游戏的正常运行 void GameRun(pSnake ps); //蛇的移动 void SnakeMove(pSnake ps); //判断蛇头到达的坐标处是否是食物 int NextIsFood(pSnake ps, pSnakeNode pnext); //吃掉食物 void EatFood(pSnake ps, pSnakeNode pnext); //不吃食物 void NoFood(pSnake ps, pSnakeNode pnext); //蛇是否撞墙死 void KillByWall(pSnake ps); //蛇是否自杀 void KillBySelf(pSnake ps); //打印帮助信息 void PrintHelpInfo(); //按空格暂停以及恢复 void Pause(); //游戏结束后的善后处理 void GameEnd(pSnake ps); //设置贪吃蛇颜色 void color(int c) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); // 保存原始的文本属性 CONSOLE_SCREEN_BUFFER_INFO consoleInfo; GetConsoleScreenBufferInfo(hConsole, &consoleInfo); originalAttributes = consoleInfo.wAttributes; // 设置新的文本属性 SetConsoleTextAttribute(hConsole, c); } //恢复原始的文本属性 void restoreOriginalColor() { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); // 恢复原始的文本属性 SetConsoleTextAttribute(hConsole, originalAttributes); } //设置光标坐标 void SetPos(short x, short y) { COORD pos = { x, y }; HANDLE hOutput = NULL; //获取标准输出的句柄(用来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos); } //打印欢迎界面 void WelComeToGame() { //设置颜色 color(3); SetPos(40, 14);//定位光标 printf("欢迎来到贪食蛇!"); SetPos(40, 25); system("pause");//对程序暂停一下 system("cls");//清空屏幕 SetPos(20, 14); printf("使用 ↑.↓.←.→.分别控制蛇的移动, F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); } //创建地图 void CreateMap() { //设置墙体颜色 color(FOREGROUND_RED | FOREGROUND_GREEN); //上 SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (i = 1; i < 26; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i < 26; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } //恢复原始的文本属性 restoreOriginalColor(); } //初始化贪食蛇 void InitSnake(pSnake ps) { pSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) {//初始蛇身给他五个节点 cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake()::malloc()"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; //头插法-----将每个蛇身连接起来 if (ps->_pSnake == NULL) { ps->_pSnake = cur; } else { cur->next = ps->_pSnake; ps->_pSnake = cur; } } //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //恢复原始的文本属性 restoreOriginalColor(); //初始化下贪吃蛇结构中的其他内容 ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_FoodWeight = 10; ps->_SleepTime = 200; ps->_Dir = RIGHT; ps->_HighScore = 0; // 初始化历史最高分 // 检查历史最高分文件是否存在,如果不存在则创建 FILE* file = fopen("highscore.txt", "r"); if (file == NULL)//不存在highscore.txt文件 { // 创建新文件 file = fopen("highscore.txt", "w"); if (file != NULL) { fprintf(file, "%d", 0); // 初始时将历史最高分设置为0 fclose(file); } else { perror("GameStart()::fopen()"); } } else//存在highscore.txt文件 { fclose(file); } ps->_FilePath = "highscore.txt"; // 设置历史最高分记录文件路径 } //创建食物 void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do {//在墙内寻找生成食物的坐标 x = rand() % 53 + 2;//随机数范围2-54 y = rand() % 25 + 1; } while (x % 2 != 0);//食物的生成坐标要为2的倍数,要不让蛇嵌进墙里咋办,还有蛇只吃到一半食物咋办 //食物已经在墙内了,现在要看它是否与蛇身重叠 pSnakeNode cur = ps->_pSnake; while (cur) {//遍历判断蛇身坐标是否与所寻找的食物坐标重叠 if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } //创建食物节点(可将食物理解成蛇身的一部分) pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreateFood()::mallloc()"); return; } pFood->x = x; pFood->y = y; pFood->next = NULL; ps->_pFood = pFood; //打印食物 //食物颜色 color(FOREGROUND_RED); SetPos(x, y); wprintf(L"%lc", FOOD); //恢复原始的文本属性 restoreOriginalColor(); } //游戏开始,完成游戏的初始化 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); // 读取历史最高分记录文件 FILE* file = fopen(ps->_FilePath, "r"); if (file != NULL) { fscanf(file, "%d", &ps->_HighScore);//从ps->_FilePath文件中读取最高分放到ps->_HighScore种 fclose(file); } } //判断蛇头到达的坐标处是否是食物 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; //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //恢复原始的文本属性 restoreOriginalColor(); free(ps->_pFood);//此处的食物节点已被pnext记录,并被打印了,所以用后要及时释放 ps->_Score += ps->_FoodWeight; //创建一个新的食物 CreateFood(ps); } //不吃食物 void NoFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); //恢复原始的文本属性 restoreOriginalColor(); free(cur->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->_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; return 1; } cur = cur->next; } } //蛇的移动 void SnakeMove(pSnake ps) { //定义一个pNext用于记录蛇头将要到达的下一个位置节点 pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; //根据按键状态来寻找pNext的坐标位置 switch (ps->_Dir) {//上、下、左、右 case UP: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y - 1; break; case DOWN: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y + 1; break; case LEFT: pNext->x = ps->_pSnake->x - 2; pNext->y = ps->_pSnake->y; break; case RIGHT: pNext->x = ps->_pSnake->x + 2; pNext->y = ps->_pSnake->y; break; } //判断蛇头到达的坐标处是否是食物 if (NextIsFood(ps, pNext)) { //吃掉食物 EatFood(ps, pNext); } else { //不吃食物 NoFood(ps, pNext); } //蛇是否撞墙死 KillByWall(ps); //蛇是否自杀 KillBySelf(ps); } //打印帮助信息 void PrintHelpInfo() { SetPos(64, 15); printf("1.不能撞墙,不能咬自己"); SetPos(64, 16); printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动"); SetPos(64, 17); printf("3.F3加速,F4减速"); SetPos(64, 18); printf("4.ESC-退出,空格-暂停"); SetPos(64, 20); printf("@摆烂的小z"); } //按空格暂停 void Pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) { break; } } } //游戏的正常运行 void GameRun(pSnake ps) { //打印帮助信息 PrintHelpInfo(); do { //分数时时更新啦 SetPos(64, 10); printf("得分:%5d", ps->_Score); SetPos(64, 11); printf("每个食物分数:%2d", ps->_FoodWeight); SetPos(64, 12); printf("历史最高分:%5d", ps->_HighScore); // 比较并更新历史最高分 if (ps->_Score > ps->_HighScore) { ps->_HighScore = ps->_Score; // 保存历史最高分到文件 FILE* file = fopen(ps->_FilePath, "w"); if (file != NULL) { fprintf(file, "%d", ps->_HighScore);//将新的历史最高分ps->_HighScore写到ps->_FilePath文件中 fclose(file); } } 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))//按ESC退出 { 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 GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->_Status) { case END_NORMAL: printf("您主动退出了游戏"); break; case KILL_BY_SELF: printf("您自杀了"); break; case KILL_BY_WALL: printf("您撞墙了"); break; } //一局游戏结束,释放蛇身的节点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; //释放食物节点 free(ps->_pFood); ps->_pFood = NULL; } void test() { char ch = 0; do { Snake snake = { 0 }; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 18); printf("再来一局?(Y/N):"); ch = getchar(); // 获取用户输入的字符 //清空输入缓冲区,直到遇到换行符 int c; while ((c = getchar()) != '\n' && c != EOF); } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); } int main() { //设置程序适应本地环境 setlocale(LC_ALL, ""); srand((unsigned int)time(NULL)); test(); return 0; }
Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以称为ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows32位平台的应用程序编程接口。
我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列
mode con cols=100 lines=30
参考:mode命令
也可以通过命令设置控制台窗口的名字:
title 贪食蛇启动!!!
参考:title命令
COORD是WindowsAPI中定义的⼀个结构体,表示⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0) 的原点位于缓冲区的顶部左侧单元格。
COORD类型的声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };
GetStdHandle是⼀个WindowsAPI函数。它用于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
例如:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
//PCONSOLE_CURSOR_INFO 是指向CONSOLE_CURSOR_INFO结构的指针,该结构接收有关主机游标
例如:
HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput =GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
这个结构体,包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
• dwSize
,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
• bVisible
,游标的可见性。如果光标可见,则此成员为TRUE
。例:
CursorInfo.bVisible = false; //隐藏控制台光标
设置指定控制台屏幕缓冲区的光标的大小和可见性。
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
例如:
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
例如:
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
SetPos:封装⼀个设置光标位置的函数
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState的返回值是short类型,在上⼀次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
例:
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
SetConsoleTextAttribute 函数用于设置控制台文本的前景(文本)颜色和背景颜色。
BOOL WINAPI SetConsoleTextAttribute(
_In_ HANDLE hConsoleOutput,
_In_ WORD wAttributes
);
//wAttributes: 一个 WORD 类型,表示文本属性,包括前景色、背景色等。
我们可以封装一个函数用于改变程序中的贪吃蛇颜色,例:
设置贪吃蛇颜色的函数:
//设置贪吃蛇颜色
void color(int c)
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
// 保存原始的文本属性
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;//CONSOLE_SCREEN_BUFFER_INFO 是一个结构体,用于存储有关控制台屏幕缓冲区的信息。
GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
originalAttributes = consoleInfo.wAttributes;//originalAttributes为我们在前面定义的一个全局变量,用来保存原来的文本颜色
// 设置新的文本属性
SetConsoleTextAttribute(hConsole, c);
}
恢复原来的文本颜色的函数:
//恢复原始的文本属性
void restoreOriginalColor()
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
// 恢复原始的文本属性
SetConsoleTextAttribute(hConsole, originalAttributes);
}
在上面游戏地图中,我们打印墙体使用宽字符:■,打印蛇使用宽字符□,打印食物使用宽字符●。
注意:普通的字符是占⼀个字节的,这类宽字符是占用2个字节。
下面我们详细介绍一下宽字符相关知识及其的打印。
<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。
在标准中,依赖地区的部分有以下几项:
• 数字量的格式
• 货币量的格式
• 字符集
• 日期和时间的表示形式
通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语言⽀持针对不同的类项进行修改,下面的⼀个宏,指定⼀个类项:
• LC_COLLATE:影响字符串比较函数strcoll() 和strxfrm() 。
• LC_CTYPE:影响字符处理函数的行为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响printf() 的数字格式。
• LC_TIME:影响时间格式strftime() 和wcsftime() 。
• LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语言环境。
char* setlocale (int category, const char* locale);
setlocale 函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第⼀个参数可以是前面说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"
(正常模式)和" "
(本地模式)。
在任意程序执行开始,都会隐藏式执行调用:
setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。
当程序运行起来后想改变地区,就只能显示调用setlocale函数。用" "
作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。
setlocale(LC_ALL, " ");//切换到本地环境
宽字符的字面量必须加上前缀“L”
,否则C语言会把字面量当作窄字符类型处理。前缀“L”
在单引号前面,表示宽字符,对应wprintf()
的占位符为%lc
;在双引号前面,表示宽字符串,对应wprintf()
的占位符为%ls
。例:
#include <stdio.h> #include<locale.h> int main() { setlocale(LC_ALL, ""); wchar_t ch1 = L'●'; wchar_t ch2 = L'小'; wchar_t ch4 = L'■'; printf("%c%c\n", 'a', 'b'); wprintf(L"%lc\n", ch1); wprintf(L"%lc\n", ch2); printf("%c\n", 'z'); wprintf(L"%lc\n", ch4); return 0; }
我选择的是27行,58列(当然也可以根据自己的情况修改)。
蛇身: 蛇的长度是5,连续5个节点。蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中,另外⼀半在墙外的现象,坐标不好对齐。
食物:坐标不能和蛇的身体重合
我们用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标即可,蛇节点结构如下:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
封装⼀个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{
pSnakeNode _pSnake;//指向贪食蛇头结点的指针(蛇身)
pSnakeNode _pFood;//指向食物节点的指针(可将食物就当作蛇身的一部分,吃了后蛇身就变长一节)
int _Score;//总分数
int _FoodWeight;//单个食物分数
int _SleepTime;//每走一步停顿时间
enum DIRECTION _Dir;//获取按键状态描述蛇的方向
enum GAME_STATUS _Status;//游戏的运行状态:正常、按ESC退出、撞墙死、自杀
int _HighScore;// 历史最高分
const char* _FilePath;// 历史最高分记录文件路径
}Snake,*pSnake;
用枚举记录蛇的方向
enum DIRECTION//读取按键状态
{//上、下、左、右
UP = 1,
DOWN,
LEFT,
RIGHT
};
记录游戏状态
enum GAME_STATUS//运行状态
{
OK,//正常运行
END_NORMAL,//按ESC结束
KILL_BY_WALL,//撞墙死
KILL_BY_SELF//自杀
};
主逻辑分为3个过程:
• 游戏开始(GameStart)完成游戏的初始化
• 游戏运行(GameRun)完成游戏运行逻辑的实现
• 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
执行任务:
• 控制台窗口大小的设置
• 控制台窗口名字的设置
• 鼠标光标的隐藏
• 打印欢迎界面
• 创建地图
• 初始化第蛇
• 创建食物
• 读取历史最高分
总逻辑:
//游戏开始,完成游戏的初始化 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); // 读取历史最高分记录文件 FILE* file = fopen(ps->_FilePath, "r"); if (file != NULL) { fscanf(file, "%d", &ps->_HighScore);//从ps->_FilePath文件中读取最高分放到ps->_HighScore种 fclose(file); } }
//打印欢迎界面 void WelComeToGame() { //设置颜色 color(3); SetPos(40, 14);//定位光标 printf("欢迎来到贪食蛇!"); SetPos(40, 25); system("pause");//对程序暂停一下 system("cls");//清空屏幕 SetPos(20, 14); printf("使用 ↑.↓.←.→.分别控制蛇的移动, F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); }
//创建地图 void CreateMap() { //FOREGROUND_RED、FOREGROUND_GREEN 是 Windows API 中定义的一些常量,可以通过按位或操作(|)进行组合使用,以设置多个属性。 //设置墙体颜色 color(FOREGROUND_RED | FOREGROUND_GREEN); //上 SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (i = 1; i < 26; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i < 26; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } //恢复原始的文本属性 restoreOriginalColor(); }
//初始化贪食蛇 void InitSnake(pSnake ps) { pSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) {//初始蛇身给他五个节点 cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake()::malloc()"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; //头插法-----将每个蛇身连接起来 if (ps->_pSnake == NULL) { ps->_pSnake = cur; } else { cur->next = ps->_pSnake; ps->_pSnake = cur; } } //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //恢复原始的文本属性 restoreOriginalColor(); //初始化下贪吃蛇结构中的其他内容 ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_FoodWeight = 10; ps->_SleepTime = 200; ps->_Dir = RIGHT; ps->_HighScore = 0; // 初始化历史最高分 // 检查历史最高分文件是否存在,如果不存在则创建 FILE* file = fopen("highscore.txt", "r"); if (file == NULL)//不存在highscore.txt文件 { // 创建新文件 file = fopen("highscore.txt", "w"); if (file != NULL) { fprintf(file, "%d", 0); // 初始时将历史最高分设置为0 fclose(file); } else { perror("GameStart()::fopen()"); } } else//存在highscore.txt文件 { fclose(file); } ps->_FilePath = "highscore.txt"; // 设置历史最高分记录文件路径 }
//创建食物 void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do {//在墙内寻找生成食物的坐标 x = rand() % 53 + 2;//随机数范围2-54 y = rand() % 25 + 1; } while (x % 2 != 0);//食物的生成坐标要为2的倍数,要不让蛇嵌进墙里咋办,还有蛇只吃到一半食物咋办 //食物已经在墙内了,现在要看它是否与蛇身重叠 pSnakeNode cur = ps->_pSnake; while (cur) {//遍历判断蛇身坐标是否与所寻找的食物坐标重叠 if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } //创建食物节点(可将食物理解成蛇身的一部分) pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreateFood()::mallloc()"); return; } pFood->x = x; pFood->y = y; pFood->next = NULL; ps->_pFood = pFood; //打印食物 //食物颜色 color(FOREGROUND_RED); SetPos(x, y); wprintf(L"%lc", FOOD); //恢复原始的文本属性 restoreOriginalColor(); }
执行任务:
• 打印帮助信息
• 获取键值
• 蛇的移动:
吃掉食物
不吃食物
蛇是否撞墙死
蛇是否自杀
总逻辑:
//游戏的正常运行 void GameRun(pSnake ps) { //打印帮助信息 PrintHelpInfo(); do { //分数时时更新啦 SetPos(64, 10); printf("得分:%5d", ps->_Score); SetPos(64, 11); printf("每个食物分数:%2d", ps->_FoodWeight); SetPos(64, 12); printf("历史最高分:%5d", ps->_HighScore); // 比较并更新历史最高分 if (ps->_Score > ps->_HighScore) { ps->_HighScore = ps->_Score; // 保存历史最高分到文件 FILE* file = fopen(ps->_FilePath, "w"); if (file != NULL) { fprintf(file, "%d", ps->_HighScore);//将新的历史最高分ps->_HighScore写到ps->_FilePath文件中 fclose(file); } } 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))//按ESC退出 { 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(64, 15);
printf("1.不能撞墙,不能咬自己");
SetPos(64, 16);
printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
SetPos(64, 17);
printf("3.F3加速,F4减速");
SetPos(64, 18);
printf("4.ESC-退出,空格-暂停");
SetPos(64, 20);
printf("@摆烂的小z");
}
封装⼀个宏KEY_PRESS,检测按键状态
//定义一个宏用于检测此按键是否被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)
//吃掉食物 void EatFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //恢复原始的文本属性 restoreOriginalColor(); free(ps->_pFood); ps->_Score += ps->_FoodWeight; //创建一个新的食物 CreateFood(ps); } //不吃食物 void NoFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇身 //蛇的颜色 color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); //恢复原始的文本属性 restoreOriginalColor(); free(cur->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->_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; return 1; } cur = cur->next; } } //蛇的移动 void SnakeMove(pSnake ps) { //定义一个pNext用于记录蛇头将要到达的下一个位置节点 pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; //根据按键状态来寻找pNext的坐标位置 switch (ps->_Dir) {//上、下、左、右 case UP: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y - 1; break; case DOWN: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y + 1; break; case LEFT: pNext->x = ps->_pSnake->x - 2; pNext->y = ps->_pSnake->y; break; case RIGHT: pNext->x = ps->_pSnake->x + 2; pNext->y = ps->_pSnake->y; break; } //判断蛇头到达的坐标处是否是食物 if (NextIsFood(ps, pNext)) { //吃掉食物 EatFood(ps, pNext); } else { //不吃食物 NoFood(ps, pNext); } //蛇是否撞墙死 KillByWall(ps); //蛇是否自杀 KillBySelf(ps); }
//游戏结束后的善后处理 void GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->_Status) { case END_NORMAL: printf("您主动退出了游戏"); break; case KILL_BY_SELF: printf("您自杀了"); break; case KILL_BY_WALL: printf("您撞墙了"); break; } //一局游戏结束,释放蛇身的节点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; //释放食物节点 free(ps->_pFood); ps->_pFood = NULL; }
2024.2.26
上面的代码是在VS2022里面写的,在dev c++里运行可能有些问题,因为Dev-C++默认不支持Unicode字符,所以将源文件保存为UTF-8编码就可以了。下面演示一下方法。
无注释版贪吃蛇代码:
#include<stdio.h> #include<locale.h> #include<windows.h> #include<stdbool.h> #include<stdlib.h> #include<time.h> WORD originalAttributes; #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, 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; int _HighScore; const char* _FilePath; }Snake, * pSnake; void GameStart(pSnake ps); void SetPos(short x, short y); void color(int c); void restoreOriginalColor(); void WelComeToGame(); void CreateMap(); void InitSnake(pSnake ps); void GameRun(pSnake ps); void SnakeMove(pSnake ps); int NextIsFood(pSnake ps, pSnakeNode pnext); void EatFood(pSnake ps, pSnakeNode pnext); void NoFood(pSnake ps, pSnakeNode pnext); void KillByWall(pSnake ps); void KillBySelf(pSnake ps); void PrintHelpInfo(); void Pause(); void GameEnd(pSnake ps); void color(int c) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO consoleInfo; GetConsoleScreenBufferInfo(hConsole, &consoleInfo); originalAttributes = consoleInfo.wAttributes; SetConsoleTextAttribute(hConsole, c); } void restoreOriginalColor() { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hConsole, originalAttributes); } void SetPos(short x, short y) { COORD pos = { x, y }; HANDLE hOutput = NULL; hOutput = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hOutput, pos); } void WelComeToGame() { color(3); SetPos(40, 14); printf("欢迎来到贪食蛇!"); SetPos(40, 25); system("pause"); system("cls"); SetPos(20, 14); printf("使用 ↑.↓.←.→.分别控制蛇的移动, F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); } void CreateMap() { color(FOREGROUND_RED | FOREGROUND_GREEN); SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } SetPos(0, 26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } for (i = 1; i < 26; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } for (i = 1; i < 26; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } restoreOriginalColor(); } void InitSnake(pSnake ps) { pSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake()::malloc()"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; if (ps->_pSnake == NULL) { ps->_pSnake = cur; } else { cur->next = ps->_pSnake; ps->_pSnake = cur; } } color(FOREGROUND_GREEN); cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } restoreOriginalColor(); ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_FoodWeight = 10; ps->_SleepTime = 200; ps->_Dir = RIGHT; ps->_HighScore = 0; FILE* file = fopen("highscore.txt", "r"); if (file == NULL) { file = fopen("highscore.txt", "w"); if (file != NULL) { fprintf(file, "%d", 0); fclose(file); } else { perror("GameStart()::fopen()"); } } else { fclose(file); } ps->_FilePath = "highscore.txt"; } 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->_pSnake; while (cur) { if (cur->x == x && cur->y == y) { goto again; } cur = cur->next; } pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreateFood()::mallloc()"); return; } pFood->x = x; pFood->y = y; pFood->next = NULL; ps->_pFood = pFood; color(FOREGROUND_RED); SetPos(x, y); wprintf(L"%lc", FOOD); restoreOriginalColor(); } 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); FILE* file = fopen(ps->_FilePath, "r"); if (file != NULL) { fscanf(file, "%d", &ps->_HighScore); fclose(file); } } 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; color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } restoreOriginalColor(); free(ps->_pFood); ps->_Score += ps->_FoodWeight; CreateFood(ps); } void NoFood(pSnake ps, pSnakeNode pnext) { pnext->next = ps->_pSnake; ps->_pSnake = pnext; color(FOREGROUND_GREEN); pSnakeNode cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); restoreOriginalColor(); free(cur->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->_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; return 1; } cur = cur->next; } } void SnakeMove(pSnake ps) { pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; switch (ps->_Dir) { case UP: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y - 1; break; case DOWN: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y + 1; break; case LEFT: pNext->x = ps->_pSnake->x - 2; pNext->y = ps->_pSnake->y; break; case RIGHT: pNext->x = ps->_pSnake->x + 2; pNext->y = ps->_pSnake->y; break; } if (NextIsFood(ps, pNext)) { EatFood(ps, pNext); } else { NoFood(ps, pNext); } KillByWall(ps); KillBySelf(ps); } void PrintHelpInfo() { SetPos(64, 15); printf("1.不能撞墙,不能咬自己"); SetPos(64, 16); printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动"); SetPos(64, 17); printf("3.F3加速,F4减速"); SetPos(64, 18); printf("4.ESC-退出,空格-暂停"); SetPos(64, 20); printf("@摆烂的小z"); } void Pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) { break; } } } void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(64, 10); printf("得分:%5d", ps->_Score); SetPos(64, 11); printf("每个食物分数:%2d", ps->_FoodWeight); SetPos(64, 12); printf("历史最高分:%5d", ps->_HighScore); if (ps->_Score > ps->_HighScore) { ps->_HighScore = ps->_Score; FILE* file = fopen(ps->_FilePath, "w"); if (file != NULL) { fprintf(file, "%d", ps->_HighScore); fclose(file); } } 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 GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->_Status) { case END_NORMAL: printf("您主动退出了游戏"); break; case KILL_BY_SELF: printf("您自杀了"); break; case KILL_BY_WALL: printf("您撞墙了"); break; } pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; free(ps->_pFood); ps->_pFood = NULL; } void test() { char ch = 0; do { Snake snake = { 0 }; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 18); printf("再来一局?(Y/N):"); ch = getchar(); int c; while ((c = getchar()) != '\n' && c != EOF); } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); } int main() { setlocale(LC_ALL, ""); srand((unsigned int)time(NULL)); test(); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。