贪吃蛇游戏C语言破解:成为编程高手的必修课!

                                                                                个人主页:秋风起,再归来~

                                                                                文章专栏:C语言实战项目                              

                                                                        个人格言:悟已往之不谏,知来者犹可追

                                                                                        克心守己,律己则安!

1、游戏效果演示

贪吃蛇游戏效果演示

2、win32 API介绍

这里实现贪吃蛇会使⽤到的⼀些Win32 API知识,接下来我介绍一下。

2.1 Win32 API

Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便 称之为Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows  32位平台的应⽤程序编程接⼝。

2.2 控制台程序

平常我们运⾏起来的⿊框程序其实就是控制台程序 我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:

设置控制台窗⼝的⼤⼩,30⾏,100列

mode con cols=100 lines=30

也可以通过命令设置控制台窗⼝的名字:

title 贪吃蛇

 这些能在控制台窗⼝执⾏的命令,也可以调⽤C语⾔函数system来执⾏。例如

#include<stdlib.h>
int main()
{//设置控制台相关属性//要包含头文件<stdlib.h>system("mode con cols=100 lines=25");system("title 贪吃蛇");//getchar();system("pause");return 0;
}

2.3 控制台屏幕上的坐标COORD

COORD是WindowsAPI中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0)的原点位于缓冲区的顶部左侧单元格。

COORD类型的声明:
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

 默认光标的位置~

//修改光标位置:COORD是win32自定义的关于光标在控制台的位置的结构体类型
COORD pos = { 10,20 };

2.4 GetStdHandle

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标 准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备

HANDLE GetStdHandle(DWORD nStdHandle);
//获得标准输出设备的句柄
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);

2.5 GetConsoleCursorInfo

GetConsoleCursorInfo检索有关指定控制台屏幕缓冲区的光标⼤⼩可⻅性的信息

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);//获取控制台光标信息 

2.5.1 CONSOLE_CURSOR_INFO

  CONSOLE_CURSOR_INFO是win32自定义的一个结构体类型,里面包含了光标的比例和可见度

typedef struct _CONSOLE_CURSOR_INFO {DWORD  dwSize;BOOL   bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

• dwSize,由光标填充的字符单元格的百分⽐。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。

• bVisible,游标的可⻅性。如果光标可⻅,则此成员为TRUE。 

2.6 SetConsoleCursorInfo

设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

示例:

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<stdbool.h>
int main()
{//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//CONSOLE_CURSOR_INFO是win32自定义的一个结构体类型,里面包含了光标的比例和可见度CONSOLE_CURSOR_INFO cursorInfo = { 0 };//获得光标信息GetConsoleCursorInfo(houtput, &cursorInfo);//获得光标信息GetConsoleCursorInfo(houtput, &cursorInfo);//修改控制台的光标信息cursorInfo.dwSize = 100;//比例cursorInfo.bVisible = false;//可见度//设置光标的信息SetConsoleCursorInfo(houtput, &cursorInfo);system("pause");return 0;
}

光标被隐藏啦!   

2.7 SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);

示例: 

int main()
{//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//修改光标位置:COORD是win32自定义的关于光标在控制台的位置的结构体类型COORD pos = { 10,20 };//设置光标位置SetConsoleCursorPosition(houtput, pos);getchar();//system("pause");return 0;
}

SetPos:封装⼀个设置光标位置的函数

//把设置光标位置的操作封装成为一个函数
void _SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//修改光标位置:COORD是win32自定义的关于光标在控制台的位置的结构体类型COORD pos = { x,y };//设置光标位置SetConsoleCursorPosition(houtput, pos);
}

2.8 GetAsyncKeyState

获取按键情况,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 )

虚拟键码 (Winuser.h) - Win32 apps

3、<locale.h>本地化

如上图游戏效果演示所示:

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★ 普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。

这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。

C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语 国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符 号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符 号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体 系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪 怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希 伯来语编码中却代表了字⺟Gimel,在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。 

后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。在标准中,依赖地区的部分有以下⼏项:

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

3.1 类项

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:

• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。

• LC_CTYPE:影响字符处理函数的⾏为。

• LC_MONETARY:影响货币格式。

• LC_NUMERIC:影响 printf() 的数字格式。

• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。

• LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境

3.2 setlocale函数

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, " ");//切换到本地环境 
#include<locale.h>
int main()
{//C语言标准模式char* ret = setlocale(LC_ALL, NULL);printf("%s\n", ret);//本地化之后的模式ret = setlocale(LC_ALL, "");printf("%s\n", ret);return 0;
}

