C语言游戏实现——贪吃蛇

思路讲解

**

贪吃蛇游戏是要求我们要操控一条蛇,在游戏规定的空间之内,进行吃食物,吃到一个就增加蛇身的长度,并且游戏得分加1,如果吃到自己,和碰到墙就算死亡,同时可以增加蛇的速度和减慢蛇的速度,相对应的得分也会增加或减少,这就是游戏规则

**
我们可以创建一个结构体变量来存放游戏的相关信息

//贪吃蛇
struct snake_information
{snakenode* psnake;snakenode* pfood;int sleeptime;int socre;int food_socre;enum GAEM_STATE state;enum DRECTION snake_dir;
};

用一个snkaenode来存放蛇的节点的信息

struct snakenode
{int x;int y;struct snakenode* nodenext;
};
typedef struct snakenode snakenode;

用一个枚举体来存放当前游戏的状态

enum GAEM_STATE
{NORMAL = 1,KILL_BY_SEIF,KILL_BY_WALL,END_NORMAL,OK
};

还有存放蛇当前的方向

enum DRECTION
{UP = 1,DOWM,RIGHT,LEFT};

这就是思路

游戏说明

1.首先我们要在开头列出开始游戏欢迎界面。
2.在开头列出相关操作按键的说明。
3.本贪吃蛇游戏设计的按键为:上下左右方向键作为蛇的移动方向F3和F4作为蛇的移动速度的加快和减慢,空格键是暂停 ESC是退出如果玩完一局要继续玩游戏就按1,不玩就按2.
有了以上的思路,我们大体可以分为几个部分来写,首先就是游戏的欢迎界面,其次就是地图的创建,和蛇身的创建,然后就是对蛇的控制和移动,还有对吃到食物和没有吃到食物的判断,还有撞墙没有,最后对于得分的控制。
在写代码之前我们要先来了解几个win32API函数

什么是WIN32API

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

GetStdHandle函数

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。总的来说就是这个函数要获取控制台的权限,然后要配合其他函数调用使用我们只有获得了操控控制台的权限,我们才能设置控制台的大小,还有光标坐标的改变等。
他的原型是这样的

HANDLE GetStdHandle(DWORD nStdHandle);

这是他的参数
在这里插入图片描述
实例

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

这里我们创建了一个名为HANDLE的结构体指针变量,用来存放GetStdHandle这个函数的返回值

GetConsoleCursorInfo 函数

检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息,说的通俗一点就是用来检查控制台里面的光标信息,在这里插入图片描述
这个就是光标
在这里插入图片描述
有了这两个函数的了解我们就可以写一个可以写一个获取控制台光标的信息的函数了

HANDLE hOutput = NULL;
//先创建一个HANDLE类型的结构体指针变量,因为GetStdHandle的返回类型是HANDLE
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//获取控制台的权限CONSOLE_CURSOR_INFO CursorInfo;//创建一个CONSOLE_CURSOR_INFO的结构体指针GetConsoleCursorInfo(hOutput, &CursorInfo);//调用GetConsoleCursorInfo函数来获取控制台光标大小,和可见信息

CONSOLE_CURSOR_INFO函数

这个结构体,包含有关控制台光标的信息

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

在这里插入图片描述
我们可以用这行代码隐藏光标信息,为什么要隐藏光标那是由于在运行程序的时候那个光标会在那里一直跳动,不美观

CursorInfo.bVisible = false; //隐藏控制台光标

SetConsoleCursorPosition 函数

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置,也就是说这个函数我们可以设置我们的光标在控制台显示的任意位置。
他的语法是这样的

BOOL WINAPI SetConsoleCursorPosition(_In_ HANDLE hConsoleOutput,_In_ COORD  dwCursorPosition
);

实例

# include<stdio.h>
# include<Windows.h>
int main()
{COORD pos = { 20, 34 };HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(hOutput, pos);printf("112233");
}

在这里插入图片描述
有了这些了解,我们可以单独封装一个函数用来设置光标位置,为以后的贪吃蛇做准备。
我们还需要了解一个函数,我们想一想我们要在键盘上控制贪吃蛇的移动和加速减速,还有控制游戏的相关信息,这时候就需要用一个这个函数

getAsyncKeyState 函数

