【贪吃蛇:C语言实现】

文章目录

  • 前言
  • 1.了解Win32API相关知识
    • 1.1什么是Win32API
    • 1.2设置控制台的大小、名称
    • 1.3控制台上的光标
    • 1.4 GetStdHandle(获得控制台信息)
    • 1.5 SetConsoleCursorPosition(设置光标位置)
    • 1.6 GetConsoleCursorInfo(获得光标信息)
    • 1.7 SetConsoleCursorInfo(设置光标信息)
    • 1.8 GetAsyncKeyState(获取按键信息)
  • 2.游戏设计与分析
    • 2.1地图设计
    • 2.2数据结构的设计
      • 2.2.1蛇身的设计
      • 2.2.2蛇的方向
      • 2.2.3游戏的状态
      • 2.2.4管理整条蛇
    • 2.3.游戏流程
  • 3. 功能的具体实现
    • 游戏开始前
    • 3.1 初始化蛇
    • 3.2 创建食物
    • 游戏运行
    • 3.3 检测按键
    • 3.4 蛇移动
    • 3.5 判断节点是否是食物
    • 3.6 吃食物
    • 3.7 不吃食物
    • 3.8 撞墙
    • 3.9 撞到自己
    • 3.10 游戏结束
    • 3.11 main函数
  • 4.参考代码
    • 4.1 main
    • 4.2 Snake.h
    • 4.3 Snake.c

在这里插入图片描述

前言

彻底学习完C语言之后,为巩固所学,我们来实现一个贪吃蛇小游戏。
该游戏涉及到的C语言的知识有:函数、枚举、结构体、动态内存管理、预处理指令。
另外,还包含数据结构(链表)以及Win32API相关知识。

先看一下游戏效果吧
在这里插入图片描述

目前该游戏还有很多可以改进的地方,由于蛇的移动使用的是Sleep函数,看起来有点卡顿,而且按键会有一点延迟。
代码仓库链接

1.了解Win32API相关知识

1.1什么是Win32API

Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。调用各种接口,可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。

1.2设置控制台的大小、名称

设置大小和名称的命令为:

  • mode con cols=列 lines=行
  • tiele 名称

如果想在C语言中设置窗口,需要使用system函数。

在这里插入图片描述

1.3控制台上的光标

COORD 是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标,坐标系(0,0)的原点位于缓冲区的左侧顶部的单元格。使用需要window.h的头文件
在这里插入图片描述

COORD类型的声明:

typedef struct _COORD {SHORT X;//横坐标SHORT Y;//纵坐标
} COOR, *PCOORD;

1.4 GetStdHandle(获得控制台信息)

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

HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);

该函数需要一个参数,参数如下:
在这里插入图片描述

1.5 SetConsoleCursorPosition(设置光标位置)

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

BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,     /要设置哪个控制台COORD pos		           /将光标设置的位置
);

通过上述三个功能,实现设置光标位置

在这里插入图片描述

1.6 GetConsoleCursorInfo(获得光标信息)

GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小可见性的信息。然后将获得的信息放在一个结构体中

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE      hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO   lpConsoleCursorInfo
);

_ Out_ PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构体的指针

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

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;	BOOL bVisible;	
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
  • dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
  • bVisible,光标的可见性。 如果光标可见,则此成员为 TRUE。

在这里插入图片描述

1.7 SetConsoleCursorInfo(设置光标信息)

SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标的大小和可见性。
其参数和get方法相同

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

设置前:

在这里插入图片描述
设置后:

在这里插入图片描述
隐藏光标:

在这里插入图片描述

1.8 GetAsyncKeyState(获取按键信息)

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 )
#include<Windows.h>
int main()
{while (1){if (KEY_PRESS(0x30))printf("0\n");else if (KEY_PRESS(0x31))printf("1\n");else if (KEY_PRESS(0x32))printf("2\n");else if (KEY_PRESS(0x33))printf("3\n");else if (KEY_PRESS(0x34))printf("4\n");else if (KEY_PRESS(0x35))printf("5\n");else if (KEY_PRESS(0x36))printf("6\n");else if (KEY_PRESS(0x37))printf("7\n");}return 0;
}

2.游戏设计与分析

2.1地图设计