3.3宽字符的打印

那如果想在屏幕上打印宽字符,怎么打印呢?

宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。

前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。

#include<locale.h>
int main()
{//本地化setlocale(LC_ALL, "");//打印宽字符wchar_t ch1 = L'●';wchar_t ch2 = L'□';wchar_t ch3 = L'★';wprintf(L"%lc\n", ch1);wprintf(L"%lc\n", ch2);wprintf(L"%lc\n", ch3);return 0;
}

从输出的结果来看,我们发现⼀个普通字符占⼀个字符的位置 但是打印⼀个汉字字符,占⽤2个字符的位置,那么我们如果 要在贪吃蛇中使⽤宽字符,就得处理好地图上坐标的计算。

 4、游戏实现的整体思路

1. (第一界面)通过贪吃蛇游戏的视频演示我们可以知道我们首先要在屏幕上打印欢迎界面

2. (第二界面)然后再在屏幕上打印帮助手册

3. (第三界面)接着在屏幕上打印游戏运行时的界面

4. (第四界面)游戏结束后再在屏幕上打印结束原因和最后得分

5. (第五界面)打印玩家是否想在来一局

 4.1 我们要用到的数据结构

实现这些步骤前,我们先要思考并完成我们要用到的数据结构

在游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信 息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏,

所以蛇节点结构如下:

//蛇身的节点类型
typedef struct SnakeNode
{//蛇的身体的每个节点的坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:

//创建一个结构体类型来维护蛇的各种信息
typedef struct Snake
{pSnakeNode _pSnakeHead;//维护蛇头的指针pSnakeNode _pFood;//维护食物的指针enum DIRECTION _dri;//维护蛇的方向enum GAME_STATE _state;//维护蛇的状态int _score;//维护当前游戏的总分int _foodWeight;//维护一个食物默认的分数int _sleepTime;//维护蛇的速度
}Snake,*pSnake;

 蛇的⽅向,可以⼀⼀列举,使⽤枚举:

//枚举蛇的四种方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};

游戏状态,可以⼀⼀列举,使⽤枚举:

//枚举四种游戏状态
enum GAME_STATE
{OK,//正常运行END_NORMAL,//正常退出KILL_BY_WALL,//撞到墙了KILL_BY_SELF//撞到自己了
};

 4.2 封装三个文件

1.test.h(游戏测试逻辑)

2. snake.h(所有头文件的包含和函数的声明)

3 snake.c(函数的具体实现)

4.3 test.c

这里是游戏的整体逻辑,具体实现在snake.c中

#define _CRT_SECURE_NO_WARNINGS
#include"snake.h"//完成游戏的测试逻辑
void test()
{int ch = 0;do{//创建贪吃蛇Snake snake = { 0 };pSnake ps = &snake;//初始化游戏//0. 光标隐藏//1. 打印欢迎界面//2. 绘制地图//3. 蛇身初始化//4. 食物初始化GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏(善后工作)GameEnd(&snake);/*SetPos(62, 18);printf("您要再来一局吗?(Y/N):");*/ch = getchar();//清理缓冲区里面的内容getchar(); system("cls");} while (ch == 'Y' || ch == 'y');
}int main()
{//设置适配本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}

4.4 snake.h

我先把会用到的头文件,结构类型和函数声明放到这里(看完这些之后后面的代码就更容易看懂)

下面有很详细的注释,先不需要知道函数具体怎么实现,这里只需要知道它们的功能即可。

当然,还有一些函数并没在这里声明(这些函数大多都是为实现某个功能为具体的另一个函数(被这个函数调用)服务却没有在整体上都用到)

#pragma once
//包含所需要的头文件
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<locale.h>
#include<stdbool.h>
#include<time.h>
#include<conio.h>#define WALL L'□'
#define POS_X 24
#define POS_Y 5
#define BODY L'●'
#define FOOD L'★'
//定义宏判断键盘上的按键是否被按过
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)//类型的声明//枚举蛇的四种方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//枚举四种游戏状态
enum GAME_STATE
{OK,//正常运行END_NORMAL,//正常退出KILL_BY_WALL,//撞到墙了KILL_BY_SELF//撞到自己了
};//蛇身的节点类型
typedef struct SnakeNode
{//蛇的身体的每个节点的坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
//typedef struct SnakeNode* pSnakeNode;//创建一个结构体类型来维护蛇的各种信息
typedef struct Snake
{pSnakeNode _pSnakeHead;//维护蛇头的指针pSnakeNode _pFood;//维护食物的指针enum DIRECTION _dri;//维护蛇的方向enum GAME_STATE _state;//维护蛇的状态int _score;//维护当前游戏的总分int _foodWeight;//维护一个食物默认的分数int _sleepTime;//维护蛇的速度
}Snake,*pSnake;//函数的声明//游戏初始化
void GameStart(pSnake ps);//定位光标位置
void SetPos(int x, int y);//打印地图
void CreatMap();//初始化蛇身
void InitSnake(pSnake ps);//食物初始化
void CreateFood(pSnake ps);//游戏运行
void GameRun(pSnake ps);//游戏结束(善后工作)
void GameEnd(pSnake ps);//蛇走一步
void SnakeMove(pSnake ps);//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物
void NoFood(pSnake ps,pSnakeNode pn);//检查是否撞墙
void KillByWall(pSnake ps);//检查是否撞到自己
void KillBySelf(pSnake ps);//检测是否有按键被按下
void KeyFun();

 4.5 GameStart(游戏初始化)

1.SetPos(定位光标位置)

//把设置光标位置的操作封装成为一个函数
void SetPos(int x, int y)
{//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//修改光标位置:COORD是win32自定义的关于光标在控制台的位置的结构体类型COORD pos = { x,y };//设置光标位置SetConsoleCursorPosition(houtput, pos);
}

2.游戏初始化逻辑 

//游戏初始化
void GameStart(pSnake ps)
{//0. 先设置窗口的大小再隐藏光标system("mode con cols=150 lines=40");system("title 贪吃蛇");//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//CONSOLE_CURSOR_INFO是win32自定义的一个结构体类型,里面包含了光标的比例和可见度CONSOLE_CURSOR_INFO cursorInfo = { 0 };//获得光标信息GetConsoleCursorInfo(houtput, &cursorInfo);//修改控制台的光标信息cursorInfo.dwSize = 100;//比例cursorInfo.bVisible = false;//可见度//设置光标的信息SetConsoleCursorInfo(houtput, &cursorInfo);//1. 打印欢迎界面和游戏功能介绍WelcomeToGame();//2. 绘制地图CreatMap();//3. 蛇身初始化InitSnake(ps);//4. 食物初始化CreateFood(ps);
}

 4.5.1 WelcomeToGame(打印欢迎界面)

//打印欢迎界面
void WelcomeToGame()
{//第一界面(欢迎界面)SetPos(62, 14);printf("欢迎来到贪吃蛇小游戏!");SetPos(64, 16);system("pause");system("cls");//清理屏幕//第二界面(游戏功能介绍)SetPos(50, 12);printf("1、用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速!");SetPos(50, 13);printf("2、加速将可以得到更高的分数!");SetPos(65, 15);system("pause");system("cls");//清理屏幕
}

4.5.2 CreatMap(绘制地图)

//打印地图
void CreatMap()
{int i = 0;for (int i = 0; i <= 100; i += 2){wprintf(L"%lc", WALL);}SetPos(0, 36);for (int i = 0; i <= 100; i += 2){wprintf(L"%lc", WALL);}for (int i = 1; i <= 36; i ++){SetPos(0, i);wprintf(L"%lc", WALL);}for (int i = 1; i <= 36; i++){SetPos(100, i);wprintf(L"%lc", WALL);}
}

4.5.3 InitSnake(蛇身初始化)

//初始化蛇身
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;//创建5个蛇身的节点for (int i = 1; i <= 5; i++){cur = CreatSnakeNode();cur->next = NULL;cur->x = POS_X + i * 2;cur->y = POS_Y;//将5个蛇身节点串起来if (ps->_pSnakeHead == NULL){//蛇头为空就直接插入ps->_pSnakeHead = cur;}else//头插{cur->next = ps->_pSnakeHead;ps->_pSnakeHead = cur;}}cur = ps->_pSnakeHead;while (cur){SetPos(cur->x,cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_dri = RIGHT;//方向默认向右ps->_foodWeight = 10;ps->_score = 0;ps->_sleepTime = 200;ps->_state = OK;
}

4.5.4 CreateFood(食物初始化)

//初始化食物
void CreateFood(pSnake ps)
{//先随机生成食物的坐标int x = 0;int y = 0;
again://生成的位置必须在地图内部do{x = rand() % 97 + 2;y = rand() % 35 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_pSnakeHead;//判断食物位置是否与蛇身重叠while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//打印食物SetPos(x, y);wprintf(L"%lc", FOOD);pSnakeNode food = CreatSnakeNode();food->x = x;food->y = y;food->next = NULL;//将食物节点放到ps中维护起来ps->_pFood = food;
}

4.6 GameRun(游戏运行)

1.检测按键是否被按过

//定义宏判断键盘上的按键是否被按过
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)

2.检测是否有按键被按下

//检测是否有按键被按下
void KeyFun()
{while (_kbhit()){//使用_getch()获取按下的键,不阻塞程序int key = _getch();}
}

3.游戏暂停 

//游戏暂停
void Pause()
{while(1){if (KEY_PRESS(VK_SPACE)){break;}Sleep(200);KeyFun();}
}

游戏运行逻辑 

//游戏运行
void GameRun(pSnake ps)
{PrintHelpInfo();do {SetPos(104, 12);printf("按空格键开始游戏!");SetPos(104, 13);printf("您当前的总分是%3d", ps->_score);SetPos(104, 14);printf("当前每个食物的总分是%3d", ps->_foodWeight);if (KEY_PRESS(VK_UP) && ps->_dri != DOWN){ps->_dri = UP;//上}else if (KEY_PRESS(VK_DOWN) && ps->_dri != UP){ps->_dri = DOWN;//下}	else if(KEY_PRESS(VK_LEFT) && ps->_dri != RIGHT){ps->_dri = LEFT;//左}else if(KEY_PRESS(VK_RIGHT) && ps->_dri != LEFT){ps->_dri = RIGHT;//右}else if (KEY_PRESS(VK_SPACE) ){Pause();//暂停}else if (KEY_PRESS(VK_ESCAPE)){ps->_state = END_NORMAL;//正常退出}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleepTime>80){ps->_sleepTime -= 30;ps->_foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_foodWeight>2){ps->_sleepTime += 30;ps->_foodWeight -= 2;}}//走一步SnakeMove(ps);//每走一步休息一下Sleep(ps->_sleepTime);KeyFun();} while (ps->_state == OK);
}

 4.6.1 PrintHelpInfo(右侧打印帮助手册)

//打印帮助手册
void PrintHelpInfo()
{SetPos(104, 16);printf("--------------------------------------------");SetPos(104, 17);printf("|1、不能撞墙,不能咬到自己!               |");SetPos(104, 18);printf("|2、用 ↑ . ↓ . ← . → 分别控制蛇的移动! |");SetPos(104, 19);printf("|3、F3为加速,F4为减速!                   |");SetPos(104, 20);printf("|4、按Esc退出游戏,按空格暂停游戏 !        |");SetPos(104, 21);printf("--------------------------------------------");SetPos(60, 17);
}

  4.6.2 SnakeMove(蛇走一步)

1.蛇走一步逻辑

//蛇走一步
void SnakeMove(pSnake ps)
{//创建一个节点来记录蛇头的下一个位置pSnakeNode nextHead = CreatSnakeNode();nextHead->next = NULL;switch (ps->_dri){case UP:nextHead->x = ps->_pSnakeHead->x;nextHead->y = ps->_pSnakeHead->y - 1;break;case DOWN:nextHead->x = ps->_pSnakeHead->x;nextHead->y = ps->_pSnakeHead->y +1;break;case LEFT:nextHead->y = ps->_pSnakeHead->y;nextHead->x = ps->_pSnakeHead->x - 2;break;case RIGHT:nextHead->y = ps->_pSnakeHead->y;nextHead->x = ps->_pSnakeHead->x + 2;break;default:break;}//判断下一个节点是不是食物if((ps->_pFood->x == nextHead->x) && (ps->_pFood->y == nextHead->y)){//下一个是食物那就吃掉食物EatFood(nextHead, ps);}else{//下一个位置不是食物NoFood(ps, nextHead);}//KillByWall(ps);KillBySelf(ps);
}

 2. EatFood(吃掉食物)

//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{//头插吃掉食物ps->_pFood->next = ps->_pSnakeHead;ps->_pSnakeHead = ps->_pFood;ps->_score += ps->_foodWeight;//分数增加//打印蛇身pSnakeNode cur = ps->_pSnakeHead;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//释放刚刚malloc的位置free(pn);pn = NULL;//食物被吃掉了,那就再创建一个食物CreateFood(ps);
}

3. NoFood往前走一步(不是食物)

//下一个位置不是食物
void NoFood(pSnake ps,pSnakeNode pn)
{//头插pn->next = ps->_pSnakeHead;ps->_pSnakeHead = pn;pSnakeNode cur = ps->_pSnakeHead;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);cur->next = NULL;
}

 4. KillByWall(检测是否撞墙)

//检查是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnakeHead->x == 0|| ps->_pSnakeHead->x == 98|| ps->_pSnakeHead->y == 0|| ps->_pSnakeHead->y == 36){ps->_state = KILL_BY_WALL;return;}return;
}

5.  KillBySelf(检测是否撞到自己)

//检查是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnakeHead->next;while (cur){if ((cur->x == ps->_pSnakeHead->x) &&(cur->y == ps->_pSnakeHead->y)){ps->_state = KILL_BY_SELF;return;}cur = cur->next;}return;
}

4.7 GameEnd(游戏善后)

//游戏善后
void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnakeHead;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}system("cls");SetPos(62, 16);switch (ps->_state){case END_NORMAL:printf("您主动退出游戏!\n");break;case KILL_BY_WALL:printf("您撞墙了!\n");break;case KILL_BY_SELF:printf("您撞到自己了!\n");break;}SetPos(62, 17);printf("您最终的成绩是:%d", ps->_score);Sleep(2000);KeyFun();system("cls");SetPos(62, 18);printf("您要再来一局吗?(Y/N):");
}

 4.8 snake.c(完整代码)