这个函数是可以获取按键情况,将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果要检测一个按键是否被按过,可以检测这个函数的返回值的最低为是不是为1可以用按位与来检测,可以写一个宏函数来判断,因为简单不用在去封装一个函数来占用较大的系统空间

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK)&1) ? 1 : 0 )

这个函数的返回类型与1进行按位与如果是按过了,函数的返回的最低为就是1,与1进行&操作就返回的是1,1为真这个宏函数就返回1,反之为假。(VK)就是按键的虚拟值这个链接就是一些键盘的16进制的虚拟值

本地化设置

为什么要进行本地化设置,因为不同的国家之间有差异
<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式
这里要用到setlocale函数,他的原型是这样的
在这里插入图片描述
setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和" “(本地模式)。在任意程序执⾏开始,都会隐藏式执⾏调⽤:当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。当程序运⾏起来后想改变地区,就只能显⽰调setlocale函数。⽤” "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。这里我们设置本地化的、只是为了打印宽字符汉字

有了以上的API函数的了解下面我们正式进入贪吃蛇游戏的讲解

游戏开始前准备工作

游戏开始菜单

我们先从最简单的游戏开始菜单讲起,这里就要用到我们的API函数用来定位光标的位置和控制台窗口的大小设置

//设置从哪开始打印
void set_pos(short x, short y)
{HANDLE handoutput = NULL;handoutput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(handoutput, pos);
}
void welcome_to_game()
{set_pos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");set_pos(42, 20);system("pause");system("cls");set_pos(25, 14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");set_pos(25, 15);wprintf(L"加速能够得到更高的分数\n");set_pos(42, 20);system("pause");system("cls");
}

wprintf就是用来打印宽字符的格式是

wprintf(L"%l//要打印的内容");

这里我们首先封装了一个设置光标位置的函数,用来在控制台的任意位置来打印所需要的信息,这个welcome_to_game函数将会被gamestart函数给调用
下面这个函数就是我们游戏开始时的准备工作下面会一一讲解

void startgame(snakeinfo* 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);welcome_to_game();init_wall();init_snake(ps);create_food(ps);
}

这里我们这里要关注这个代码

system("mode con cols=100 lines=30");system("title 贪吃蛇");

这个就是设置我们控制台的大小的函数,这个函数在windows.h里面这里我们可以设置我们的控制台的长和宽也就是x和y坐标,但这里的设置都是有讲究的我们要设置宽字符的打印,宽字符占两个字符,那么就需要吧X和Y坐标都设置偶数,然后把控制台的名字改成贪吃蛇在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里就是我们设置好展示到屏幕上的内容。

地图的创建

接下来就创建地图了,我们的地图需要创建成这个样子
在这里插入图片描述
那该怎么创建呢,下面是代码

void init_wall()
{//打印上边框set_pos(0, 0);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}set_pos(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}for (int i = 0; i <= 25; i++){set_pos(0, i);wprintf(L"%lc", WALL);}for (int i = 0; i <= 25; i++){set_pos(56, i);wprintf(L"%lc", WALL);}
}

这里的WALL我们用了一个宏定义来代替方块

#define WALL L'□'

这里我们行和列的创建也是有讲究的我们的x轴必须为偶数y轴可以为奇数,也可以为偶数,这是因为我们打印的是宽字符x要占两个坐标y只占一个坐标
z在这里插入图片描述
可以看看这张图。这样墙体的创建就完成了。

蛇身的创建

蛇身的创建我们可以用单链表来实现,如果不知道单链表可以看看这篇博客单链表详解
可以看看这张图
在这里插入图片描述
这是代码

void init_snake(snakeinfo* ps)
{//头插入snakenode* pcur = NULL;//五个节点for (int i = 0; i < 5; i++){pcur = (snakenode*)malloc(sizeof(snakenode));if (pcur == NULL){perror("init_snake();malloc");exit(1);}pcur->nodenext = NULL;pcur->x = SNAKEBODY_X + 2 * i;//x必须是2的倍数pcur->y = SNAKEBODY_Y;//这里的x,和y我们宏定义成24,5//空链表if (ps->psnake == NULL){ps->psnake = pcur;}//非空链表else{pcur->nodenext = ps->psnake;ps->psnake = pcur;}}pcur = ps->psnake;while (pcur != NULL){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);//BODY宏定义成#define BODY L'●'pcur = pcur->nodenext;}ps->food_socre = 10;//设置游戏食物初始分数ps->socre = 0;//设置当前得分ps->state = OK;//设置当前游戏状态ps->snake_dir = RIGHT;//设置蛇的运行方向ps->sleeptime = 200;//设置游戏的运行速度
}

