贪吃蛇(C)

        游戏背景:贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。

       总: 游戏设计大纲

        使⽤C语⾔Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇。

        实现的基本功能

        1、贪吃蛇地图绘制。

        2、蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作)。

        3、 蛇撞墙死亡。

        4、蛇撞⾃⾝死亡。

        5、 计算得分。

        6、 蛇⾝加速、减速。

        7、 暂停游戏、退出游戏。

        一、Win32 API介绍

        1、1win32 API

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

        下面我们用vs2022演示,需要包含头文件<windows.h>。

        以下函数都是在window.h中的,我们只需要用即可。 

        1、2控制台程序

        平常我们运⾏起来的⿊框程序其实就是控制台程序。我们可以设计窗口的大小和title

        system在<stdlib.h>中

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

        1、3控制台屏幕上的坐标COORD

        COORD 是Windows API中定义的⼀种结构,表⽰⼀个字符在控制台屏幕上的坐标

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

        我们就可以控制一个字符在控制台屏幕上出现的位置。

COORD pos = { 10, 15 };

        1、4 GetStdHandle

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

HANDLE GetStdHandle(DWORD nStdHandle);

        这里我们使用标准输出STD_OUTPUT_HANDLE

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

        1、5 GetConsoleCursorInfo(获取光标信息)

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

BOOL WINAPI GetConsoleCursorInfo(HANDLE hConsoleOutput,PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

        使用:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息

        1、5、1 CONSOLE_CURSOR_INFO

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

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

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

        bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。(我们会把它设为false,来让光标不在屏幕上出现)。

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

        1、6 SetConsoleCursorInfo

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

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

        使用:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态

        1、7 SetConsoleCursorPosition

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

BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);

        使用:

COORD pos = { 10, 5};//获取标准输出的句柄(⽤来标识不同设备的数值)HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(handle, pos);

        1、8将以上所对光标进行的操作进行封装SetPos()

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

//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE handle = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(handle, pos);
}

        想要对光标进行其他操作,也可以加入里面,这里只是举例。 

        1、9 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 )

        二、地图绘制

        这⾥不得不讲⼀下控制台窗⼝的⼀些知识,如果想在控制台的窗⼝中指定位置输出信息,我们得知道 该位置的坐标,所以⾸先介绍⼀下控制台窗⼝的坐标知识。 控制台窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓

        2、1宽字符 

         在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★ 普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。为什么我们要使用宽字符呢?我们在控制台可以看见,x轴的2长度才和y轴的1长度相当,所以我们为了让界面好看工整,我们使用宽字符。

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

        2、1、1<locale.h>本地化

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

        数字量的格式 , 货币量的格式 , 字符集 , ⽇期和时间的表⽰形式

        2、1、2 类项

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

        LC_COLLATE:影响字符串比较函数strcoll()和strxfrm()

        LC_CTYPE:影响字符处理函数的行为

        LC_MONETARY:影响货币格式

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

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

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

        2、1、3 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, " ");//切换到本地环境

        2、1、4 宽字符打印

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

        宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类项处理。前缀“L”在单引号前,表示宽字符,对应wprintf()的占位符为%lc;“L“在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls

#include <stdio.h>
#include<locale.h>
int main() {setlocale(LC_ALL, "");wchar_t ch1 = L'●';wchar_t ch2 = L'你';wchar_t ch3 = L'好';wchar_t ch4 = L'★';printf("%c%c\n", 'a', 'b');//对照wprintf(L"%lc\n", ch1);wprintf(L"%lc\n", ch2);wprintf(L"%lc\n", ch3);wprintf(L"%lc\n", ch4);return 0;
}

        2、2地图坐标

        我们假设实现⼀个棋盘27⾏,58列的棋盘(⾏和列可以根据⾃⼰的情况修改),再围绕地图画出墙。最终行= 25,列 = 54 = 宽字符27列。(x轴的长度一定得是2的倍数,否则由于宽字符的原因,后面可能会出现蛇身的某个结点或者食物一半在内,一半在墙上的问题。)

         2、3蛇身和食物

        初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24, 5)处开始出现 蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数(这里指蛇的每个结点的左边一定得对应x上2的倍数),否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。

        关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然后打印★。

        三、数据结构的设计

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