#define _CRT_SECURE_NO_WARNINGS
#include"snake.h"//检测是否有按键被按下
void KeyFun()
{while (_kbhit()){//使用_getch()获取按下的键,不阻塞程序int key = _getch();}
}//把设置光标位置的操作封装成为一个函数
void SetPos(int x, int y)
{//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//修改光标位置:COORD是win32自定义的关于光标在控制台的位置的结构体类型COORD pos = { x,y };//设置光标位置SetConsoleCursorPosition(houtput, pos);
}//打印地图
void CreatMap()
{int i = 0;for (int i = 0; i <= 100; i += 2){wprintf(L"%lc", WALL);}SetPos(0, 36);for (int i = 0; i <= 100; i += 2){wprintf(L"%lc", WALL);}for (int i = 1; i <= 36; i ++){SetPos(0, i);wprintf(L"%lc", WALL);}for (int i = 1; i <= 36; i++){SetPos(100, i);wprintf(L"%lc", WALL);}
}//打印欢迎界面
void WelcomeToGame()
{//第一界面(欢迎界面)SetPos(62, 14);printf("欢迎来到贪吃蛇小游戏!");SetPos(64, 16);system("pause");system("cls");//清理屏幕//第二界面(游戏功能介绍)SetPos(50, 12);printf("1、用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速!");SetPos(50, 13);printf("2、加速将可以得到更高的分数!");SetPos(65, 15);system("pause");system("cls");//清理屏幕
}//创建1个蛇身的节点
pSnakeNode CreatSnakeNode()
{pSnakeNode ret = (pSnakeNode)malloc(sizeof(SnakeNode));if (ret == NULL){perror("CreatSnakeNode::fail\n");return NULL;}return ret;
}//初始化蛇身
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;//创建5个蛇身的节点for (int i = 1; i <= 5; i++){cur = CreatSnakeNode();cur->next = NULL;cur->x = POS_X + i * 2;cur->y = POS_Y;//将5个蛇身节点串起来if (ps->_pSnakeHead == NULL){//蛇头为空就直接插入ps->_pSnakeHead = cur;}else//头插{cur->next = ps->_pSnakeHead;ps->_pSnakeHead = cur;}}cur = ps->_pSnakeHead;while (cur){SetPos(cur->x,cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_dri = RIGHT;//方向默认向右ps->_foodWeight = 10;ps->_score = 0;ps->_sleepTime = 200;ps->_state = OK;
}//初始化食物
void CreateFood(pSnake ps)
{//先随机生成食物的坐标int x = 0;int y = 0;
again://生成的位置必须在地图内部do{x = rand() % 97 + 2;y = rand() % 35 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_pSnakeHead;//判断食物位置是否与蛇身重叠while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//打印食物SetPos(x, y);wprintf(L"%lc", FOOD);pSnakeNode food = CreatSnakeNode();food->x = x;food->y = y;food->next = NULL;//将食物节点放到ps中维护起来ps->_pFood = food;
}//打印帮助手册
void PrintHelpInfo()
{SetPos(104, 16);printf("--------------------------------------------");SetPos(104, 17);printf("|1、不能撞墙,不能咬到自己!               |");SetPos(104, 18);printf("|2、用 ↑ . ↓ . ← . → 分别控制蛇的移动! |");SetPos(104, 19);printf("|3、F3为加速,F4为减速!                   |");SetPos(104, 20);printf("|4、按Esc退出游戏,按空格暂停游戏 !        |");SetPos(104, 21);printf("--------------------------------------------");SetPos(60, 17);
}//游戏暂停
void Pause()
{while(1){if (KEY_PRESS(VK_SPACE)){break;}Sleep(200);KeyFun();}
}//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{//头插吃掉食物ps->_pFood->next = ps->_pSnakeHead;ps->_pSnakeHead = ps->_pFood;ps->_score += ps->_foodWeight;//分数增加//打印蛇身pSnakeNode cur = ps->_pSnakeHead;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//释放刚刚malloc的位置free(pn);pn = NULL;//食物被吃掉了,那就再创建一个食物CreateFood(ps);
}//下一个位置不是食物
void NoFood(pSnake ps,pSnakeNode pn)
{//头插pn->next = ps->_pSnakeHead;ps->_pSnakeHead = pn;pSnakeNode cur = ps->_pSnakeHead;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);cur->next = NULL;
}//蛇走一步
void SnakeMove(pSnake ps)
{//创建一个节点来记录蛇头的下一个位置pSnakeNode nextHead = CreatSnakeNode();nextHead->next = NULL;switch (ps->_dri){case UP:nextHead->x = ps->_pSnakeHead->x;nextHead->y = ps->_pSnakeHead->y - 1;break;case DOWN:nextHead->x = ps->_pSnakeHead->x;nextHead->y = ps->_pSnakeHead->y +1;break;case LEFT:nextHead->y = ps->_pSnakeHead->y;nextHead->x = ps->_pSnakeHead->x - 2;break;case RIGHT:nextHead->y = ps->_pSnakeHead->y;nextHead->x = ps->_pSnakeHead->x + 2;break;default:break;}//判断下一个节点是不是食物if((ps->_pFood->x == nextHead->x) && (ps->_pFood->y == nextHead->y)){//下一个是食物那就吃掉食物EatFood(nextHead, ps);}else{//下一个位置不是食物NoFood(ps, nextHead);}//KillByWall(ps);KillBySelf(ps);
}//检查是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnakeHead->x == 0|| ps->_pSnakeHead->x == 98|| ps->_pSnakeHead->y == 0|| ps->_pSnakeHead->y == 36){ps->_state = KILL_BY_WALL;return;}return;
}//检查是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnakeHead->next;while (cur){if ((cur->x == ps->_pSnakeHead->x) &&(cur->y == ps->_pSnakeHead->y)){ps->_state = KILL_BY_SELF;return;}cur = cur->next;}return;
}//游戏初始化
void GameStart(pSnake ps)
{//0. 先设置窗口的大小再隐藏光标system("mode con cols=150 lines=40");system("title 贪吃蛇");//获得标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//CONSOLE_CURSOR_INFO是win32自定义的一个结构体类型,里面包含了光标的比例和可见度CONSOLE_CURSOR_INFO cursorInfo = { 0 };//获得光标信息GetConsoleCursorInfo(houtput, &cursorInfo);//修改控制台的光标信息cursorInfo.dwSize = 100;//比例cursorInfo.bVisible = false;//可见度//设置光标的信息SetConsoleCursorInfo(houtput, &cursorInfo);//1. 打印欢迎界面和游戏功能介绍WelcomeToGame();//2. 绘制地图CreatMap();//3. 蛇身初始化InitSnake(ps);//4. 食物初始化CreateFood(ps);
}//游戏运行
void GameRun(pSnake ps)
{PrintHelpInfo();do {SetPos(104, 12);printf("按空格键开始游戏!");SetPos(104, 13);printf("您当前的总分是%3d", ps->_score);SetPos(104, 14);printf("当前每个食物的总分是%3d", ps->_foodWeight);if (KEY_PRESS(VK_UP) && ps->_dri != DOWN){ps->_dri = UP;//上}else if (KEY_PRESS(VK_DOWN) && ps->_dri != UP){ps->_dri = DOWN;//下}	else if(KEY_PRESS(VK_LEFT) && ps->_dri != RIGHT){ps->_dri = LEFT;//左}else if(KEY_PRESS(VK_RIGHT) && ps->_dri != LEFT){ps->_dri = RIGHT;//右}else if (KEY_PRESS(VK_SPACE) ){Pause();//暂停}else if (KEY_PRESS(VK_ESCAPE)){ps->_state = END_NORMAL;//正常退出}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleepTime>80){ps->_sleepTime -= 30;ps->_foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_foodWeight>2){ps->_sleepTime += 30;ps->_foodWeight -= 2;}}//走一步SnakeMove(ps);//每走一步休息一下Sleep(ps->_sleepTime);KeyFun();} while (ps->_state == OK);
}//游戏善后
void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnakeHead;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}system("cls");SetPos(62, 16);switch (ps->_state){case END_NORMAL:printf("您主动退出游戏!\n");break;case KILL_BY_WALL:printf("您撞墙了!\n");break;case KILL_BY_SELF:printf("您撞到自己了!\n");break;}SetPos(62, 17);printf("您最终的成绩是:%d", ps->_score);Sleep(2000);KeyFun();system("cls");SetPos(62, 18);printf("您要再来一局吗?(Y/N):");
}