这样我们就创建出蛇身了,他的原理就是通过创建一个链表存放,X和Y在控制台上的坐标,通过遍历链表设置坐标然后在打印出来
在这里插入图片描述
有了坐标我们就需要创建食物了

创建食物

下面是代码

void create_food(snakeinfo* ps)
{int x = 0;int y = 0;
again:do{x = rand() % (55 - 2 + 1) + 2;y = rand() % (25 - 1 + 1) + 1;} while (x % 2 != 0);snakenode* pcur = ps->psnake;while (pcur){if (x == pcur->x && y == pcur->y){goto again;}pcur = pcur->nodenext;}snakenode* food = (snakenode*)malloc(sizeof(snakenode));if (food == NULL){perror("food();malloc");exit(1);}pfood->x = x;pfood->y = y;pfood->nodenext = NULL;set_pos(x, y);wprintf(L"%lc", FOOD);ps->pfood = food;
}

这里我们创建食物也用了链表来创建,首先我们要在地图上随机生成食物,他只能在我们的地图范围内,还有不能生成在我们蛇的身上所以我们用了一个do,while和goto,agian语句来防止食物随机生成在蛇的身上,然后在把食物的信息存放在snakeinfo结构体里面。
在这里插入图片描述

这里我们的游戏开始准备工作就做完了。下面就要让游戏跑起来了

运行游戏

下面是游戏运行的整体代码,会一一讲解

void game_run(snakeinfo* ps)
{PrintHelpInfo();do{set_pos(64, 10);printf("总分数:%d\n", ps->socre);set_pos(64, 11);printf("当前食物的分数:%2d\n", ps->food_socre);if (KEY_PRESS(VK_UP) && ps->snake_dir != DOWM){ps->snake_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->snake_dir != UP){ps->snake_dir = DOWM;}else if (KEY_PRESS(VK_LEFT) && ps->snake_dir != RIGHT){ps->snake_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->snake_dir != LEFT){ps->snake_dir = 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 = ps->sleeptime - 30;ps->food_socre = ps->food_socre + 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->food_socre > 2){ps->sleeptime = ps->sleeptime + 30;ps->food_socre = ps->food_socre - 2;}}snake_move(ps);Sleep(ps->sleeptime);} while (ps->state == OK);
}

打印帮助信息

先来看看这个函数 PrintHelpInfo();

void PrintHelpInfo()
{set_pos(60, 10);wprintf(L"%ls", L"不能穿墙,不能吃自己");set_pos(60, 15);wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动方向");set_pos(64, 16);wprintf(L"%ls", L"按F3加速,F4减速");set_pos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");}

这里就是各种提示,他会显示在游戏运行时的右边
在这里插入图片描述

游戏运行时按键检测

这里就是检查我们游戏运行的按键情况,如果当前按下的键是向上键,并且当前的蛇的运行状态不是向下的,那么就可以改变蛇的走向,如果方向是向下的,按上键这个是不合法的,左右键也同理,当前是左不能往右,是右不能往左

	if (KEY_PRESS(VK_UP) && ps->snake_dir != DOWM){ps->snake_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->snake_dir != UP){ps->snake_dir = DOWM;}else if (KEY_PRESS(VK_LEFT) && ps->snake_dir != RIGHT){ps->snake_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->snake_dir != LEFT){ps->snake_dir = RIGHT;}

暂停游戏

当我们按下空格键时游戏的状态变成暂停,这里我们用了一个死循环来控制暂停,如果我们要继续进行游戏就在按一下空格键就行了,就打破死循环

void Pause()
{while (1){Sleep(500);if (KEY_PRESS(VK_SPACE)){break;}}
}

加速减速判断

这里我们设置的是按F3和F4来控制游戏的加速减速,我们通过sleep函数的休眠时间来控制蛇的快慢,我们初始的时候sleep设置的是200毫秒,这里设定的最快不能超过80毫秒每按一次减去30毫秒分数加2,
减速也同样的道理,只是用分数来判断,最低不能低于2分每次加30毫秒。