typedef struct SnakeNode
{int x;//(x,y)坐标int y;struct SnakeNode* next;//蛇身结点的下一个结点
}SnakeNode, *pSnakeNode;
//*pSnakeNode 等价于 typedef struct SnakeNode* pSnakeNode
// 则pSnakeNode = SnakeNode*
//即也定义结构体指针

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

//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
//蛇的状态
enum GAME_STATUS
{OK,//正常运行END_NORMAL,//ESC退出KILL_BY_WALL,//撞墙KILL_BY_SELF//撞到自身
};
//整个游戏过程都在维护这条蛇,即多个结点构成的链表
typedef struct Snake
{pSnakeNode _pSnakeHead;//指向蛇的头结点的指针pSnakeNode _pFood;//指向食物的指针//食物本质上也就是蛇身的结点,只不过打印方式不同int _CurrentScore;//记录当前分数int _FoodScore;//记录每个食物多少分,默认十分int _SleepTime;//每走一步的休眠时间,即蛇走的速度//休眠越少,速度越快;反之越慢。enum DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏状态:正常,退出,撞墙,撞到自己
}Snake, *pSnake;

        四、游戏流程

        

        五、代码实现

        5、1Snake.h

#define _CRT_SECURE_NO_WARNINGS -1
#pragma once 
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<windows.h>
#include<locale.h>
#include<time.h>#define WALL "□"
#define BODY "●"
#define FOOD "★"#define BEGIN_X 24
#define BEGIN_Y 5#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇的状态
enum GAME_STATUS
{OK,//正常运行END_NORMAL,//ESC退出KILL_BY_WALL,//撞墙KILL_BY_SELF//撞到自身
};typedef struct SnakeNode
{int x;//(x,y)坐标int y;struct SnakeNode* next;//蛇身结点的下一个结点
}SnakeNode, *pSnakeNode;
//*pSnakeNode 等价于 typedef struct SnakeNode* pSnakeNode
// 则pSnakeNode = SnakeNode*
//即也定义结构体指针//整个游戏过程都在维护这条蛇,即多个结点构成的链表
typedef struct Snake
{pSnakeNode _pSnakeHead;//指向蛇的头结点的指针pSnakeNode _pFood;//指向食物的指针//食物本质上也就是蛇身的结点,只不过打印方式不同int _CurrentScore;//记录当前分数int _FoodScore;//记录每个食物多少分,默认十分int _SleepTime;//每走一步的休眠时间,即蛇走的速度//休眠越少,速度越快;反之越慢。enum DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏状态:正常,退出,撞墙,撞到自己
}Snake, *pSnake;//----游戏开始---- - 完成游戏初始化
void GameStart(pSnake psnake);//游戏欢迎界面
void WelComeToGame();
//定位光标
void SetPos(short x, short y);
//打印地图
void CreatMap();
//初始化蛇
void InitSnake(pSnake psnake);
//创建食物
void CreatFood(pSnake psnake);//----游戏运行---- - 游戏的正常运行过程
void GameRun(pSnake psnake);//打印帮助信息
void PrintHelpInfo();//游戏暂停和恢复
void Pause();//蛇动起来
void SnakeMove(pSnake psnake);//判断移动之后是否是食物
bool NextIsFood(pSnake psnake, pSnakeNode pnext);//吃掉食物
void EatFood(pSnake psnake,pSnakeNode pnext);
//不吃食物
void EatFood(pSnake psnake, pSnakeNode pnext);//撞墙
void KillByWall(pSnake psnake);//撞到自己
void KillBySelf(pSnake psnake);//-----游戏结束---- - 善后工作(释放资源)
void GameEnd(pSnake psnake);

        5、2Snake.c