5、 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2979160.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

vue2+vxe-table实现表格增删改查+虚拟滚动

vue2vxe-table实现表格增删改查虚拟滚动 使用的vxe-table版本&#xff1a;v3.x (vue 2.6 长期维护版) 完整代码 <template><div><vxe-toolbar ref"xToolbar" export :refresh"{query: findList}"><template #buttons><vxe-b…

1W 1.5KVDC 3KVDC 隔离宽范围输入,单、双输出 DC/DC 电源模块——TP2L-1W 系列

TP2L-1W系列是一款高性能、超小型的电源模块&#xff0c;宽范围2&#xff1a;1,4:1输入&#xff0c;输出有稳压和连续短路保护功能&#xff0c;隔离电压为1.5KVDC&#xff0c;3KVDC工作温度范围为–40℃到85℃。特别适合对输出电压的精度有严格要求的地方&#xff0c;外部遥控功…

瑞_Docker(笔记超详细,有这一篇就够了)

文章目录 1 Docker入门1.1 卸载旧版Docker1.2 安装Docker1.2.1 安装Docker的yum库1.2.2 配置Docker的yum源1.2.3 安装Docker1.2.4 启动和校验&#xff08;开机自启&#xff09;1.2.5 配置镜像加速 1.3 使用示例&#xff1a;部署MySQL1.3.1 命令解读 2 Docker基础2.1 常见命令2.…

