C语言小游戏--贪吃蛇实现

news/2024/10/4 19:40:23/文章来源:https://blog.csdn.net/2301_79761834/article/details/141913977

C语言小游戏--贪吃蛇实现

  • 1.游戏实现背景
  • 2.Win32 API介绍
    • 2.1什么是Win32 API
    • 2.2控制台程序(Console)
    • 2.3控制台屏幕的坐标COORD
    • 2.4GetStdHandle
      • 2.4.1函数语法
      • 2.4.2函数的使用
    • 2.5GetConsoleCursorInfo
      • 2.5.1函数语法
      • 2.5.2函数的使用
    • 2.6CONSOLE_CURSOR_INFO
      • 2.6.1结构体结构
      • 2.6.2结构体的使用
    • 2.7SetConsoleCursorInfo
      • 2.7.1函数语法
      • 2.7.2函数的使用
    • 2.8SetConsoleCursorPosition
      • 2.8.1函数语法
      • 2.8.2函数的使用
      • 2.8.3设置一个光标位置的函数
    • 2.9GetAsyncKeyState
      • 2.9.1函数语法
      • 2.9.2函数的使用
  • 3.地图的实现
    • 3.1<locale.h>本地化
    • 3.2类项
    • 3.3setlocale函数
      • 3.3.1函数说明
      • 3.3.2函数的使用
    • 3.4宽字符的打印
    • 3.5地图的绘制
  • 4.游戏的完整代码实现
    • 4.1游戏开始
    • 4.2游戏运行
    • 4.3游戏结束

1.游戏实现背景

贪吃蛇小游戏在很早以前就已经存在,我们应该也玩过贪吃蛇这个游戏,这一讲实现的贪吃蛇小游戏是基于C语言,以及链表来实现的,目的在于培养代码思维,对前面的知识做出总结

2.Win32 API介绍

本次贪吃蛇的实现需要用到很多关于Win32 API的知识,我们现在来了解一下:

2.1什么是Win32 API

Win32 API是一组用于Windows操作系统的应用程序编程接口,它提供了一组函数,允许程序员访问操作系统的服务(Windows这个多作业系统除了协调程序的运行、分配内存、管理资源之外,同时是一个很大的服务中心),如 文件管理、内存管理、进程控制、图形和声音处理等

2.2控制台程序(Console)

平时我们运行起来的黑框就是控制台程序
我们可以使用cmd命令来改变控制台窗口的长宽:长为 30、宽为30

//lines:行  cols:列
mode con cols=30 lines=30

我们还可以改变控制台窗口的名称:

title 测试API程序

这些能在控制台窗口执行的命令,也能够调用C语言system函数来执行:

//控制台程序
#include <stdlib.h>
int main()
{system("mode con cols=30 lines=30");//改变运行窗口的大小system("title 测试API程序");//改变运行窗口的名字return 0;
}

最终效果:
在这里插入图片描述
但是我们会发现:我们设的长和宽分明是相等的,但是为什么控制台窗口的大小不是一个正方形呢?我们来看:

2.3控制台屏幕的坐标COORD

COORD是Windows API中定义的一个结构体,表示一个字符在屏幕缓冲区的一个坐标,坐标的分布如下:
在这里插入图片描述
COORD结构体的定义为:

//COORD结构体定义了x和y坐标
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, * PCOORD;

我们使用这一个结构体也较为简单,假设我们要给一个坐标赋值:

COORD pos = {10, 20};

2.4GetStdHandle

GetStdHandle是一个Windows API函数,用于从一个标准设备(标准输入设备、标准输出设备)中获得一个句柄(我们可以将句柄通俗地理解成:操纵操作系统的把柄),有了这个句柄,我们就可以来操纵操作系统

2.4.1函数语法

既然为函数,就应该存在语法,它的函数定义为:

HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle
);

该函数只有一个参数nStdHandle,对于该参数的介绍为:
在这里插入图片描述
如果函数成功,那么函数将会返回指定设备的句柄

2.4.2函数的使用