else if (KEY_PRESS(VK_F3))
{//加速if (ps->sleeptime > 80){ps->sleeptime = ps->sleeptime - 30;ps->food_socre = ps->food_socre + 2;}
}
else if (KEY_PRESS(VK_F4))
{//减速if (ps->food_socre > 2){ps->sleeptime = ps->sleeptime + 30;ps->food_socre = ps->food_socre - 2;}
}

蛇的移动

蛇的移动都在这个函数里面

snake_move(ps);

下面是代码

void snake_move(snakeinfo* ps)
{//设置蛇即将的走向snakenode* nextnode = (snakenode*)malloc(sizeof(snakenode));if (nextnode == NULL){perror("snake_move();;malloc");exit(1);}switch (ps->snake_dir){case UP:nextnode->x = ps->psnake->x;nextnode->y = ps->psnake->y - 1;break;case DOWM:nextnode->x = ps->psnake->x;nextnode->y = ps->psnake->y + 1;break;case LEFT:nextnode->x = ps->psnake->x - 2;nextnode->y = ps->psnake->y;break;case RIGHT:nextnode->x = ps->psnake->x + 2;nextnode->y = ps->psnake->y;break;}if (next_is_food(nextnode, ps)){eat_food(nextnode, ps);}else{no_food(nextnode, ps);}kill_by_wall(ps);kill_by_self(ps);
}

这里我们通过创建新的节点来设置蛇的走向然后用一个switch语句来判断当前蛇的方向每走一次判断下一个节点是食物还不是食物,或者撞墙,撞身体死了,X为什么每次要走2歌因为是宽字符。

下一个节点是食物

这里要结合两个函数来看,一个是吃食物,另一个是判断下一个节点是不是食物
判断下一个节点是不是食物

int next_is_food(snakenode* pn, snakeinfo* ps)
{if (ps->pfood->x == pn->x && ps->pfood->y == pn->y)return 1;elsereturn 0;
}

这里就是通过蛇头来判断当前节点是不是食物,如果是就返回1,不是就返回0,通过查重坐标来判断。
吃食物

void eat_food(snakenode* pn, snakeinfo* ps)
{ps->pfood->nodenext = ps->psnake;ps->psnake = ps->pfood;free(pn);pn = NULL;snakenode* pcur = ps->psnake;while (pcur){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->nodenext;}ps->socre = ps->socre + ps->food_socre;create_food(ps);
}

这里就是把食物节点变成蛇头节点,把食物拿来头插入,让食物节点变成蛇头节点,然后在把pn申请的空间给释放了,这里我们开头是申请了空间的来成为蛇的下一个节点,因为这里有食物,食物已经创建了一个节点,所以那个节点我们不需要了就要释放,然后就是遍历链表循环打印蛇身出现在控制台上,最后就是加分了,然后在创建一次食物。

下一个节点不是食物

void no_food(snakenode* pn, snakeinfo* ps)
{//采用头插法pn->nodenext = ps->psnake;ps->psnake = pn;snakenode* pcur = ps->psnake;while (pcur->nodenext->nodenext != NULL){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->nodenext;}set_pos(pcur->nodenext->x, pcur->nodenext->y);printf("  ");free(pcur->nodenext);pcur->nodenext = NULL;
}

这里我们就要把我们开头申请的节点用上了,也是采用头插法,让新节点变成头,然后在循环打印出来为什么while里面要这样写pcur->nodenext->nodenext != NULL可以看看这张图
在这里插入图片描述
我们往前面走了最后一个空间肯定要消除掉所以要打印空字符如果不打印上一次蛇尾就会留下印子,像这样,我们需要打印空格完在释放空间节点
在这里插入图片描述

撞墙

void kill_by_wall(snakeinfo* ps)
{if (ps->psnake->x == 0 || ps->psnake->x == 56 || ps->psnake->y == 0 || ps->psnake->y == 26){ps->state = KILL_BY_WALL;}
}

这里就很简单,就只需要判断蛇头有没有和墙的任何一个坐标重合

自己杀死自己

void kill_by_self(snakeinfo* ps)
{snakenode* pcur = ps->psnake->nodenext;while (pcur != NULL){if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y){ps->state = KILL_BY_SEIF;break;}pcur = pcur->nodenext;}
}