第二届 eBPF 开发者大会分享回顾 - Pipy 与 eBPF:重塑系统级编程的新范式

四月的西安&#xff0c;春意盎然&#xff0c;这座古城在温暖的春风中更添了几分旖旎风光。第二届 eBPF 开发者大会在西安顺利召开。 本次大会由西安邮电大学主办&#xff0c;主题为“发挥 eBPF 技术力量&#xff0c;提升计算机系统可观测性和性能”&#xff0c;旨在探讨和分享 …

世界读书日 | 开发者必读书单重磅来袭,华为云DTSE专家天团力荐

春色恰如许&#xff0c;读书正当时。 读书&#xff0c;就像解锁一把神秘钥匙&#xff0c;为开发者洞开新世界的大门&#xff0c;赋予他们破译复杂难题的能力、挑战未知领域的勇气。书页翻动间&#xff0c;开发者得以站在巨人的肩膀上&#xff0c;汲取前人经验&#xff0c;积蓄…

比特币之路:技术突破、创新思维与领军人物

比特币的兴起是一段充满技术突破、创新思维和领军人物的传奇之路。在这篇文章中&#xff0c;我们将探讨比特币发展的历程&#xff0c;以及那些在这一过程中发挥重要作用的关键人物。 技术突破与前奏 比特币的诞生并非凭空而来&#xff0c;而是建立在先前的技术储备之上。在密码…