#include <windows.h>
int main()
{//函数返回的是一个句柄,那么我们就可以使用一个句柄来接受,我们先创建一个句柄变量HANDLE hOutput = NULL;//获取标准输出的句柄hOutput = GetStdHandle(STD_OUTPUT_HANDLE);return 0;
}

2.5GetConsoleCursorInfo

2.5.1函数语法

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);函数有两个参数:
1.hConsoleOutput:这个是控制台屏幕缓冲区的句柄
2.lpConsoleCursorInfo:它是指向CONSOLE_CURSOR_INFO结构的指针,该结构可以接受有关主机光标的信息
如果该函数成功,会返回非零值,函数失败,会返回零

2.5.2函数的使用

int main()
{//获得一个句柄HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//创建一个光标信息的变量CONSOLE_CURSOR_INFO Cursorinfo;//使用含税获取光标信息GetConsoleCursorInfo(hOutput, &Cursorinfo);return 0;
}

2.6CONSOLE_CURSOR_INFO

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

2.6.1结构体结构

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL  bVisible;
} CONSOLE_CURSOR_INFO, * PCONSOLE_CURSOR_INFO;
结构体中包含两个参数:
1.dwSize:表示的是游标填充字符单元的百分比,该值介于1-100之间
2.bVisible:表示游标的可见性,如果游标可见,那么此成员为True

2.6.2结构体的使用

int main()
{//获得一个句柄HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//创建一个光标信息的变量CONSOLE_CURSOR_INFO Cursorinfo;//使用含税获取光标信息GetConsoleCursorInfo(hOutput, &Cursorinfo);//结构体的使用:打印一下此时控制台光标占填充字符单元的百分比printf("%d", Cursorinfo.dwSize);//25,也就是说此时光标占一个字符单元的百分之25return 0;
}

在这里插入图片描述

2.7SetConsoleCursorInfo

2.7.1函数语法