这里相当于我们创建了一个pcur来遍历除开蛇头的剩下的蛇身节点,pcur就像一个探针,一直在蛇身上走,如果头节点与这个pcur相撞,那么就判定自己咬到自己了,然后就把游戏状态改为自己杀死自己,退出游戏。

# 结束游戏

这里就是结束游戏,根据返回的状态来判断游戏是以什么样的情况结束,最后就是释放节点,创建一个临时节点。

void gameend(snakeinfo* ps)
{set_pos(26, 12);switch (ps->state){case NORMAL:printf("正常退出游戏\n");break;case KILL_BY_SEIF:printf("碰到自己身体了\n");break;case KILL_BY_WALL :printf("碰到墙体了\n");break;}snakenode* temp = NULL;snakenode* pcur = ps->psnake;while (pcur){temp = pcur;pcur = pcur->nodenext;free(temp);}set_pos(21, 25);}

主函数

这里就游戏运行的主要逻辑。

void test()
{int ch = 0;do{fflush(stdin);snakeinfo snake = { 0 };startgame(&snake);game_run(&snake);gameend(&snake);set_pos(10, 10);wprintf(L"再来一局:1/2?注意:只能按1次1或者2\n");set_pos(15, 16);scanf("%d", &ch);system("cls");} while(ch == 1);set_pos(0, 27);
}
int main()
{srand((unsigned int)time(NULL));//rand函数要用setlocale(LC_ALL, "");test();return 0;
}

源代码