自定义数据 微调CLIP (结合paper)

CLIP 是 Contrastive Language-Image Pre-training 的缩写&#xff0c;是一个擅长理解文本和图像之间关系的模型&#xff0c;下面是一个简单的介绍&#xff1a; 优点&#xff1a; CLIP 在零样本学习方面特别强大&#xff0c;它可以&#xff08;用自然语言&#xff09;给出图像…

lementui el-menu侧边栏占满高度且不超出视口

做了几次老是忘记&#xff0c;这次整理好逻辑做个笔记方便重复利用&#xff1b; 问题&#xff1a;elementui的侧边栏是占不满高度的&#xff1b;但是使用100vh又会超出视口高度不美观&#xff1b; 解决办法&#xff1a; 1.获取到侧边栏底部到视口顶部的距离 2.获取到视口的高…

操作系统:进程间通信 | 管道

目录 1.进程间通信介绍 1.1.简要介绍 1.2.进程间通信的目的 1.3.进程间通信的本质 2.管道 2.1.管道的通信原理 2.2.匿名管道 2.3.命名管道 2.4.基于匿名管道的进程池demo 2.4.1.进程池的相关引入 2.4.2.整体框架的分析 2.4.3.代码的实现 1.进程间通信介绍 1.1.简…

华为认证FAQ | 考试预约、考券购买常见问题

