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://www.hqwc.cn/news/641999.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VideoComposer: Compositional Video Synthesis with Motion Controllability

decompose videos into three distinct types of conditions: textual conditions, spatial conditions, temperal conditions 条件的内容&#xff1a; a. textual condition: coarse grained visual content and motions, 使用openclip vit-H/14的text encoder b. spatial co…

docker 虚拟化与docker的概念

一、云计算的三种服务模式 laas、pass、saas 1.1 IaaS: Infrastructure-as-a-Service&#xff08;基础设施即服务&#xff09; 第一层叫做IaaS&#xff0c;有时候也叫做Hardware-as-a-Service&#xff0c;几年前如果你想在办公室或者公司的网站上运行一些企业应用&#xff0c…

最大神经系统Hala Point正式亮相,AI算力新标杆诞生!

英特尔宣布推出 Hala Point 前言 就在近日&#xff0c;英特尔公司宣布推出了目前以来最大的神经系统Hala Point。目前看来该系统将用于支持未来的、受大脑启发的AI研究项目&#xff0c;解决与AI模型可持续性相关的挑战等任务上。那么Hala Point究竟有什么魅力呢&#xff1f;我们…

这就叫专业,安防监控领域的可视化效果走一波!

设计安防监控领域的可视化大屏时&#xff0c;需要考虑以下几个方面&#xff1a; 显示实时监控画面&#xff1a; 将监控摄像头的实时画面显示在大屏上&#xff0c;以便实时观察和监控各个区域的情况。可以使用分割屏幕的方式&#xff0c;同时显示多个监控画面。 报警和事件显示…

网络工程师的网络故障排除方法(非常详细)零基础入门到精通,收藏这一篇就够了

网络故障是在日常工作中经常会遇到的问题&#xff0c;尤其是对于那些经常需要和网络打交道的网络工程师们&#xff0c;对于他们而言&#xff0c;如何才能快速进行网络故障的排查、及时解决网络问题呢&#xff1f;接下来我们来讲讲网络排障的基本思路。 一、首先需要熟悉OSI七层…

接口测试和Mock学习路线(上)

一、接口测试和Mock学习路线-第一阶段&#xff1a; 掌握接口测试的知识体系与学习路线掌握面试常见知识点之 HTTP 协议掌握常用接口测试工具 Postman掌握常用抓包工具 Charles 与 Fiddler结合知名产品实现 mock 测试与接口测试实战练习 1.接口协议&#xff1a; 需要先了解 O…

msvcr120.dll文件缺失的相关修复方法分享,有一键修复msvcr120.dll的方式

关于msvcr120.dll文件缺失的缺失&#xff0c;其实网上已经有很多的方法了&#xff0c;但是有很多事不靠谱的&#xff0c;或者是讲得不太清楚的&#xff0c;所以小编觉还是有需要来给大家详细的讲解一下msvcr120.dll文件缺失的相关修复方法&#xff0c;好了废话不多说&#xff0…

后端工程师——Java工程师招聘要求

后端工程师隶属于软件研发工程师,是从事软件开发相关工作人员,其主要职责是 平台设计、接口设计 和 功能实现。作为后端人员,有很多的就业机会,根据你的编程语言掌握情况、个人经验和薪资追求等,可以申请 Java 开发、 PHP 开发、游戏开发人员或 Web 开发人员等职位。 在国…

网址是怎么做成二维码的?扫码查看网站页面怎么做?

现在很多内容都会使用二维码的方式来实现快速的传递&#xff0c;比如现在通过扫码跳转网站、文章、在线视频等等&#xff0c;就是网址转二维码的一种应用。手机扫描二维码自动跳转对应的链接展示内容&#xff0c;有利于网址的快速分享。那么网址二维码制作的方法和步骤是什么样…

服务器基础知识(1)

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;服务器❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 1、什么是服务器 服务器是计算机的一种&#xff0c;它比普通计算机运行更快、负载更高、价格更贵。服务…

C++学习进阶版(二):与文件相关的函数用法

目录 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 &#xff08;2&#xff09;封装成函数 ① 无返回值类型 ② 直接返回读取的内容 2、求文件的行数 3、文件内容读取成一个字符串 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 …

快速排序题目SelectK问题(力扣75.颜色分类、力扣215.数组中的第K个最大元素、面试题17.14最小K个数)

力扣75.颜色分类 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sor…