由于我打印墙体和蛇想使用 □,但是它是宽字符,该如何打印呢?
在这里插入图片描述
C语言规定:宽字符的类型wchar_t 和宽字符的输⼊和输出函数。
头文件为:<locale.h>

在这里插入图片描述

宽字符与普通字符对比:

在这里插入图片描述

由于墙体使用的是宽字符,所以在打印时需要注意!

在这里插入图片描述
在这里插入图片描述

以下函数实现打印墙体的功能:

#define WALL L'□'
void DrawMap()
{SetPos(0, 0);//上for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);//左、右for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);SetPos(56, i);wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);  
}

再打印地图之前,我们需要设置窗口的大小,并且给玩家一些引导(Win32API的使用已在上面讲过)

//窗口相关设置
void ScreenPrepare()
{//设置窗体大小system("mode con cols=100 lines=40");system("title 贪吃蛇小游戏");//隐藏光标HideCurSor();//打印欢迎语Welcome();//打印地图DrawMap();//显示提示信息HelpInfo();
}

具体函数的实现我们会在后面给出,先看一下效果吧~

在这里插入图片描述

2.2数据结构的设计

2.2.1蛇身的设计

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

//蛇节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针

2.2.2蛇的方向

蛇的方向可以一一列举,使用枚举

//蛇的方向
//上、下、做、左、右
enum Direction
{UP = 1,DOWN,LEFT,RIGHT
};

2.2.3游戏的状态

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

//游戏的状态
enum GameState
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END//结束
};

2.2.4管理整条蛇

我们再封装⼀个Snake的结构体来维护整条贪吃蛇。

//整条蛇
typedef struct Snake
{pSnakeNode pSnake;  //指向整条蛇的指针pSnakeNode PFood;   //指向食物的指针enum Direction Dire;  //蛇的方向enum GameState State; //蛇的状态int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快int Score;//游戏得分int FoodScore;//一个食物的分数
}Snake,* pSnake;

2.3.游戏流程

在这里插入图片描述

3. 功能的具体实现

游戏开始前

3.1 初始化蛇

游戏开始,蛇的长度默认是3。

  • 创建节点,头插法依次连接
  • 节点的纵坐标相同,横坐标依次增加2,产生串的效果
  • 蛇的方向默认为右
  • 打印蛇,并且为蛇头设置颜色

此处颜色设置我们使用Win32API提供的函数:

BOOL WINAPI SetConsoleTextAttribute(_In_ HANDLE hConsoleOutput,//当前设备句柄_In_ WORD   wAttributes//颜色
);

在这里插入图片描述

//设置颜色
void SetColor(int HeadColor)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor);
}//打印蛇
void PrintSnake(pSnake ps)
{pSnakeNode cur = ps->pSnake;int flag = 1;while (cur){if (flag == 1){//为蛇头设置颜色SetColor(12);}SetPos(cur->x, cur->y);//设置每个节点的位置wprintf(L"%lc", BODY);cur = cur->next;flag = 0;SetColor(10);}
}//初始化蛇
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//蛇的长度开始为3for (i = 0; i < 3; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc");return;}cur->next = NULL;cur->x = POS_X + i * 2;  //节点横坐标依次增加2cur->y = POS_Y;//头插法将节点相连if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇PrintSnake(ps);//初始化蛇的其它信息ps->Dire = RIGHT;ps->FoodScore = 10;ps->SleepTime = 200;ps->State = OK;ps->Score = 0;
}

3.2 创建食物

食物要随机生成,有以下几个注意事项:

  • 食物应该在墙体内
  • 食物不可与蛇身重叠
  • 由于我们蛇的打印使用的是宽字符,所以食物的坐标应该在2的倍数处
//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//食物坐标应该在墙体内
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//食物坐标不与蛇重叠pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//根据坐标创建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreatFood():malloc()");return;}//打印食物pFood->x = x;pFood->y = y;SetColor(14);SetPos(pFood->x, pFood->y);wprintf(L"%lc", FOOD);SetColor(10);ps->pFood = pFood;
}

在这里插入图片描述

游戏运行

3.3 检测按键