BOOL WINAPI SetConsoleCursorInfo(_In_       HANDLE              hConsoleOutput,_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
该函数有两个参数:
1.hConsoleOutput:指向控制台屏幕缓冲区的句柄
2.lpConsoleCursorInfo:指向CONSOLE_CURSOR_INFO结构体的指针
该函数如果成功,返回非零值,失败返回零

2.7.2函数的使用

#include <stdbool.h>
int main()
{//获得一个句柄HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//创建一个光标信息的变量CONSOLE_CURSOR_INFO Cursorinfo;//使用含税获取光标信息GetConsoleCursorInfo(hOutput, &Cursorinfo);//函数的使用://我们可以根据我们不同的需求对光标进行修改//1.修改光标大小Cursorinfo.dwSize = 50;//直接修改结构体中的参数然后Set就好了SetConsoleCursorInfo(hOutput, &Cursorinfo);//2.隐藏光标Cursorinfo.bVisible = false;SetConsoleCursorInfo(hOutput, &Cursorinfo);return 0;
}

2.8SetConsoleCursorPosition

这是一个设置光标位置的函数

2.8.1函数语法

BOOL WINAPI SetConsoleCursorPosition(_In_ HANDLE hConsoleOutput,_In_ COORD  dwCursorPosition
);
该函数有两个参数:
1.hConsoleOutput:控制台屏幕缓冲区的句柄
2.dwCursorPosition:指定新光标位置的COORD结构,坐标为屏幕缓冲区字符单元的列和行,坐标必须位于控制台屏幕缓冲区的边界之内
如果函数成功,则返回非零值,函数失败,返回零

2.8.2函数的使用

int main()
{//定义一个光标结构体,标志着需要将光标定位的位置COORD pos = { 15, 30 };//获得句柄HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标位置SetConsoleCursorPosition(hOutput, pos);return 0;
}

此时光标位置就被改变了:
在这里插入图片描述

2.8.3设置一个光标位置的函数

我们在设计贪吃蛇小游戏的过程中,要在不同位置放置食物、提示信息等,所以我们可以封装一个设置光标位置的函数,方便后续使用

//封装一个设置光标位置的函数
void SetPos(short x, short y)
{//函数构造比较简单,只需要使用Set函数即可//1.光标位置结构体COORD pos = { x, y };//2.获得句柄HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//3.设置光标信息SetConsoleCursorPosition(hOutput, pos);
}

2.9GetAsyncKeyState

这个函数是用来获取按键情况的函数,这个在游戏中作用很大

2.9.1函数语法

SHORT GetAsyncKeyState([in] int vKey
);
函数只有一个参数:
vKey:表示虚拟按键代码,每一个按键都有一个对应的值,将值传入就可以判断该按键是否被按着或者被按过
函数的返回值为short类型,在上一次调用该函数后,如果返回的16位的short数据中,最高位为1,说明状态是按下的,如果最低位为1,说明该按键被按过

链接: 虚拟键代码值
我们可以通过上面的一个链接来看按键的代码值为多少

2.9.2函数的使用

//我们可以写一个宏,通过使用宏我们可以更高效地判断按键的状态
#define KEY_STATE(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1: 0 )//如果返回值的最低位为1,表示该按键被按过//检测数字键的实现
int main()
{while(1){if (KEY_STATE(0x30))//0x30表示按键0{printf("0\n");}else if (KEY_STATE(0x31)){printf("1\n");}else if (KEY_STATE(0x32)){printf("2\n");}else if (KEY_STATE(0x33)){printf("3\n");}else if (KEY_STATE(0x34)){printf("4\n");}else if (KEY_STATE(0x35)){printf("5\n");}else if (KEY_STATE(0x36)){printf("6\n");}else if (KEY_STATE(0x37)){printf("7\n");}else if (KEY_STATE(0x38)){printf("8\n");}else if (KEY_STATE(0x39)){printf("9\n");}}return 0;
}

有了上面的知识储备之后,下面我们来实现贪吃蛇

3.地图的实现

地图的实现需要用到宽字符,普通的字符占用一个字节的位置,而宽字符会占用两个字符的位置,我们打印墙体需要的是:□,打印蛇需要用的是:●,打印食物需要的是:★

3.1<locale.h>本地化

C语言默认采用ASCII编码,且使用的是低7位,最高位是没有使用的,可表示为:0xxxxxxx,可以看出,ASCII字符集共包含128个字符,这个数量的字符在英语国家是完全够用的,但是在其它国家,中国、法国等国家这些字符是不够用的,所以一些欧洲国家决定,将最高位也编入新的符号,但是不同国家的符号不同,所以同样的ASCII码也可能表示不同的符号,0-127表示的符号相同,但是128-255表示的符号就可能不同
亚洲国家的文字就更多了,汉字就有着10万左右,一个字节只能表示256种符号,这肯定是不够的,所以就必须使用多个字节表示一个符号,所以理论上就可以表示256*256=65536个符号

为了使C语言国际化,C语言标准中不断加入了国家化的支持,比如:加入宽字符的类型和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了程序员针对特定地区调整程序行为的函数,我们下面来看一下<locale.h>头文件:
该头文件提供的函数用于控制C标准库中对于不同地区产生不同的行为的部分,在标准中,依赖地区的部分有以下几项:
1.数字量的格式
2.货币量的格式
3.字符集
4.日期和时间表示形式

3.2类项

通过修改地区,程序可以改变其行为来适应世界的不同区域,但地区的修改可能会影响库的许多部分,其中一部分有我们不想改变的,所以C语言支持对不同类型进行修改,下面的一个宏,针对一个类项:

1. LC_COLLATE:影响字符串⽐较函数 strcoll()strxfrm()
2. LC_CTYPE:影响字符处理函数的⾏为。
3. LC_MONETARY:影响货币格式。
4. LC_NUMERIC:影响 printf() 的数字格式。
5, LC_TIME:影响时间格式 strftime()wcsftime()6. LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

链接: 类项详细说明

3.3setlocale函数

3.3.1函数说明

char* setlocale(int category, const char* locale);
该函数有两个参数:
1. category:指的是前面讲的类项中的其中一个,如果传入的是LC_ALL,那么就会影响所有的类项
2. locale:这个参数C语言只给了两个选择:
2.1 "C"正常模式,也就是C语言标准模式
2.2 " "本地模式,也就是按照地区设置的模式
也就是说,C语言在开始时,都会隐藏一个函数调用:
stelocale(LC_ALL, "C");
该函数返回的是一个字符串指针,表示已经设置好的格式,如果调用失败,那么会返回NULL
setlocale也可以查询当前地区,只需要将第二参数设置为NULL即可

3.3.2函数的使用

//setlocale函数的使用
#include <locale.h>
int main()
{char* loc;//查询默认的本地信息loc = setlocale(LC_ALL, NULL);printf("默认的本地信息:%s\n", loc);//默认的本地信息:C//查询设置之后的信息loc = setlocale(LC_ALL, "");printf("设置之后的本地信息:%s\n", loc);//设置之后的本地信息:Chinese (Simplified)_China.936return 0;
}

3.4宽字符的打印

地图、食物、蛇身都是使用宽字符构成的,所以我们要了解宽字符的打印

宽字符的打印十分简单,和平常的打印类似,但是会从printf改变为wprintf,占位符从%c改为%lc,再在需要打印的字符前加上L即可:

//宽字符的打印
#include <locale.h>
int main()
{//先设置以下地区setlocale(LC_ALL, "");//打印宽字符//注意://1.wprintf//2.两个L的位置wprintf(L"%lc\n", L'●');wprintf(L"%lc\n", L'★');wprintf(L"%lc\n", L'□');return 0;
}

宽字符与正常字符占的位置大小也不相同:
在这里插入图片描述

3.5地图的绘制

地图的打印相对简单,只需要通过设置坐标打印出地图即可

//地图的绘制
void MapCreat()
{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 <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}

外边框就是地图的样子:
在这里插入图片描述

4.游戏的完整代码实现

因为在代码中,就已经将所有信息描述地相对清楚了,所以这里直接贴上代码

4.1游戏开始

游戏设计内容分为三个部分:开始、进行、结束

//将地图、蛇身、食物都定义为宏,方便使用
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//设置蛇初始化时的位置
#define POS_X 24
#define POS_Y 5//定义一个宏,用来检测按键状态
#define KEY_STATE(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1: 0 )//如果返回值的最低位为1,表示该按键被按过//创建贪吃蛇结点结构体
typedef struct SnackNode
{//只需要有坐标即可以及指向下一个结点的指针即可int x;int y;struct SnackNode* next;
}SnackNode, *pSnackNode;//将蛇的方向放在一个枚举类型中,使用枚举类型是因为枚举类型是有值的,可以通过==符号来进行判断
enum DIRECTION
{//上、下、左、右UP = 1,DOWN,LEFT,RIGHT
};//将游戏的状态也放在一个枚举类型中
enum STATE
{//游戏正常运行OK,ESC_NORMAL,//按esc游戏正常退出KILL_BY_SELF,//撞到自己死亡KILL_BY_WALL//撞到墙死亡
};//我们要将贪吃蛇游戏的信息封装在一个结构体中,方便后续查询
typedef struct Snack
{//贪吃蛇头结点的信息pSnackNode _pSnack;//食物结点的信息pSnackNode _pFood;//当前食物的分数\权重int _FoodWeight;//当前总分数int _Score;//当前蛇的速度,也就是休息时间,休息时间越短,表示蛇的速度越快int _SleepTime;//当前蛇的方向enum DIRECTION _Direction;//游戏的状态(正常、撞墙、撞到自己)enum STATE _State;
}Snack, *pSnack;//1.开始游戏
void GameStart(pSnack ps);//初始化信息的打印
void WelcomPrint();//地图的绘制
void MapCreat();//提示信息的打印
void HelpPrint();//蛇身的初始化
void InitSnack(pSnack ps);//食物的初始化
void InitFood(pSnack ps);///设置光标位置的函数
void SetPos(short x, short y)
{//函数构造比较简单,只需要使用Set函数即可//1.光标位置结构体COORD pos = { x, y };//2.获得句柄HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//3.设置光标信息SetConsoleCursorPosition(hOutput, pos);
}//初始化信息的打印
void WelcomPrint()
{//设置一下光标的位置,让打印的信息出现在屏幕正中心SetPos(35, 14);wprintf(L"欢迎来到贪吃蛇小游戏!");SetPos(36, 16);system("pause");system("cls");SetPos(20, 14);wprintf(L"使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");SetPos(35, 16);system("pause");system("cls");
}//地图的绘制
void MapCreat()
{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 <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}//提示信息的打印
void HelpPrint()
{SetPos(62, 15);printf("1.不能撞墙,不能咬到自己");SetPos(62, 16);printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");SetPos(62, 17);printf("3.F3加速,F4减速");SetPos(62, 18);printf("4.ESC-退出, 空格-暂停游戏");
}//蛇身的初始化
void InitSnack(pSnack ps)
{pSnackNode node = NULL;//蛇身的打印//假设蛇一开始为5节,就需要开辟5个蛇结点for(int i = 0; i<5; i++){node = (pSnackNode)malloc(sizeof(SnackNode));if (node == NULL){perror("malloc error");return;}//因为宽字符占两个x的位置,所以这里需要2*i表示一个结点的x坐标node->x = POS_X + 2 * i;//而y轴不同,一个宽字符占y轴的宽度仍然是一个宽度node->y = POS_Y;node->next = NULL;//然后再将这5个蛇结点链接起来if (ps->_pSnack == NULL){ps->_pSnack = node;}else{//头插法node->next = ps->_pSnack;ps->_pSnack = node;}}//通过链接起来的蛇结点来打印蛇node = ps->_pSnack;while (node){SetPos(node->x, node->y);wprintf(L"%lc", BODY);node = node->next;}//蛇的其它参数的设置ps->_Direction = RIGHT;//假设开始方向为向右走ps->_FoodWeight = 10;//假设初始时一个食物是10分ps->_Score = 0;//初始时总分数为0分ps->_SleepTime = 200;//初始速度为200msps->_State = OK;//开始时一切正常运行ps->_pFood = NULL;
}//食物的初始化
void InitFood(pSnack ps)
{//此时需要将食物设置在一个随机位置,使用rand函数即可int x = 0;int y = 0;again://x的范围应该为2-54,而且必须也是2的倍数do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//食物出现的位置不能够和蛇的身体重合pSnackNode pcur = ps->_pSnack;while (pcur){if (pcur->x == x && pcur->y == y){goto again;}pcur = pcur->next;}//创建食物结点pSnackNode pFood = (pSnackNode)malloc(sizeof(SnackNode));if (pFood == NULL){perror("malloc fail");return;}pFood->x = x;pFood->y = y;ps->_pFood = pFood;//打印食物SetPos(ps->_pFood->x, ps->_pFood->y);wprintf(L"%lc", FOOD);
}//1.游戏开始
void GameStart(pSnack ps)
{//1.初始化信息的打印//1.1初始化信息的设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//1.2隐藏光标HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO Cursorinfo;GetConsoleCursorInfo(hOutput, &Cursorinfo);Cursorinfo.bVisible = false;SetConsoleCursorInfo(hOutput, &Cursorinfo);//1.3初始化信息的打印WelcomPrint();//2.地图的绘制MapCreat();//3.提示信息的打印HelpPrint();//蛇身的初始化InitSnack(ps);//因为初始化蛇身会改变蛇的参数,所以要将ps传入//食物的初始化InitFood(ps);
}

4.2游戏运行

//2.游戏运行
void GameRun(pSnack ps);//蛇撞到自己
void KillBySelf(pSnack ps);//蛇撞到墙
void KillByWall(pSnack ps);//蛇吃掉了食物
void EatFood(pSnack ps, pSnackNode pnext);//蛇没吃掉食物
void NoFood(pSnack ps, pSnackNode pnext);//游戏暂停
void Pause();//蛇的移动
void SnackMove(pSnack ps);/////蛇撞到自己
void KillBySelf(pSnack ps)
{pSnackNode pcur = ps->_pSnack->next;while (pcur){if (pcur->x == ps->_pSnack->x && pcur->y == ps->_pSnack->y){ps->_State = KILL_BY_SELF;}pcur = pcur->next;}
}//蛇撞到墙
void KillByWall(pSnack ps)
{if (ps->_pSnack->x == 0 ||ps->_pSnack->x == 56 ||ps->_pSnack->y == 0 ||ps->_pSnack->y == 26)ps->_State = KILL_BY_WALL;
}//判断下一个结点是否是食物
int NextIsFood(pSnack ps, pSnackNode pnext)
{if (ps->_pFood->x == pnext->x&& ps->_pFood->y == pnext->y){return 1;}else{return 0;}
}//蛇吃掉了食物
void EatFood(pSnack ps, pSnackNode pnext)
{//1.需要将食物结点也加入到蛇链表上,然后再生成一个食物pnext->next = ps->_pSnack;ps->_pSnack = pnext;//2.将新的蛇打印出来pSnackNode pcur = ps->_pSnack;while (pcur){SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}//3.释放原来食物的结点,然后再创建一个食物结点free(ps->_pFood);ps->_Score += ps->_FoodWeight;//加分InitFood(ps);
}//蛇没吃掉食物
void NoFood(pSnack ps, pSnackNode pnext)
{//头插pnext->next = ps->_pSnack;ps->_pSnack = pnext;//打印蛇身pSnackNode cur = ps->_pSnack;while (cur->next->next){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 SnackMove(pSnack ps)
{//蛇的移动只需要重新开辟一块新的空间,然后再将最后一块结点的空间释放即可//1.开辟一块新的空间pSnackNode newnode = (pSnackNode)malloc(sizeof(SnackNode));if (newnode == NULL){perror("malloc faile!");return;}newnode->x = 0;newnode->y = 0;newnode->next = NULL;//2.先通过蛇移动的方向改变一下x和y值switch (ps->_Direction){case UP:newnode->x = ps->_pSnack->x;newnode->y = ps->_pSnack->y - 1;//注意:向上移动时,需要将y值-1,而不是+1break;case DOWN:newnode->x = ps->_pSnack->x;newnode->y = ps->_pSnack->y + 1;break;case LEFT:newnode->x = ps->_pSnack->x - 2;newnode->y = ps->_pSnack->y;break;case RIGHT:newnode->x = ps->_pSnack->x + 2;newnode->y = ps->_pSnack->y;break;}//3.再判断蛇的状态//3.1如果下一个位置是食物,吃掉食物if(NextIsFood(ps, newnode)){EatFood(ps, newnode);}else{NoFood(ps, newnode);}//3.2蛇撞到自己,游戏结束KillBySelf(ps);//3.3蛇撞到墙,游戏结束KillByWall(ps);
}//游戏暂停
void Pause()
{while (1){Sleep(100);//只有当再次按下空格键时,停止暂停if (KEY_STATE(VK_SPACE)){break;}}
}//2.游戏运行
void GameRun(pSnack ps)
{do{//打印得分以及食物分数情况SetPos(64, 10);printf("总得分:%05d", ps->_Score);SetPos(64, 11);printf("每个食物的分数:%2d", ps->_FoodWeight);//通过按键修改蛇身结构//1.修改蛇的方向if (KEY_STATE(VK_UP) && ps->_Direction != DOWN){ps->_Direction = UP;}else if(KEY_STATE(VK_DOWN) && ps->_Direction != UP){ps->_Direction = DOWN;}else if (KEY_STATE(VK_LEFT) && ps->_Direction != RIGHT){ps->_Direction = LEFT;}else if (KEY_STATE(VK_RIGHT) && ps->_Direction != LEFT){ps->_Direction = RIGHT;}//2.修改蛇的状态else if (KEY_STATE(VK_SPACE)){Pause();//空格键为暂停}else if (KEY_STATE(VK_F3)){//修改权重也是需要限制的if(ps->_SleepTime >= 80){ps->_SleepTime -= 30;ps->_FoodWeight += 2;//修改休息时间就可以实现加速状态,加速时食物的得分权重会更高}}else if (KEY_STATE(VK_F4)){if (ps->_SleepTime < 320){ps->_SleepTime += 30;ps->_FoodWeight -= 2;}}//蛇每次移动之后需要休息一下,来保证蛇的速度Sleep(ps->_SleepTime);//蛇的移动SnackMove(ps);} while (ps->_State == OK);//当游戏状态为正常时,表示游戏继续进行
}

4.3游戏结束

//3.游戏结束
void GameEnd(pSnack ps)
{//游戏结束//1.打印结束原因SetPos(20, 12);switch (ps->_State){case KILL_BY_SELF:printf("您撞到了自己,游戏结束\n");break;case KILL_BY_WALL:printf("您撞到了墙,游戏结束\n");break;case ESC_NORMAL:printf("您通过esc正常退出,游戏结束\n");break;}//2.释放空间pSnackNode pcur = ps->_pSnack;while (pcur){pSnackNode del = pcur->next;free(pcur);pcur = del;}ps->_pSnack = NULL;
}

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

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

相关文章

【自考zt】【软件工程】【21.10】

关键字&#xff1a; 软件需求基本性质、软件系统需求挑战、耦合&#xff08;高内容&#xff0c;低无直接&#xff09;、内聚&#xff08;初始化时间&#xff09;、uml包、rup边界类、测试首要目标、单元测试最后工作、性能需求 软件开发本质、软件需求规约三种风格、提炼、用…

原生 iOS 引入 Flutter 报错 kernel_blob.bin 找不到

情况 在一次原生 iOS 项目中引入 Flutter 的过程中&#xff0c;在模拟器中运行出现报错&#xff1a; 未能打开文件“kernel_blob.bin”&#xff0c;因为它不存在。 如下图&#xff1a; 模拟器中一片黑 原因&解决方案 这个是因为 Flutter 的打包 iOS framework 命令中…

gird布局《个人觉得非常好用》

一、相对于flex布局 &#xff08;1&#xff09;优点&#xff1a;gird布局&#xff08;适用于二维布局的场景更加灵活&#xff09;&#xff0c;flex&#xff08;适用于一维布局的场景更加灵活&#xff09; 二、代码示例 <!DOCTYPE html> <html lang"en">…

C#进阶-ASP.NET实现可以缩放和旋转的图片预览页

本文详细介绍了如何在ASP.NET WebForms中实现一个功能丰富的图片预览页面。通过结合HTML、CSS和JavaScript&#xff0c;用户可以方便地对图片进行放大、缩小以及旋转操作。文章从页面的基本布局开始&#xff0c;逐步讲解了如何设置图片展示区、添加控制按钮、编写CSS样式以及实…

推动生态系统架构创新与可持续发展的关键引擎——The Open Group 2024年度大会全解析

在当今快速变化的数字化转型浪潮中&#xff0c;开放标准正逐渐成为推动企业创新和生态系统架构发展的重要力量。为了帮助企业在实现可持续发展的道路上加速推进&#xff0c;The Open Group 2024生态系统架构与可持续发展年度大会将为架构师、技术专家以及企业CIO、CTO提供一个深…

Redis中使用布隆过滤器解决缓存穿透问题

一、缓存穿透(失效)问题 缓存穿透是指查询一个一定不存在的数据&#xff0c;由于缓存中没有命中&#xff0c;会去数据库中查询&#xff0c;而数据库中也没有该数据&#xff0c;并且每次查询都不会命中缓存&#xff0c;从而每次请求都直接打到了数据库上&#xff0c;这会给数据…

机械革命imini Pro820迷你主机评测和拆解,8845H小主机使用政府补贴仅需两千三

机械革命imini Pro820迷你主机评测和拆解&#xff0c;8845H小主机使用政府补贴仅需两千三。 最近上线了家电补贴相关的活动&#xff0c;最高可以补贴20%&#xff0c;然后就看到了这款mini主机感觉很划算就下单了&#xff0c;用来替换我旧的N5095小主机&#xff0c;当服务器用。…

fpga系列 HDL:简化的FIFO实现

CODE 下面是一个简化的FIFO实现示例&#xff0c;基于Verilog HDL&#xff1a; module fifo (input wire clk, // 时钟信号input wire reset, // 异步复位信号input wire wr_en, // 写使能信号input wire rd_en, // 读使能…

全志A527 A133 A523 T527 T133 H6 H8应用无法开启后台服务

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改4.1 代码修改方法14.2 代码修改方法24.3 代码修改方法35.彩蛋1.前言 像全志的很多平台,普通的app并不能正常的启动后台的服务,这样对于应用层很困扰,无法启动后台的服务,功能就不能正常使用…

PHP体检信息管理系统-计算机毕业设计源码54850

目录 1 绪论 1.1 选题背景 1.2选题意义 1.3研究的主要内容 1.4论文结构与章节安排 2系统分析 2.1.1 技术可行性分析 2.1.2经济可行性分析 2.1.3操作可行性分析 2.2 系统流程分析 2.2.1 数据新增流程 2.2.2 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2…

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency><groupId>org.springframework</groupId>…

pdf怎么压缩?分享5种压缩PDF文件的方法

pdf怎么压缩&#xff1f;PDF文件的压缩在日常办公和学习中尤为重要&#xff0c;它不仅能够大幅度缩减文件大小&#xff0c;节省宝贵的存储空间&#xff0c;还能加快文件在网络中的传输速度&#xff0c;提升工作效率。特别是在处理包含大量图像或复杂布局的PDF文档时&#xff0c…

Linux——网络基础Socket编程

目录 一计算机网络背景 二协议 1初始协议 1.1协议分层 1.2OSI七层模型 1.3TCP/IP五层模型 2再始协议 2.1为什么要有TCP/IP协议 2.2TCP/IP与OS的关系 2.3所以什么是协议 三网络传输基本流程 1局域网&#xff08;以太网&#xff09;通信原理 1.1认识mac地址 2同…

【C++】如何用C++创建对象,理解作用域、堆栈、内存分配

九、如何用C创建对象&#xff0c;理解作用域、堆栈、内存分配 本部分讨论如何用C创建对象。建议先看【C】C中的关键字&#xff1a;const、mutable、auto、new....-CSDN博客 中的关键字new、delete&#xff0c;方便本篇的理解。 C是对内存管控最强的一门编程语言。 当我们写完…

【Hot100】LeetCode—322. 零钱兑换

目录 1- 思路动态规划 2- 实现⭐322. 零钱兑换——题解思路 3- ACM 实现 原题链接&#xff1a;322. 零钱兑换 1- 思路 动态规划 动规五部曲 1- 定义 dp 数组确定含义 dp[j] 代表凑到金钱为 j 的最少硬币个数 2- 递推公式 dp[j] Math.min(dp[j],dp[amount-]1) 3- 初始化 dp[…

数据结构与算法——Java实现 4.数组

目录 一、数组 — 概述 1.定义 2.特点 3.公式 小测试 二、数组的性能 1.空间占用 2.随机访问 三、动态数组 1.实现动态数组 2.新增元素&#xff08;最后一个元素位置&#xff09; 3.新增元素、数组扩容 4.检查数组容量 5.类中定义的其他方法 ① 按索引查找元素 ② 返回数组长度…

【C语言】了解函数,认识函数

目录 一、函数的概念 函数特点 二、库函数 1.标准库和头文件 2.库函数的使用方法 常见的库函数总结 3.自定义函数 1.函数的形参和实参 2.数组做参数时&#xff1a; 四、 函数的嵌套调用和链式访问 1.嵌套调用 链式访问 五、函数的声明和定义 1.单个文件 多个文件&…

CodeFormer——卓越的AI照片修复工具,能够轻松消除图片以及视频中的马赛克,还原清晰画质。

CodeFormer是什么 CodeFormer是一款由南洋理工大学和商汤科技联合开发的AI照片和视频修复工具。融合了变分自动编码器&#xff08;VQGAN&#xff09;和Transformer技术&#xff0c;对模糊和马赛克的照片或视频进行高质量的修复。CodeFormer通过先进的算法优化图像细节&#xf…

SpringBoot学习(14)增删改查

快速上手 配置文件 pom 包配置 pom 包里面添加 Jpa 和 Thymeleaf 的相关包引用 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><gr…

Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 SurfaceView 是 Android 平台上用于高效渲染图形的视图控件。它将内容绘制在一个独立的 Surface 上&#xff0c;可以直接由渲染线程访问&#x…