●考试预约常见问题● Q : 如何进行考试预约&#xff1f; A : 登录“华为人才在线官网” >>参考考试预约操作指引在线预约考试>>检查考试预约记录&#xff0c;确认预约成功 (私信获取考试预约操作指引文档&#xff09;。&#xff08;注&#xff1a;非本人预约…

程序员学CFA——数量分析方法(四)

数量分析方法&#xff08;四&#xff09; 常见概率分布基本概念离散型随机变量与连续型随机变量离散型随机变量连续型随机变量 分布函数概率密度函数&#xff08;PDF&#xff09;累积分布函数&#xff08;CDF&#xff09; 离散分布离散均匀分布伯努利分布二项分布定义股价二叉树…

程序的表示、转换与链接:三、运算电路基础

目录 一、整数加减运算理论二、数字逻辑电路基础和整数加减运算部件三、如何启用逻辑电路&#xff1a;从C表达式到逻辑电路四、C语言中的各类运算 一、整数加减运算理论 整数加减运算 无符号整数加减运算&#xff1a;指针、地址等通常被说明为无符号整数&#xff0c;因而在进行…

pycharm远程连接server

1.工具–部署–配置 2.部署完成后&#xff0c;将现有的项目的解释器设置为ssh 解释器。实现在远端开发 解释器可以使用/usr/bin/python3

Opencv_10_自带颜色表操作

void color_style(Mat& image); Opencv_10_自带颜色表操作&#xff1a; void ColorInvert::color_style(Mat& image) { int colormap[] { COLORMAP_AUTUMN, COLORMAP_BONE , COLORMAP_JET , COLORMAP_WINTER, COLORMAP_RAINBOW , COLOR…

Ts支持哪些类型和类型运算(下)

目录 1、条件判断 &#xff08;extends &#xff1f;&#xff09; 2、推导 infer 3、联合 | 4、交叉 & 5、映射类型 1、条件判断 &#xff08;extends &#xff1f;&#xff09; ts里的条件判断&#xff0c;语法为 T extends XXX ? true : false &#xff0c;叫做…

【Qt 学习笔记】Qt常用控件 | 按钮类控件 | Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件 | Check Box的使用及说明 文章编号&#xff…

智能时代 | 合合信息Embedding模型荣获C-MTEB榜单第一

目录 前言 1. MTEB与C-MTEB 2. acge模型的优势 3. Embedding模型应用 4. 大模型发展的关键技术 结语 前言 随着人工智能的不断发展&#xff0c;大语言模型吸引着社会各界的广泛关注&#xff0c;支撑模型应用落地的Embedding模型成为业内的焦点&#xff0c;大模型的发展给…

解放生产力:项目管理软件的神奇作用大揭秘!

对于刚刚进入项目管理领域的新人首先要了解的概念就是项目管理软件是什么&#xff1f;项目管理软件的作用&#xff0c;如今的项目管理软件已经非常成熟&#xff0c;融合了一整套的项目管理理论&#xff0c;在管理项目进度、管理工时、团队协同方面发挥着重要作用。 一、项目管理…

vue 关键字变红

1.html <div v-html"replaceKeywordColor(item.title)" ></div> 2.js //value为搜索框内绑定的值 replaceKeywordColor(val) {if (val?.includes(this.value) && this.value ! ) {return val.replace(this.value,<font color"red&…

游戏黑灰产识别和溯源取证

参考&#xff1a;游戏黑灰产识别和溯源取证 1. 游戏中的黑灰产 1. 黑灰产简介 黑色产业&#xff1a;从事具有违法性活动且以此来牟取利润的产业&#xff1b; 灰色产业&#xff1a;不明显触犯法律和违背道德&#xff0c;游走于法律和道德边缘&#xff0c;以打擦边球的方式为“…