#define _CRT_SECURE_NO_WARNINGS 1
# include<stdio.h>
# include<locale.h>
# include<assert.h>
# include<stdlib.h>
# include<time.h>
# include<windows.h>
# include<stdbool.h>
# define  SNAKEBODY_X 24
# define  SNAKEBODY_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
enum DRECTION
{UP = 1,DOWM,RIGHT,LEFT};
enum GAEM_STATE
{NORMAL = 1,KILL_BY_SEIF,KILL_BY_WALL,END_NORMAL,OK
};
struct snakenode
{int x;int y;struct snakenode* nodenext;
};
typedef struct snakenode snakenode;
//贪吃蛇
struct snake_information
{snakenode* psnake;snakenode* pfood;int sleeptime;int socre;int food_socre;enum GAEM_STATE state;enum DRECTION snake_dir;
};
typedef struct snake_information snakeinfo;
void set_pos(short x, short y);//
void welcome_to_game();//
void startgame(snakeinfo* ps);//
void init_snake(snakeinfo* ps);//
void init_wall();//
void create_food(snakeinfo* ps);//
void eat_food(snakenode* pn, snakeinfo* ps);//
void snake_move(snakeinfo* ps);//
void no_food(snakenode* pn, snakeinfo* ps);//
int next_is_food(snakenode* pn, snakeinfo* ps);//
void kill_by_wall(snakeinfo* ps);//
void kill_by_self(snakeinfo* ps);//
void game_run(snakeinfo* ps);//
void gameend(snakeinfo* ps);
#define _CRT_SECURE_NO_WARNINGS 1
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)//设置从哪开始打印
void set_pos(short x, short y)
{HANDLE handoutput = NULL;handoutput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(handoutput, pos);
}void init_wall()
{//打印上边框set_pos(0, 0);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}set_pos(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}for (int i = 0; i <= 25; i++){set_pos(0, i);wprintf(L"%lc", WALL);}for (int i = 0; i <= 25; i++){set_pos(56, i);wprintf(L"%lc", WALL);}
}
void welcome_to_game()
{set_pos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");set_pos(42, 20);system("pause");system("cls");set_pos(25, 14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");set_pos(25, 15);wprintf(L"加速能够得到更高的分数\n");set_pos(42, 20);system("pause");system("cls");
}
void init_snake(snakeinfo* ps)
{//头插入snakenode* pcur = NULL;//五个节点for (int i = 0; i < 5; i++){pcur = (snakenode*)malloc(sizeof(snakenode));if (pcur == NULL){perror("init_snake();malloc");exit(1);}pcur->nodenext = NULL;pcur->x = SNAKEBODY_X + 2 * i;pcur->y = SNAKEBODY_Y;//空链表if (ps->psnake == NULL){ps->psnake = pcur;}//非空链表else{pcur->nodenext = ps->psnake;ps->psnake = pcur;}}pcur = ps->psnake;while (pcur != NULL){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->nodenext;}ps->food_socre = 10;ps->socre = 0;ps->state = OK;ps->snake_dir = RIGHT;ps->sleeptime = 200;
}
void create_food(snakeinfo* ps)
{int x = 0;int y = 0;
again:do{x = rand() % (55 - 2 + 1) + 2;y = rand() % (25 - 1 + 1) + 1;} while (x % 2 != 0);snakenode* pcur = ps->psnake;while (pcur){if (x == pcur->x && y == pcur->y){goto again;}pcur = pcur->nodenext;}snakenode* pfood = (snakenode*)malloc(sizeof(snakenode));if (pfood == NULL){perror("food();malloc");exit(1);}pfood->x = x;pfood->y = y;pfood->nodenext = NULL;set_pos(x, y);wprintf(L"%lc", FOOD);ps->pfood = pfood;
}void startgame(snakeinfo* 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);welcome_to_game();init_wall();init_snake(ps);create_food(ps);
}
void PrintHelpInfo()
{set_pos(60, 10);wprintf(L"%ls", L"不能穿墙,不能吃自己");set_pos(60, 15);wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动方向");set_pos(64, 16);wprintf(L"%ls", L"按F3加速,F4减速");set_pos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");}
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}
int next_is_food(snakenode* pn, snakeinfo* ps)
{if (ps->pfood->x == pn->x && ps->pfood->y == pn->y)return 1;elsereturn 0;
}
void eat_food(snakenode* pn, snakeinfo* ps)
{ps->pfood->nodenext = ps->psnake;ps->psnake = ps->pfood;free(pn);pn = NULL;snakenode* pcur = ps->psnake;while (pcur){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->nodenext;}ps->socre = ps->socre + ps->food_socre;create_food(ps);
}
void no_food(snakenode* pn, snakeinfo* ps)
{//采用头插法pn->nodenext = ps->psnake;ps->psnake = pn;snakenode* pcur = ps->psnake;while (pcur->nodenext->nodenext != NULL){set_pos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->nodenext;}set_pos(pcur->nodenext->x, pcur->nodenext->y);//printf("  ");free(pcur->nodenext);pcur->nodenext = NULL;
}
void kill_by_wall(snakeinfo* ps)
{if (ps->psnake->x == 0 || ps->psnake->x == 56 || ps->psnake->y == 0 || ps->psnake->y == 26){ps->state = KILL_BY_WALL;}
}
void kill_by_self(snakeinfo* ps)
{snakenode* pcur = ps->psnake->nodenext;while (pcur != NULL){if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y){ps->state = KILL_BY_SEIF;break;}pcur = pcur->nodenext;}
}
void snake_move(snakeinfo* ps)
{//设置蛇即将的走向snakenode* nextnode = (snakenode*)malloc(sizeof(snakenode));if (nextnode == NULL){perror("snake_move();;malloc");exit(1);}switch (ps->snake_dir){case UP:nextnode->x = ps->psnake->x;nextnode->y = ps->psnake->y - 1;break;case DOWM:nextnode->x = ps->psnake->x;nextnode->y = ps->psnake->y + 1;break;case LEFT:nextnode->x = ps->psnake->x - 2;nextnode->y = ps->psnake->y;break;case RIGHT:nextnode->x = ps->psnake->x + 2;nextnode->y = ps->psnake->y;break;}if (next_is_food(nextnode, ps)){eat_food(nextnode, ps);}else{no_food(nextnode, ps);}kill_by_wall(ps);kill_by_self(ps);
}
void game_run(snakeinfo* ps)
{PrintHelpInfo();do{set_pos(64, 10);printf("总分数:%d\n", ps->socre);set_pos(64, 11);printf("当前食物的分数:%2d\n", ps->food_socre);if (KEY_PRESS(VK_UP) && ps->snake_dir != DOWM){ps->snake_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->snake_dir != UP){ps->snake_dir = DOWM;}else if (KEY_PRESS(VK_LEFT) && ps->snake_dir != RIGHT){ps->snake_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->snake_dir != LEFT){ps->snake_dir = 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 = ps->sleeptime - 30;ps->food_socre = ps->food_socre + 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->food_socre > 2){ps->sleeptime = ps->sleeptime + 30;ps->food_socre = ps->food_socre - 2;}}snake_move(ps);Sleep(ps->sleeptime);} while (ps->state == OK);
}
void gameend(snakeinfo* ps)
{set_pos(26, 12);switch (ps->state){case NORMAL:printf("正常退出游戏\n");break;case KILL_BY_SEIF:printf("碰到自己身体了\n");break;case KILL_BY_WALL :printf("碰到墙体了\n");break;}snakenode* temp = NULL;snakenode* pcur = ps->psnake;while (pcur){temp = pcur;pcur = pcur->nodenext;free(temp);}set_pos(21, 25);}void test()
{int ch = 0;do{fflush(stdin);snakeinfo snake = { 0 };startgame(&snake);game_run(&snake);gameend(&snake);set_pos(10, 10);wprintf(L"再来一局:1/2?注意:只能按1次1或者2\n");set_pos(15, 16);scanf("%d", &ch);system("cls");} while(ch == 1);set_pos(0, 27);
}
int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");test();return 0;
}

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

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