玩家按上下左右键控制蛇的移动,在检测时需要注意:

  • 玩家按的键若与蛇的方向相反,则不做响应;玩家按键符合,则修改蛇的方向
  • 玩家按F1或F2修改蛇的移动速度;蛇的速度应大于0,食物的分数最低为1分
  • 按键的检测使用Win32API提供的功能:GetAsyncKeyState
//玩游戏
void GameRun(pSnake ps)
{do{//显示分数SetColor(12);SetPos(65, 12);printf("目前得分:%-5d",ps->Score);SetColor(10);SetPos(65, 13);printf("每个食物:%2d分",ps->FoodScore);//检测按键if (KEY_PRESS(VK_UP) && ps->Dire != DOWN){//按上键,且蛇的方向不能向下ps->Dire = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP){//按下键,且蛇的方向不能向上ps->Dire = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT){//按左键,且蛇的方向不能向右ps->Dire = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT){//按右键,且蛇的方向不能向左ps->Dire = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){//按ESC,退出游戏ps->State = END;}else if (KEY_PRESS(VK_SPACE)){//按空格,暂停pause();}else if (KEY_PRESS(VK_F1)){//按F1加速,即睡眠时间变短//休眠时间不能是负数,最快就是休眠30msif (ps->SleepTime >= 50){ps->SleepTime -= 20;//速度变快,食物的分数变高ps->FoodScore += 2;}}else if (KEY_PRESS(VK_F2)){//F2减速,睡眠时间变长//食物的分数不能减到负数,最多减为1分if (ps->FoodScore >= 3){ps->SleepTime += 20;ps->FoodScore -= 2;}}//按照蛇的睡眠时间,真正实现休眠Sleep(ps->SleepTime);//休眠后,蛇要移动SnakeMove(ps);} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}

3.4 蛇移动

蛇的移动就是根据蛇的方向,产生节点,判断节点是不是食物。

  • 所产生的节点的位置根据蛇的方向而定
  • 节点是食物,吃掉食物,蛇身变长
  • 节点不是食物,也吃掉食物,但长度不变
//移动蛇
void SnakeMove(pSnake ps)
{//先产生节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc()");return;}//根据蛇的方向,设定节点的位置switch (ps->Dire){case UP://蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN://蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT://蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT://蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个节点是否是食物if (JudgeNext(ps,pNext)){//是食物,吃掉,长度增加EatFood(ps, pNext);}else{//不是食物,吃掉,长度不增加NoFood(ps, pNext);}//未完//撞墙//撞自己
}

3.5 判断节点是否是食物

将所产生的节点与食物的节点对比即可

//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}

3.6 吃食物

吃掉食物很简单,将所产生的节点与蛇想连;连接后得分增加,并释放所产生的节点,再次产生食物。

//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{//吃掉食物,头插法将节点插入pNext->next = ps;ps = pNext;//打印蛇PrintSnake(ps_->snake);//加分PrintSnake(ps->pSnake);//释放食物节点free(ps->pFood);//再次创建食物CreatFood(ps);
}

3.7 不吃食物

不吃食物需要将产生的节点与蛇相连,然后删除蛇尾(将蛇尾打印尾空格,并释放蛇尾节点)

//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext)
{//头插法连接pNext->next = ps->pSnake;ps->pSnake = pNext;//删除蛇尾pSnakeNode cur = ps->pSnake;//找到蛇尾的前一个节点while (cur->next->next){cur = cur->next;}pSnakeNode del = cur->next;//将蛇尾打印尾空格SetPos(del->x, del->y);printf("  ");//释放蛇尾节点free(del);cur->next = NULL;//打印蛇PrintSnake(ps);
}

3.8 撞墙

若蛇头坐标与墙体坐标重合,则说明撞墙了。

//撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->State = KillByWall;}
}

3.9 撞到自己

若蛇头坐标与蛇身节点的坐标重合,则说明撞到自己了。

//撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->State = KILL_BY_SELF;}cur = cur->next;}
}

3.10 游戏结束

给出游戏结束的原因,释放蛇身节点。

//游戏结束
void GameEnd(pSnake ps)
{SetPos(20, 13);SetColor(12);switch (ps->State){case END:printf("您结束了游戏");break;case KILL_BY_SELF:printf("很遗憾!您撞到了自己");break;case KILL_BY_WALL:printf("很遗憾!您撞墙了");break;}pSnakeNode cur = ps->pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps = NULL;SetColor(10);
}