#include"snake.h"
//----游戏开始---- - 完成游戏初始化//定位光标
void SetPos(short x, short y)
{COORD pos = { x,y };HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(handle_out, pos);//设置光标位置
}//打印欢迎界面
void WelComeToGame()
{//定位光标//来到中间,打印”欢迎....“SetPos(40,15);printf("欢迎来到贪吃蛇小游戏\n");SetPos(40, 25);system("pause");//暂停命令system("cls");//清理屏幕信息SetPos(25, 15);printf("使用 ↑ ↓ ← → 分别控制蛇的移动,F3是加速,F4是减速");SetPos(40, 25);system("pause");system("cls");}//打印地图
void CreatMap()
{//打印 col = 58,row = 27的棋盘//宽字符横向占两个字节//上:[0,0]到[56,0],下:[0,26]到[56,26]//左:[0,1]到[0,25],右:[56,1]到[56,25]//上SetPos(0, 0);for (int i = 0; i <= 56; i += 2){printf(WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){printf(WALL);}//左for (int i = 1; i <= 25; i++){SetPos(0, i);printf(WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);printf(WALL);}SetPos(60, 25);//system("pause");
}//初始化蛇
void InitSnake(pSnake psnake)
{pSnakeNode cur = NULL;//默认开始蛇有五个结点for (int i = 0; i < 5; i++){pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake malloc fail\n");exit(-1);}cur->x = BEGIN_X+2*i, cur->y = BEGIN_Y;//横着连续放置每个结点cur->next = NULL;//来一个单链表头插法,将“蛇头”指到最右边那个if (psnake->_pSnakeHead == NULL){psnake->_pSnakeHead = cur;}else{cur->next = psnake->_pSnakeHead;psnake->_pSnakeHead = cur;}}//打印蛇身cur = psnake->_pSnakeHead;while (cur != NULL){SetPos(cur->x, cur->y);printf(BODY);cur = cur->next;}psnake->_Status = OK;psnake->_CurrentScore = 0;psnake->_FoodScore = 10;psnake->_pFood = NULL;psnake->_SleepTime = 200;//200mspsnake->_Dir = RIGHT;//因为上面的蛇初始化,所以这里蛇初始向右。SetPos(60, 25);//system("pause");
}//创建食物
void CreatFood(pSnake psnake)
{//坐标应该是随机生成,但是随机是有约束的//首先不能出墙,其次x坐标必须是2的倍数,最后食物不能和蛇的身体冲突int x = 0;int y = 0;//墙中间范围是:x[2,54],y[1,25]
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x的坐标必须是2的倍数//坐标不能和蛇身冲突pSnakeNode cur = psnake->_pSnakeHead;while (cur != NULL){if (cur->x == x && cur->y == y){goto again;//冲突就重新执行}cur = cur->next;}pSnakeNode pFoodNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFoodNode == NULL){perror("CreatFood malloc fail\n");exit(-1);}pFoodNode->x = x, pFoodNode->y = y;pFoodNode->next = NULL;psnake->_pFood = pFoodNode;SetPos(x, y);printf(FOOD);SetPos(60, 25);//system("pause");
}void GameStart(pSnake psnake)
{//控制台窗口的设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle_out, &CursorInfo);//获取光标信息CursorInfo.bVisible = false;//隐藏光标SetConsoleCursorInfo(handle_out, &CursorInfo);//设置光标状态//打印欢迎界面WelComeToGame();//创建地图CreatMap();//初始化蛇InitSnake(psnake);//创建食物CreatFood(psnake);
}//----游戏运行---- - 游戏的正常运行过程//打印帮助信息
void PrintHelpInfo()
{SetPos(66, 15);printf("1、不能撞墙,不能咬到自己");SetPos(66, 16);printf("2、使用↑.↓.←.→ 分别控制蛇移动");SetPos(66, 17);printf("3、F3加速,F4减速");SetPos(66, 18);printf("4、ESC-退出,空格-暂停/继续游戏");SetPos(66, 19);printf("ZY@版权");SetPos(60, 25);//system("pause");
}//游戏暂停和恢复
void Pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}//判断移动之后是否是食物
bool NextIsFood(pSnake psnake, pSnakeNode pnext)
{if (psnake->_pFood->x == pnext->x && psnake->_pFood->y == pnext->y)return true;return false;
}//吃掉食物
void EatFood(pSnake psnake, pSnakeNode pnext)
{//头插pnext->next = psnake->_pSnakeHead;psnake->_pSnakeHead = pnext;//打印蛇pSnakeNode cur = psnake->_pSnakeHead;while (cur != NULL){SetPos(cur->x, cur->y);printf(BODY);cur = cur->next;}//释放食物结点free(psnake->_pFood);//加成绩psnake->_CurrentScore += psnake->_FoodScore;//新创建食物CreatFood(psnake);
}
//不吃食物
void NotEatFood(pSnake psnake, pSnakeNode pnext)
{//头插pnext->next = psnake->_pSnakeHead;psnake->_pSnakeHead = pnext;//打印蛇身,不打印最后一个结点,并找到最后一个结点pSnakeNode cur = psnake->_pSnakeHead;while (cur->next->next != NULL){SetPos(cur->x, cur->y);printf(BODY);cur = cur->next;}//将原本最后一个结点处的身体打印为空格SetPos(cur->next->x, cur->next->y);printf("  ");//释放最后一个结点free(cur->next);cur->next = NULL;
}//撞墙
void KillByWall(pSnake psnake)
{//撞墙if (psnake->_pSnakeHead->x == 0 || psnake->_pSnakeHead->x == 56|| psnake->_pSnakeHead->y == 0 || psnake->_pSnakeHead->y == 26)psnake->_Status = KILL_BY_WALL;
}//撞到自己
void KillBySelf(pSnake psnake)
{//蛇头与蛇身的坐标重合就是撞到了pSnakeNode cur = psnake->_pSnakeHead->next;while (cur != NULL){if (psnake->_pSnakeHead->x == cur->x && psnake->_pSnakeHead->y == cur->y){psnake->_Status = KILL_BY_SELF;break;}cur = cur->next;}
}//蛇动起来
void SnakeMove(pSnake psnake)
{//创建移动的下一个位置结点pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));{if (pnext == NULL){perror("pnext malloc fail\n");exit(-1);}}pnext->next = NULL;switch (psnake->_Dir){case UP:pnext->x = psnake->_pSnakeHead->x;pnext->y = psnake->_pSnakeHead->y - 1;break;case DOWN:pnext->x = psnake->_pSnakeHead->x;pnext->y = psnake->_pSnakeHead->y + 1;break;case LEFT:pnext->x = psnake->_pSnakeHead->x - 2;pnext->y = psnake->_pSnakeHead->y;break;case RIGHT:pnext->x = psnake->_pSnakeHead->x + 2;pnext->y = psnake->_pSnakeHead->y;break;}//判断移动之后,是否是食物,或者撞墙,或者撞到自己//食物if (NextIsFood(psnake, pnext)){//吃掉食物EatFood(psnake,pnext);}else{//不吃NotEatFood(psnake,pnext);}//撞墙KillByWall(psnake);//撞到自己KillBySelf(psnake);}void GameRun(pSnake psnake)
{//右侧打印帮助信息PrintHelpInfo();//打印当前已经获得分数和每个食物的分数//并检测按键do{SetPos(66, 10);printf("得分:%05d", psnake->_CurrentScore);SetPos(66, 11);printf("每个食物分数:%2d", psnake->_FoodScore);//检测按键//按上时,并且蛇前一秒并不是往下走,就可以向上走;其他也一样if (KEY_PRESS(VK_UP) && psnake->_Dir != DOWN){psnake->_Dir = UP;}else if (KEY_PRESS(VK_DOWN) && psnake->_Dir != UP){psnake->_Dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && psnake->_Dir != RIGHT){psnake->_Dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && psnake->_Dir != LEFT){psnake->_Dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE))//ESC{psnake->_Status = END_NORMAL;break;}else if (KEY_PRESS(VK_SPACE))//空格{Pause();}else if (KEY_PRESS(VK_F3))//F3,加速{if (psnake->_SleepTime>=80)//最快50,食物最高分20{psnake->_SleepTime -= 30;//每次缩短20mspsnake->_FoodScore += 2;}}else if (KEY_PRESS(VK_F4))//F4,减速{if (psnake->_SleepTime < 320)//最慢320,食物最低分2{psnake->_SleepTime += 30;psnake->_FoodScore -= 2;}}//蛇休眠(暂停)Sleep(psnake->_SleepTime);//蛇动起来SnakeMove(psnake);} while (psnake->_Status == OK);
}//-----游戏结束---- - 善后工作(释放资源)
void GameEnd(pSnake psnake)
{SetPos(20, 12);switch (psnake->_Status){case END_NORMAL:printf("您主动退出游戏,Game Over");break;case KILL_BY_SELF:printf("游戏自杀,Game Over");break;case KILL_BY_WALL:printf("对不起,您撞墙了,Game Over");break;}SetPos(0, 27);//释放蛇身的结点pSnakeNode cur = psnake->_pSnakeHead;while (cur != NULL){pSnakeNode tmp = cur->next;free(cur);cur = tmp;}free(cur);psnake->_pSnakeHead = NULL;
}

        5、3Test.c

#include"snake.h"void Test()
{int ch = 0;do{//创建贪吃蛇Snake snake = { 0 };//1、游戏开始 - 初始化游戏GameStart(&snake);//2、游戏运行 - 游戏的正常运行过程GameRun(&snake);//3、游戏结束 - 善后工作(释放资源)GameEnd(&snake);SetPos(20, 14);printf("再来一次吗?[ Y / N ]:");ch = getchar();getchar();//清理掉\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);}int main()
{setlocale(LC_ALL, "");//本地化srand((unsigned int)time(NULL));Test();return 0;
}

        六、总结

        贪吃蛇游戏作为经典的小游戏,实现难度并不是很大。不过这个作为我们今后设计项目来训练思维也是非常不错的。

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

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

相关文章

自己构建webpack+vue3+ts

先看看我的目录结构&#xff08;我全局使用TS&#xff09;&#xff1a; 一、安装配置webpack打包 安装esno npm install esnoesno 是基于 esbuild 的 TS/ESNext node 运行时,有了它&#xff0c;就可以直接通过esno *.ts的方式启动脚本&#xff0c;package.json中添加 type:…

echarts绘制饼图,部分数据隐藏指示线和文本,hover时隐藏指示线和文本的类别也不显示tooltip提示

option {tooltip: {trigger: item,formatter: (p) > {if (p.name) {return ${p.name}&#xff1a;${p.value}个;}},backgroundColor: #ffffff,textStyle: { color: #666666 } // 提示标签字体颜色},legend: {top: 5%,left: center},series: [{name: Access From,type: pie,…

【机组】指令控制模块实验的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 ​ 目录 &#x1f33a;一、 实验目…

重叠柱状图做法,全程动图演示

最终效果&#xff1a; 具体步骤&#xff1a; 其他图形画法&#xff1a; 点线对比图做法&#xff0c;全程动图演示 气泡图做法&#xff0c;全程动图演示 重叠柱状图做法&#xff0c;全程动图演示 瀑布图做法&#xff0c;全程动图演示 对称图做法&#xff0c;全程动图演示

[Tomcat] [最全] 目录和文件详解

打开tomcat的解压之后的目录可以看到如下的目录结构&#xff1a; Bin bin目录主要是用来存放tomcat的命令&#xff0c;主要有两大类&#xff0c;一类是以.sh结尾的&#xff08;linux命令&#xff09;&#xff0c;另一类是以.bat结尾的&#xff08;windows命令&#xff09;。 …

npm或者pnpm或者yarn安装依赖报错ENOTFOUND解决办法

如果报错说安装依赖报错&#xff0c;大概率是因为npm源没有设置对&#xff0c;比如我这里安装protobufjs的时候报错&#xff1a;ENOTFOUND npm ERR! code ENOTFOUND npm ERR! syscall getaddrinfo npm ERR! errno ENOTFOUND npm ERR! network request to https://registry.cnpm…

小白水平理解面试经典题目LeetCode 594 最大和谐字符串

594 最大和谐字符串 这道题属于字符串类型题目&#xff0c;解决的办法还是有很多的&#xff0c;暴力算法&#xff0c;二分法&#xff0c;双指针等等。 题目描述 和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。 现在&#xff0c;给你一个整数数组 nums …

扫码看文件效果怎么做?文件转成二维码能制作吗?

随着网络的快速发展&#xff0c;现在大家习惯将内容储存在云端&#xff0c;减少自身内存容量的占用&#xff0c;所以现在分享文件时&#xff0c;很多人也会使用文件转二维码的方式&#xff0c;来让其他人查看或者下载文件。今天小编来给大家分享一下文件制作二维码的技巧&#…

司铭宇老师:二手房电话营销培训:二手房电话销售技巧和话术

二手房电话营销培训&#xff1a;二手房电话销售技巧和话术 一、二手房电话销售的重要性 1.高效传播&#xff1a;通过电话&#xff0c;我们可以迅速将房源信息传播给潜在客户&#xff0c;提高房源的曝光率。 2.精准定位&#xff1a;通过电话沟通&#xff0c;我们可以初步了解客户…

如何攻克钙钛矿太阳能电池电性能测试技术壁垒?

1 前言 “碳达峰、碳中和”背景下&#xff0c;发展新能源成为降低碳排放的第一驱动力。以太阳能为代表的清洁能源在市场上的占比大幅提升&#xff0c;与之对应的太阳能电池同样发展迅速。太阳能电池是一种吸收光能产生电能的半导体光电二极管&#xff0c;硅基电池作为第一代太…

Docker(十五)Fedora CoreOS

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; Fedora CoreOS CoreOS 是一个专门为安全和大规模运行容器化工作负载而构建的新 Fedora 版本&#xff0c;它继承了 Fedora Atomic Host 和 C…

13.8.1异步、异步、异步 Page720~721

#include <iostream> #include <thread> #include <future>using namespace std;///定时炸弹第一波 void sync_sleep(int s) {cout << "sync_sleep----不使用异步" << endl;///启动定时this_thread::sleep_for(chrono::seconds(s)); /…

3、柱状图和热图

使用颜色或长度来比较数据集中的类别 既然您可以创建自己的线性图表,现在是时候了解更多的图表类型了! 顺便说一下,如果这是您第一次使用 Python 编写代码,那么您应该为迄今为止所完成的工作感到非常自豪,因为学习一项全新的技能从来都不是一件容易的事情!如果你坚持这个课…

n-皇后问题——DFS

问题描述 第一种方法 每一行放一个皇后边放皇后边判断是否符合条件递归到第n行&#xff0c;则说明当前方案符合条件&#xff0c;进行遍历 代码实现 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 10;int…

述评:中国经济是唱不衰的!

知乎网在发布《A股30年涨跌史一览》的《前言》中说&#xff1a;“以史为鉴&#xff0c;方可知兴替。月盈则亏&#xff0c;水满则溢&#xff0c;涨涨跌跌&#xff0c;才是股生&#xff01;A股不可能总是跌&#xff0c;不涨&#xff01;太阳底下没有新鲜事&#xff0c;历史是会重…

基于SpringBoot的学校防疫物资管理平台

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

PX4Gazobo中ROS通信问题记录

报错提示no data link 解决方法&#xff1a; 下载QGC然后在使用commander takeoff 之前打开QGC并等到显示连接&#xff0c;像下面这样 然后就能看到无人机起飞了。

Git Docker 学习笔记

注意&#xff1a;该文章摘抄之百度&#xff0c;仅当做学习笔记供小白使用&#xff0c;若侵权请联系删除&#xff01; 目录 列举工作中常用的几个git命令&#xff1f; 提交时发生冲突&#xff0c;你能解释冲突是如何产生的吗&#xff1f;你是如何解决的&#xff1f; git的4个…

100 道 Linux 面试题 附答案(一)

一、Linux 概述 什么是Linux Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计…

限流算法 漏桶算法、令牌桶算法

并不能说明令牌桶一定比漏洞好&#xff0c;她们使用场景不一样。令牌桶可以用来保护自己&#xff0c;主要用来对调用者频率进行限流&#xff0c;为的是让自己不被打垮。所以如果自己本身有处理能力的时候&#xff0c;如果流量突发&#xff08;实际消费能力强于配置的流量限制&a…