相关文章

Python开源项目周排行 2024年第8周

#2024年第8周2024年4月12日1llama3当知无愧AI LLM领域当红炸子鸡&#xff01;Llama 3 是由 Meta AI 开发的大型语言模型 (LLM)&#xff0c;于 2024 年 4 月发布。它基于 Megatron-Turing NLG 模型架构&#xff0c;并在超过 15 万亿个标记的公开可用数据上进行了预训练&#xff…

算法训练营day15

一、层序遍历 参考链接7.2 二叉树遍历 - Hello 算法 (hello-algo.com) 层序遍历本质上属于广度优先遍历&#xff0c;也称广度优先搜索&#xff0c; BFS通常借助队列的先入先出的特性实现 参考链接102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 像这种较为…

百度GL地图实现选点获取经纬度并且地址逆解析

index.html引入 <script src"https://api.map.baidu.com/api?typewebgl&v1.0&ak你的ak"></script>组件使用 <el-input:disabled"[详情].includes(title)"v-model"formData.site"placeholder""><templat…

【行为型模型】迭代器模式

一、迭代器模式概述 迭代器模式定义&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露其内部的表示。把游走的任务放在送代器上&#xff0c;而不是聚合上。这样简化了聚含的接口和实现,也让责任各得其所。(对象行为型) 迭代器模式的优缺点&…

virtualbox 网络设置实现主机和虚拟机互相访问

前言 一般来说&#xff0c;virtualbox 虚拟机的上网模式是 NAT。这样虚拟机可以上网并访问宿主机&#xff0c;但宿主机无法访问虚拟机&#xff0c;也无法 ping 通。下面介绍双网卡模式&#xff0c;实现虚拟机和宿主机能够互相访问 ping 通。 双网卡模式 进入虚拟机的网络设置…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简单步…

C语言—深度剖析函数指针,函数指针数组

我们先来看一段代码 #include <stdio.h> void test() {printf("hehe\n"); } int main() {printf("%p\n", test);printf("%p\n", &test);return 0; }输出的是两个地址&#xff0c;这两个地址是 test 函数的地址。 那我们的函数的地址…

杰理695的UI模式LED灯控制

UI模式LED灯修改每个模式对应的LED灯闪烁修改在ui_normal_status_deal(u8 *status, u8 *power_status, u8 ui_mg_para)

关系型数据库中primary key和foreign key、索引的作用

文章目录 一、关系型数据库中主键(primary key)和外键(foreign key)的概念。二、MySQL索引的作用(索引的优缺点)一、关系型数据库中主键(primary key)和外键(foreign key)的概念。 二、MySQL索引的作用(索引的优缺点) MySQL索引是一种数据结构,它可以提高查询性能…

MATLAB初学者入门(13)—— 遗传算法

遗传算法是一种受自然选择和遗传学启发的搜索启发式算法&#xff0c;用于解决优化和搜索问题。它模拟了自然界中生物的进化过程&#xff0c;包括基因的选择、交叉&#xff08;杂交&#xff09;和变异。 MATLAB 提供了一个方便的工具箱&#xff0c;即全局优化工具箱&#xff0c;…