3.11 main函数

让玩家选择

int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");//适应本地中文环境int ch = 0;do{Snake snake = { 0 };//创建贪吃蛇//游戏开始前的初始化GameStart(&snake);玩游戏GameRun(&snake);游戏结束,善后工作GameEnd(&snake);SetPos(20, 15);printf("还要再来一局吗?(Y/N)");ch = getchar();getchar();//吸收换行} while (ch == 'Y' || ch == 'y');SetPos(0, 30);return 0;
}

4.参考代码

4.1 main

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");//适应本地中文环境int ch = 0;do{Snake snake = { 0 };//创建贪吃蛇//游戏开始前的初始化GameStart(&snake);玩游戏GameRun(&snake);游戏结束,善后工作GameEnd(&snake);SetPos(20, 15);printf("还要再来一局吗?(Y/N)");ch = getchar();getchar();//吸收换行} while (ch == 'Y' || ch == 'y');SetPos(0, 30);return 0;
}

4.2 Snake.h

#define _CRT_SECURE_NO_WARNINGS 1#include<locale.h>
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)#define WALL L'□'//墙体
#define BODY L'●'//蛇身
#define FOOD L'★'//食物//蛇初始位置
#define POS_X 24
#define POS_Y 12//蛇的方向
//上、下、左、右
enum Direction
{UP = 1,DOWN,LEFT,RIGHT
};//游戏的状态
enum GameState
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END//结束
};//蛇节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针//整条蛇
typedef struct Snake
{pSnakeNode pSnake;  //指向整条蛇的指针pSnakeNode pFood;   //指向食物的指针enum Direction Dire;  //蛇的方向enum GameState State; //蛇的状态int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快int Score;//游戏得分int FoodScore;//一个食物的分数
}Snake,* pSnake;//设置光标位置
void SetPos(int x, int y);//设置颜色
void SetColor(int HeadColor);//游戏准备
void GameStart(pSnake psnake);//初始化蛇
void InitSnake(pSnake psnake);//创建食物
void CreatFood(pSnake ps);//打印蛇
void PrintSnake(pSnake ps);//玩游戏
void GameRun(pSnake ps);//移动蛇
void SnakeMove(pSnake ps);//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext);//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext);//不是食物
void NoFood(pSnake ps, pSnakeNode pNext);//撞墙
void KillByWall(pSnake ps);//撞到自己
void KillBySelf(pSnake ps);//游戏结束
void GameEnd(pSnake ps);

4.3 Snake.c

#define _CRT_SECURE_NO_WARNINGS 1#include"Snake.h"//隐藏光标
void HideCurSor()
{CONSOLE_CURSOR_INFO cursor = { 0 };HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &cursor);cursor.bVisible = false;SetConsoleCursorInfo(handle, &cursor);
}//设置光标位置
void SetPos(int x, int y)
{COORD pos = { x,y }; //要设置的位置HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//哪个设备SetConsoleCursorPosition(handle, pos);//设置
}void Welcome()
{//显示欢迎语一SetPos(38, 15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40, 24);system("pause");system("cls");//清屏//提示语SetPos(25, 15);printf("按 ↑、↓、←、→ 控制蛇蛇的移动,F1为加速,F2为减速,");SetPos(25, 16);printf("加速将能得到更高的分数");SetPos(40, 24);system("pause");
}//打印地图
void DrawMap()
{system("cls");SetColor(6);SetPos(0, 0);//上for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);//左、右for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);SetPos(56, i);wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);  SetColor(10);
}//显示提示信息
void HelpInfo()
{SetPos(65, 18);printf("不能穿墙,不能碰到自己,");SetPos(65, 19);printf("按 ↑、↓、←、→ 控制蛇蛇的移动,");SetPos(65, 21);printf("F1为加速,F2为减速,");SetPos(65, 22);printf("ESC:退出游戏 SPACE:暂停");
}//窗口相关设置
void ScreenPrepare()
{//设置窗体大小system("mode con cols=100 lines=40");system("title 贪吃蛇小游戏");//隐藏光标HideCurSor();//打印欢迎语Welcome();//打印地图DrawMap();//显示提示信息HelpInfo();
}//设置颜色
void SetColor(int HeadColor)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor);
}//打印蛇
void PrintSnake(pSnake ps)
{pSnakeNode cur = ps->pSnake;int flag = 1;while (cur){if (flag == 1){//为蛇头设置颜色SetColor(12);}SetPos(cur->x, cur->y);//设置每个节点的位置wprintf(L"%lc", BODY);cur = cur->next;flag = 0;SetColor(10);}
}//初始化蛇
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//蛇的长度开始为3for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc");return;}cur->next = NULL;cur->x = POS_X + i * 2;  //节点横坐标依次增加2cur->y = POS_Y;//头插法将节点相连if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇PrintSnake(ps);//初始化蛇的其它信息ps->Dire = RIGHT;ps->FoodScore = 10;ps->SleepTime = 200;ps->State = OK;ps->Score = 0;
}//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//食物坐标应该在墙体内
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//食物坐标不与蛇重叠pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//根据坐标创建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreatFood():malloc()");return;}//打印食物pFood->x = x;pFood->y = y;SetColor(14);SetPos(pFood->x, pFood->y);wprintf(L"%lc", FOOD);SetColor(10);ps->pFood = pFood;
}//游戏准备
void GameStart(pSnake psnake)
{//设置好窗口、地图ScreenPrepare();//初始化蛇InitSnake(psnake);//创建食物CreatFood(psnake);
}//暂停
void pause()
{while (1){//一直休眠,直到再次按空格键Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}//玩游戏
void GameRun(pSnake ps)
{do{//显示分数SetColor(12);SetPos(65, 12);printf("目前得分:%-5d",ps->Score);SetColor(10);SetPos(65, 13);printf("每个食物:%2d分",ps->FoodScore);//检测按键if (KEY_PRESS(VK_UP) && ps->Dire != DOWN){//按上键,且蛇的方向不能向下ps->Dire = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP){//按下键,且蛇的方向不能向上ps->Dire = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT){//按左键,且蛇的方向不能向右ps->Dire = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT){//按右键,且蛇的方向不能向左ps->Dire = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){//按ESC,退出游戏ps->State = END;}else if (KEY_PRESS(VK_SPACE)){//按空格,暂停pause();}else if (KEY_PRESS(VK_F1)){//按F1加速,即睡眠时间变短//休眠时间不能是负数,最快就是休眠30msif (ps->SleepTime >= 50){ps->SleepTime -= 20;//速度变快,食物的分数变高ps->FoodScore += 2;}}else if (KEY_PRESS(VK_F2)){//F2减速,睡眠时间变长//食物的分数不能减到负数,最多减为1分if (ps->FoodScore >= 3){ps->SleepTime += 20;ps->FoodScore -= 2;}}//按照蛇的睡眠时间,真正实现休眠Sleep(ps->SleepTime);//休眠后,蛇要移动SnakeMove(ps);} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}//移动蛇
void SnakeMove(pSnake ps)
{//先产生节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc()");return;}//根据蛇的方向,设定节点的位置switch (ps->Dire){case UP://蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN://蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT://蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT://蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个节点是否是食物if (JudgeNext(ps, pNext)){//是食物,吃掉,长度增加EatFood(ps, pNext);}else{//不是食物,吃掉,长度不增加NoFood(ps, pNext);}//撞墙KillByWall(ps);//撞自己KillBySelf(ps);
}//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{//吃掉食物,头插法将节点插入pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇PrintSnake(ps);//加分ps->Score +=  ps->FoodScore;//释放食物节点free(ps->pFood);//再次创建食物CreatFood(ps);
}//不是食物
void NoFood(pSnake ps, pSnakeNode pNext)
{//头插法连接pNext->next = ps->pSnake;ps->pSnake = pNext;//删除蛇尾pSnakeNode cur = ps->pSnake;//找到蛇尾的前一个节点while (cur->next->next){cur = cur->next;}pSnakeNode del = cur->next;//将蛇尾打印为空格SetPos(del->x, del->y);printf("  ");//释放蛇尾节点free(del);cur->next = NULL;//打印蛇PrintSnake(ps);
}//撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->State = KILL_BY_WALL;}
}//撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->State = KILL_BY_SELF;}cur = cur->next;}
}//游戏结束
void GameEnd(pSnake ps)
{SetPos(20, 13);SetColor(12);switch (ps->State){case END:printf("您结束了游戏");break;case KILL_BY_SELF:printf("很遗憾!您撞到了自己");break;case KILL_BY_WALL:printf("很遗憾!您撞墙了");break;}pSnakeNode cur = ps->pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps = NULL;SetColor(10);}

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

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

相关文章

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编 一、Switch语句1、switch语句 是if语句的简写2、break加与不加有什么特点?default语句可以省略吗&#xff1f;3、游戏中的switch语句&#xff08;示例&#xff09;4、添加case后面的值&#xff0c;一个一个增加&…

深兰科技入选亿欧《“制”敬不凡先锋榜·智能机器人Top10》榜单

日前&#xff0c;由亿欧协办的2023工博会工业智能化发展高峰论坛于上海成功举办&#xff0c;会上发布了《2023智能制造&#xff1a;“制”敬不凡先锋者》系列名单。深兰科技凭借在智能机器人开发中的技术创新和模式应用&#xff0c;入选《“制”敬不凡先锋榜——智能机器人Top1…

前端大厂面试题探索编辑部——第二期

目录 题目 单选题1 题解 关于TCP 关于UDP 单选题2 题解 A选项的HTTP是否是无状态协议 B选项的HTTP支持的方法 C选项的关于HTTP的状态码 D选项HTTP协议的传输格式 题目 单选题1 1.以下哪个描述是关于 TCP 和 UDP 的区别&#xff08;&#xff09; A. TCP 是无连接的…

行测-资料:3. 比重、平均数

1、比重 1.1 现期比重★★★ C A&#xff0c;16.63%≈1/6 B C&#xff0c;拆成 50% 和 6.6% ≈ 1/15。 C D 1.2 基期比重★ 数学推导&#xff0c;A&#xff0c;B&#xff0c;A/(1 a)&#xff0c;B / (1 b) A&#xff0c;4 / 9&#xff0c;12 / 27 x 1.14 / 1.18&#xff0c;看…

【大数据】Flink 架构(四):状态管理

Flink 架构&#xff08;四&#xff09;&#xff1a;状态管理 1.算子状态2.键值分区状态3.状态后端4.有状态算子的扩缩容4.1 带有键值分区状态的算子4.2 带有算子列表状态的算子4.3 带有算子联合列表状态的算子4.4 带有算子广播状态的算子 在前面的博客中我们指出&#xff0c;大…

WinRAR压缩包高级技巧:永久设置压缩包单个或批量单独压缩成包并且不内嵌文件夹,解压保留原始时间设置

目录点击跳转&#xff1a;WinRAR压缩包高级技巧&#xff1a;永久设置压缩包单个或批量单独压缩成包并且不内嵌文件夹&#xff0c;解压保留原始时间设置 解压永久设置1 解压保存原始时间 压缩永久设置1 默认压缩成zip手机电脑都通用的格式2 默认压缩文件不多额外嵌套一层文件夹&…

【新书推荐】3.1节 布尔运算

本节内容&#xff1a;布尔运算&#xff0c;又称为逻辑运算或位运算。 ■布尔代数&#xff1a;and与、or或、not非、xor异或&#xff0c;按位运算。 3.1.1 布尔代数 ■布尔代数与二进制的关系 乔治布尔是一位英国小学数学老师&#xff0c;19世纪最重要的数学家之一。出版了《…

《HTML 简易速速上手小册》第2章:HTML 的标签和元素(2024 最新版)

文章目录 2.1 文本格式化标签&#xff08;&#x1f3a9;✨&#x1f4dc; 网页的“时尚搭配师”&#xff09;2.1.1 基础示例&#xff1a;一篇博客的格式化2.1.2 案例扩展一&#xff1a;产品介绍页面2.1.3 案例扩展二&#xff1a;个人简历 2.2 链接和锚点&#xff08;&#x1f6a…

matplotlib实现动画效果

实现正弦波动画 import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import numpy as np# 创建图像和轴 fig, ax plt.subplots()# 生成平均分布在0~2*pi之间的100个坐标点 x_data np.linspace(0, 2 * np.pi, 100) # 画出初始图 line, ax.plo…

【漏洞复现】中移铁通禹路由器信息泄露漏洞

Nx01 产品简介 中移禹路由器支持宽带拨号、动态IP和静态IP三种上网模式,一般中国移动宽带的光猫都是智能光猫也就是光猫带路由器功能,中移禹路由器作为二级路由使用。 Nx02 漏洞描述 中移禹路由器ExportSettings处存在信息泄露漏洞&#xff0c;攻击者可以获取后台权限。 Nx03…

sqli.labs靶场(8-17关)

8、第八关&#xff08;布尔盲注&#xff09; id1显示You are in...........&#xff0c;id1单引号不显示&#xff0c;id1 --显示正常 这个应该是单引号闭合&#xff0c;接下来就和第七关差不多上脚本 爆库名长度&#xff1a;id1%27%20and%20length(database()){i}%20-- 爆库…

算法分析(概论)

目录 第一章 概论 1.算法的概念 1.定义 2.算法设计要求 3.算法的特性 4.算法描述 5.数据结构与算法 6.算法设计的基本步骤 2.算法分析 1.计算机资源 2.算法分析 3.评判算法效率的方法 4.算法时间复杂度分析 5.渐进符号 1.大Ο符号 2.大Ω符号 3.大Θ符号 4.三…

bert预训练模型下载

查看 bert 模型所支持的预训练模型有哪些 from transformers import BERT_PRETRAINED_MODEL_ARCHIVE_LIST print(BERT_PRETRAINED_MODEL_ARCHIVE_LIST) 运行结果会吧所有支持的模型打印出来&#xff0c;比如 bert-base-chinese 一般下载模型我们搜索到的都是去 https://hugg…

详解顺序结构双指针处理算法

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

MySQL中使用percona-xtrabackup工具 三种备份及恢复 (超详细教程)

CSDN 成就一亿技术人&#xff01; 今天讲讲再MySQL中使用percona-xtrabackup这个开源工具来实现在线备份。 CSDN 成就一亿技术人&#xff01; 目录 介绍percona-xtrabackup 安装Percona 完整备份 备份流程 恢复流程 1.模拟文件损坏 2.滚回日志 3.恢复数据目录 4.授权…

可解释性人工智能(XAI)概述

文章目录 每日一句正能量前言可解释性人工智能&#xff08;XAI&#xff09;定义研究的作用应用领域XAI的目标后记 每日一句正能量 一个人若想拥有聪明才智&#xff0c;便需要不断地学习积累。 前言 人工智能&#xff08;AI&#xff09;的发展速度迅猛&#xff0c;并在许多领域…

【JavaScript】ECMA6Script es6

文章目录 一、 es6的介绍二、 es6的变量和模板字符串2.1 let 与 var2.2 const 与 var2.3 模板字符串 三、 es6的解构表达式四、 es6的箭头函数4.1 声明和特点4.2 实践和应用场景4.3 rest和spread 五、es6的对象创建和拷贝5.1 对象创建的语法糖5.2 对象的深拷贝和浅拷贝 六、es6…

Qt扩展-QXlsx读写Excel配置使用

QXlsx读写Excel配置使用 一、概述1. 功能概述2. 其他维护 二、安装1. 下载源码2. 配置项目3. 测试代码4. 运行结果 一、概述 项目介绍&#xff1a;https://qtexcel.github.io/QXlsx/Example.html GitHub&#xff1a;https://github.com/QtExcel/QXlsx/tree/master QXlsx 是一个…

2024年最适合开Palworld的游戏服务器

如果要开Palworld服务器&#xff0c;当然要选大内存的服务器 在雨云&#xff0c;你不仅可以 链接&#xff1a;雨云 - 新一代云服务提供商欢迎来到以用户体验为优先的雨云&#xff0c;我们提供稳定高速的国际虚拟主机&#xff0c;云服务器产品&#xff0c;强大的功能&#xff…

WindowsOS

C:. ├─PerfLogs&#xff0c;系统日志文件夹 ├─Program Files&#xff0c;程序文件 ├─Program Files&#xff08;x86&#xff09;&#xff0c;程序文件&#xff08;x86&#xff09; ├─ProgramData&#xff0c;程序数据 ├─Windows&#xff0c;Windows系统文件夹 └─Us…