网卡技术解密:理解网卡背后的原理

✍✍在这个信息爆炸的时代&#xff0c;网卡承载着无数数据的流动&#xff0c;是我们日常生活和工作不可或缺的一部分。但是&#xff0c;您是否曾经好奇过&#xff0c;这些小小的硬件是如何在瞬息万变的网络世界中稳定地发挥作用的呢&#xff1f; 想象一下&#xff0c;每当我们…

计算机缺少msvcp120.dll如何解决,7种详细的修复方法分享

msvcr120.dll文件是微软Visual C运行时库的一部分&#xff0c;版本号为12.0。这个DLL文件包含了许多用于支持在Windows上运行的应用程序的重要函数和组件。它是确保某些程序能够正确执行的关键组成部分&#xff0c;特别是那些使用C编写或依赖于某些Microsoft库的程序。 当用户…

家用充电桩有必要买21KW交流充电桩吗?

随着电动汽车的普及和人们环保出行意识的增强&#xff0c;充电设施的需求日益增长。在选择充电桩时&#xff0c;很多人会考虑到充电速度、功率等因素。而作为交流充电桩中充电效率最高的一种&#xff0c;21KW交流充电桩是否值得购买呢&#xff1f; 从成本角度来看&#xff0c;2…

只需几步,即可享有笔记小程序

本示例是一个简单的外卖查看店铺点菜的外卖微信小程序&#xff0c;小程序后端服务使用了MemFire Cloud&#xff0c;其中使用到的MemFire Cloud功能包括&#xff1a; 其中使用到的MemFire Cloud功能包括&#xff1a; 云数据库&#xff1a;存储外卖微信小程序所有数据表的信息。…

画图的神器及必备的调色和选图工具

大学生研究生论文写作及画图的神器 前言常用的工具集合画图工具配色参考画图神器词云 最后下篇 前言 好久没有更博&#xff0c;来更一下吧。最近刚好被问到平常是用什么来画图的&#xff0c;包括会议论文&#xff0c;各种类型的PPT汇报以及项目报告等等里面的图怎么画好。所以…

YoloV8改进策略:卷积改进|DOConv轻量卷积,即插即用|适用各种场景

摘要 本文使用DOConv卷积&#xff0c;替换YoloV8的常规卷积&#xff0c;轻量高效&#xff0c;即插即用&#xff01;改进方法非常简单。 DO-Conv&#xff08;Depthwise Over-parameterized Convolutional Layer&#xff09;是一种深度过参数化的卷积层&#xff0c;用于提高卷…

用户实践:从 HBase 升级为OceanBase,仟传实现110000 TPS的千亿级KV性能优化

本文作者&#xff1a;仟传网络科技技术专家 刘贵宗 & 肖旺生 一、业务需求及选型背景 仟传网络科技&#xff08;TargetSocial&#xff09;&#xff0c;是国内知名的内容社交平台整合营销服务商&#xff0c;为企业级客户提供高效的KOL&#xff08;关键意见领袖&#xff09;…

互联网大厂ssp面经,数据结构:part1

1. 数组和链表的区别是什么&#xff1f; a. 数组是一种线性数据结构&#xff0c;存储在连续的内存块中&#xff0c;元素可以通过索引直接访问。 b. 链表是由节点组成的数据结构&#xff0c;每个节点包含数据和指向下一个节点的指针。 2. 数组和链表的的优缺点是什么&#xff…

旅游网站制作流程

旅游网站制作流程是一个较复杂的过程&#xff0c;因为它需要结合市场调研、用户需求、内容构建、技术开发等多个方面。在这篇文章中&#xff0c;我将简单介绍一下旅游网站的制作流程&#xff0c;大致分为以下步骤。 第一步&#xff1a;市场调研 在制作旅游网站前&#xff0c;我…

【机器学习】分类与预测算法的评价与优化

以实际案例解析F1值与P-R曲线的应用 一、分类算法与性能评价的重要性二、F1值与P-R曲线的概念与意义三、实例解析&#xff1a;以垃圾邮件检测为例四、代码实现与结果分析五、结论与展望 在数据驱动的时代&#xff0c;机器学习算法以其强大的数据处理和分析能力&#xff0c;成为…