项目小游戏-贪吃蛇

目录

 1.游戏开始  - GameStart

1.1cmd命令窗口

调节窗口命令  

​编辑更改窗口命名 

​编辑

1.2 Win32 API

 win32 API 的介绍: ​编辑

 获取控制台坐标COORD

 获取控制台句柄:

 获取缓冲台光标信息:

获取虚拟键位: 

本地初始化 setlocale();

 游戏开始的具体实现:

 光标隐藏和窗口的设置

 打印环境和功能介绍 

 绘制地图 

创建蛇 

 创建食物 

 游戏运行 - GameRun

 帮助信息的打印

​编辑 

 检测按键的情况操控蛇的身体

 判断蛇下一步走向是食物还是没有食物

 判断蛇每走一步状态是否正常

结束游戏 - 善后工作 - GameEnd();

实现代码参考:

 头文件 - > Snack.h

 源文件  - > Snack.c

 测试文件 - > text.c


前言:本次给大家带来的是贪吃蛇小游戏项目的实现!项目实现中会涉及C语言语法,WIn32 API ,

链表,本地化设置等等,下面就跟着我一起来实现吧!

 游戏实现效果呈现:

贪吃蛇游戏展现

 游戏流程设计:

本次游戏程序的实现分为3个部分,游戏开始;游戏运行;游戏结束,下面大家就跟着我来一个一个实现吧! 

 1.游戏开始  - GameStart

首先我们来认识几个知识点:

  1. 为了实现我们贪吃蛇游戏的运行界面,我们使用windows电脑的cmd命令窗口来展现我们贪吃蛇游戏的界面运行.
  2. 在运行贪吃蛇的过程中我们需要打印欢迎界面,功能界面,以及贪吃蛇的地图,贪吃蛇,获取操控贪吃蛇的按键状态等等
  3. 为了打印地图和贪吃蛇的外观,食物我们需要用到宽字符,需要对编译器本地化.

1.1cmd命令窗口

调节窗口命令  

system("mode con cols=100 lines=30");

为了更加方便的游玩游戏,我们需要一个合适大小的窗口,以上的代码命令可以更改cmd窗口的大小,

cols代表x轴的长度,lines代表的是y轴的长度.

注意:cmd命令窗口的x轴和y轴和原本的坐标系不同,下图作为解析: 

更改窗口命名 

system("title 贪吃蛇");

 title 后面的名称可以是中文也可以是英文.

以上两个命令准备使用的都是system函数,以下是更多信息介绍: 

1.2 Win32 API

 win32 API 的介绍: 

 获取控制台坐标COORD

我们前面了解了命令窗口cmd的坐标关系,那么怎么获得每个点位具体的坐标呢,我们就需要使用到win32 API 中的COORD结构体.

COORD: 

获取坐标: 

// 获取控制台坐标
COODR pos = {x, y};
//pos 是我们自定义的变量
//x, y 分别是坐标轴上的x和y坐标
 获取控制台句柄:

我们知道要操控一个东西,需要一定的物品或者条件,如我们要开车,那么方向盘就必不可少,那么我们要操作命令行cmd窗口又需要什么呢?答案是句柄!

HANDLE hOutput = GetStdhandle(STD_OUTPUT_HANDLE);
 获取缓冲台光标信息:

我们每次使用cmd窗口时,会有光标闪烁提示我们打印的方位接下来将从什么地方开始,但是我们运行贪吃蛇游戏时不希望出现光标,那么我们需要对光标进行操作将其隐藏起来! 

注:光获取光标信息还不够,我们要在获取后更改信息再设置一遍,就好比你打游戏时更改键位要按保存一样! 

设置定位光标位置: 

在贪吃蛇游戏中我们的贪吃蛇是时刻走动的,地图的范围也是固定,周围的提示信息也需要在合适的位置打印,这些都离不开定位光标的位置,因此我们需要一个可以定位光标位置的封装函数,结合前面所介绍的,我们就可以进行实现了 

// 定位光标位置
void SetPos(short x, short y)
{// 获取句柄HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);// 定位坐标COORD pos = {x, y};// 定位光标位置SetConsoleCursorPosition(houtput, pos);}
获取虚拟键位: 

我们知道贪吃蛇游戏中需要玩家使用键盘上的上下左右对贪吃蛇进行操作,那么计算机又是如何判断我们是否按下了相应的键位呢?这就离不开获取虚拟键位的函数:GetAsyncKeyState

注:为了方便使用我们将其定义为宏. 

 键位的虚拟值定义查询:GetAsyncKeyState 函数 (winuser.h) - Win32 应用 |Microsoft学习

本地初始化 setlocale();

在贪吃蛇游戏的实现中,我们需要打印一些宽字符作为地图的围墙 -> □ ,还有贪吃蛇的身体 -> ●,食物的形状 -> ★ ◆ ▲等等,但是这些字符在C语言底层的并不存在,因为C的来源是美国,其他国家后来对应的字符,语言都是经过后续添加进入的,所以我们要使用宽字符就需要先切换为本地模式(开始固定为C模式)。

// 设置适配本地环境
setlocale(LC_ALL, "");

 游戏开始的具体实现:

了解以上的知识后我们就可以开始实现我们的游戏开始模块的代码了,分为以下几个步骤: 

  •          0.光标隐藏和窗口的设置
  •         1.打印环境和功能介绍 
  •         2.绘制地图 
  •         3.创建蛇 
  •         4.创建食物 

 光标隐藏和窗口的设置

//窗口的设置
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);

注意:小编是函数封装的,单独测试的小伙伴记得使用main函数,并且包含头文件 :

#include <Windows.h>

设置完毕后运行的状况以下:  

 打印环境和功能介绍 

展示如下:  

// 欢迎界面的打印
void WelcomeToGame()
{SetPos(35, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(38, 20);system("pause");system("cls");SetPos(28, 14);wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(28, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(38, 20);system("pause");system("cls");
}

代码中的SetPos(); 函数我们在前面已经介绍,用于定位坐标。

// wprintf 的使用方法和printf相同, 只是这边用于中文打印两者都可以打印,这边提前使用是为了与后面宽字符打印统一printf("您好! CSDN");
wprintf(L"您好! CSDN");

 system中的pause用于暂停 -> 防止程序直接结束,cls用于清屏。

 绘制地图 

在使用代码打印地图之前,我们需要考虑好要绘制多大的地图,并且我们要知道蛇的身体和蛇的食物是宽字符组成,需要占两个字节的位置,所以我们地图的内部需要是偶数成对的方块,不然会出现蛇的身体一半在墙内一半墙外,食物一半墙内一半墙外的情况。 

以下是我的地图设计: 

 有了设计图纸后我们创建打印方面就很简单了,分为上下左右四个区域进行打印,要注意打印的个数和打印的坐标(使用SetPos函数调节)。

#define WALL L'□'
// 3.绘制地图 
void CreateMap()
{// 上int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}// 下SetPos(0, 26);for (i = 0; i < 29; i++){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);}}

创建蛇 

地图绘制完毕后,我们就需要考虑贪吃蛇的创建了,并且我们需要将贪吃蛇打印在地图中。

 首先我们创建的贪吃蛇需要考虑以下几个点:

1.贪吃蛇的身体应由链表组成,所以我们需要创建贪吃蛇的身体节点,并且存放坐标以及指向下一截身体的指针。 

// 创建贪吃蛇身的节点
typedef struct SnackNode
{// 坐标int x;int y;// 指向下一个节点的指针struct SnackNode* next;
}SnackNode, * pSnackNode;

2.我们需要创建面向对象:贪吃蛇 

贪吃蛇对象应该包含以下几点:

  1. 指向贪吃蛇头的结构体指针;用来判断贪吃蛇的方向。
  2. 指向贪吃蛇所吃食物的结构体指针;方便贪吃蛇吃下食物。 
  3. 贪吃蛇的方向;贪吃蛇是向上向下还是左或者右。
  4. 贪吃蛇的状态;贪吃蛇是否是正常运行,撞墙死亡,撞自己死亡,正常退出游戏。
  5. 总分数的显示;
  6. 每个食物的分数;
  7. 睡眠时间;要实现贪吃蛇移动需要让我们的视觉看到一会打印蛇身一会消失向前打印,这就需要使用睡眠程序实现。
// 面向对象:贪吃蛇
typedef struct Snake
{pSnackNode _pSnake;  // 指向蛇头的指针pSnackNode _pFood;   // 指向食物节点的指针enum DIRECTION _dir; // 蛇的方向enum GAME_STATUS _status; //蛇的状态int _food_weight; // 一个食物的分数int _score;		// 总分数int _sleep_time; // 休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;

以下是对象蛇的类型声明: 

// 蛇的状态
// 正常, 撞墙, 撞到自己, 正常退出
enum GAME_STATUS
{OK,				//正常KILL_BY_WALL,	//撞墙KILL_BY_SELF,	//撞到自己END_NORMAL		//正常退出
};// 蛇的方向
enum DIRECTION
{UP = 1, // 上DOWN,   // 下LEFT,   // 左RIGHT   // 右
};

 初始化蛇:

创建好后我们需要初始化蛇,其中包括蛇的身体连接,蛇身初始的打印位置,蛇的方向,蛇的状态,总分数,食物分数,打印蛇身等等。 

#define POS_X 24
#define POS_y 5
#define BODY L'●'
#define FOOD L'★'
#define FOOD1 L'◆'
#define FOOD2 L'▲'// 4.创建蛇 
void InitSnack(pSnake ps)
{int i = 0;pSnackNode cur = NULL;// 创建五个节点(蛇身)for (i = 0; i < 5; i++){cur = (pSnackNode)malloc(sizeof(SnackNode));// 申请失败if (cur == NULL){perror("InitSnack()::malloc()");return;}// 申请成功cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_y;// 头插法插入if (ps->_pSnake == NULL)// 空链表{ps->_pSnake = cur;}else // 非空链表{cur->next = ps->_pSnake;ps->_pSnake = cur;}}// 遍历打印cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 设置贪吃蛇的属性ps->_dir = RIGHT; // 默认向右ps->_score = 0;		//总分数ps->_food_weight = 10; // 食物的分数ps->_sleep_time = 200;// 单位毫秒ps->_status = OK; // 蛇的状态}

 创建食物 

 首先在创建食物之前我们需要明确,食物的生成是随机的,在地图内的,生成的坐标要是倍数坐标,这样蛇才可以吃到,随机方面我们就需要使用到随机函数rank,但是系统的随机是伪随机,要做到真正的随机我们还需要使用到时间戳,并且设定一下。生成的过程中我们不可以与蛇的身体冲突,最后进行打印,然后传入面向对象贪吃蛇统一管理。

// 设定随机rank
srand((unsigned int)time(NULL));
// 5.创建食物 
void CreateFood(pSnake ps)
{// 坐标int x = 0;int y = 0;// 生成x是2的倍数// x: 2-54// y: 1-25again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);// x和y的坐标不可以和蛇冲突pSnackNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){// 如果冲突重新生成食物goto again;}cur = cur->next;}// 创建食物节点pSnackNode pFood = (pSnackNode)malloc(sizeof(SnackNode));// 创建失败if (pFood == NULL){perror("CreateFood()::malloc()");return;}// 创建成功pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);//定位打印// 根据不同分数生成不同形状的食物if (ps->_score <= 50){wprintf(L"%lc", FOOD);}else if (ps->_score > 50 && ps->_score <= 100){wprintf(L"%lc", FOOD1);ps->_food_weight += 2;}else if (ps->_score > 100){wprintf(L"%lc", FOOD2);ps->_food_weight += 4;}// 将设定好的食物信息传入面向对象贪吃蛇中ps->_pFood = pFood;
}

最后效果:  

 游戏运行 - GameRun

 这一个环节主要实现以下内容:

  1. 帮助信息的打印
  2. 检测按键的情况操控蛇的身体
  3. 判断蛇下一步走向是食物还是没有食物
  4. 判断蛇每走一步状态是否正常

 帮助信息的打印

//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用 ↑. ↓. ←. → 来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L",按F3加速,F4减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");SetPos(64, 18);wprintf(L"%ls", L"@阳区欠出品");}// 打印总分数和食物的分数
SetPos(64, 10);
printf("总分数:%04d", ps->_score);
SetPos(64, 11);
printf("当前食物的分数:%02d", ps->_food_weight);
SetPos(64, 12);
printf("当前蛇的速度:%4d(毫秒)", ps->_sleep_time);

实现界面如下: 

 

 检测按键的情况操控蛇的身体

 前面我们提到检测按键的Win32 API 中包含的函数,我们可以先封装一个宏。

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
// 检测按键
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)// 向上走的时候蛇不可以向下
{ps->_dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)// 向下走的时候蛇不可以向上
{ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)// 向左走的时候蛇不可以向右
{ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)// 向右走的时候蛇不可以向左
{ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))//空格暂停
{// 暂停Pause();
}
else if (KEY_PRESS(VK_ESCAPE))// ESC退出游戏
{// 正常退出游戏ps->_status = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))// F3加速
{// 加速 -> 为了防止无限加速设定条件if (ps->_sleep_time > 100){ps->_sleep_time -= 30;ps->_food_weight += 2;}
}
else if (KEY_PRESS(VK_F4))// F4减速
{// 减速  -> 为了防止无限减速设定条件if (ps->_sleep_time >= 80 && ps->_sleep_time <= 300){ps->_sleep_time += 30;ps->_food_weight -= 2;}
}

暂停实现: 

暂停的底层逻辑就是一直休眠程序,知道特定的条件解除。 

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

 判断蛇下一步走向是食物还是没有食物

// 判断下一个坐标是否是食物
int NextIsFood(pSnackNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}// 下一个是食物,吃掉
void EatFood(pSnackNode pn, pSnake ps)
{// 头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;// 释放下一个位置的节点free(pn);pn = NULL;// 打印pSnackNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;ps->_anger_num += 1;// 重新创建食物CreateFood(ps);
}// 下一个位置不是食物
void NoFood(pSnackNode pn, pSnake ps)
{// 头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnackNode cur = ps->_pSnake;//  找到最后一个节点的之前节点while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 把最后一个节点打印为空格SetPos(cur->next->x, cur->next->y);printf("  ");// 释放最后一个节点free(cur->next);// 把倒数第二个节点的地址置为NULLcur->next = NULL;
}

 判断蛇每走一步状态是否正常

// 检测蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = KILL_BY_WALL;}
}// 检测蛇是否撞到自己
void KillBySelf(pSnake ps)
{pSnackNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;}cur = cur->next;}
}

 以下是贪吃蛇走动的实现:

//贪吃蛇走一步->蛇的移动
void SnakeMove(pSnake ps)
{pSnackNode pNextNode = (pSnackNode)malloc(sizeof(SnackNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}// 注意:以下的x轴坐标必须是偶数倍的加减switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}// 检查下一个坐标是否是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}// 检测蛇是否撞墙KillByWall(ps);// 检测蛇是否撞到自己KillBySelf(ps);
}

结束游戏 - 善后工作 - GameEnd();

我们需要知道游戏内的蛇身是通过空间动态内存申请的,再结束后我们都需要释放掉,并且我们需要依据蛇的不同状态反馈玩家游戏结束的原因。 

// 结束游戏 - 善后工作
void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"你主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"你撞到了墙上,游戏结束了\n");break;case KILL_BY_SELF:wprintf(L"你撞到了自己,游戏结束了\n");break;}SetPos(0, 26);// 释放蛇的身体pSnackNode cur = ps->_pSnake;while (cur){pSnackNode del = cur;cur = cur->next;free(del);}}

实现代码参考:

  1.  头文件 - > Snack.h
  2. 源文件  - > Snack.c
  3. 测试文件 - > text.c

 头文件 - > Snack.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <Windows.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>#define POS_X 24
#define POS_y 5
#define BODY L'●'
#define WALL L'□'
#define FOOD L'★'
#define FOOD1 L'◆'
#define FOOD2 L'▲'// 类型的声明// 蛇的状态
// 正常, 撞墙, 撞到自己, 正常退出
enum GAME_STATUS
{OK,				//正常KILL_BY_WALL,	//撞墙KILL_BY_SELF,	//撞到自己END_NORMAL		//正常退出
};// 蛇的方向
enum DIRECTION
{UP = 1, // 上DOWN,   // 下LEFT,   // 左RIGHT   // 右
};// 创建贪吃蛇身的节点
typedef struct SnackNode
{// 坐标int x;int y;// 指向下一个节点的指针struct SnackNode* next;
}SnackNode, * pSnackNode;// 面向对象:贪吃蛇
typedef struct Snake
{pSnackNode _pSnake;  // 指向蛇头的指针pSnackNode _pFood;   // 指向食物节点的指针enum DIRECTION _dir; // 蛇的方向enum GAME_STATUS _status; //蛇的状态int _food_weight; // 一个食物的分数int _score;		// 总分数int _sleep_time; // 休息时间,时间越短,速度越快,时间越长,速度越慢int _anger_num; // 蛇的怒气
}Snake, * pSnake;// 函数的声明// 初始化游戏
void GameStart(pSnake ps);// 欢迎界面的打印
void WelcomeToGame();//定位光标位置
void setpos(short x, short y);// 3.绘制地图 
void CreateMap();// 4.初始化蛇
void InitSnack(pSnake ps);// 5.创建食物 
void CreateFood(pSnake ps);// 运行游戏的逻辑
void GameRun(pSnake ps);//贪吃蛇走一步->蛇的移动
void SnakeMove(pSnake ps);// 判断下一个坐标是否是食物
int NextIsFood(pSnackNode pn, pSnake ps);// 下一个是食物,吃掉
void EatFood(pSnackNode pn, pSnake ps);// 下一个位置不是食物
void NoFood(pSnackNode pn, pSnake ps);// 检测蛇是否撞墙
void KillByWall(pSnake ps);// 检测蛇是否撞到自己
void KillBySelf(pSnake ps);// 结束游戏 - 善后工作
void GameEnd(pSnake ps);

 源文件  - > Snack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "snack_game.h"//定位光标位置
void SetPos(short x, short y)
{// 获得坐标COORD pos = { x, y };// 获取标准输出设备的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);// 定位光标位置SetConsoleCursorPosition(houtput, pos);
}// 欢迎界面的打印
void WelcomeToGame()
{SetPos(35, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(38, 20);system("pause");system("cls");SetPos(28, 14);wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(28, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(38, 20);system("pause");system("cls");
}// 3.绘制地图 
void CreateMap()
{// 上int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}// 下SetPos(0, 26);for (i = 0; i < 29; i++){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.创建蛇 
void InitSnack(pSnake ps)
{int i = 0;pSnackNode cur = NULL;// 创建五个节点for (i = 0; i < 5; i++){cur = (pSnackNode)malloc(sizeof(SnackNode));if (cur == NULL){perror("InitSnack()::malloc()");return;}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_y;// 头插法插入if (ps->_pSnake == NULL)// 空链表{ps->_pSnake = cur;}else // 非空链表{cur->next = ps->_pSnake;ps->_pSnake = cur;}}// 遍历打印cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 设置贪吃蛇的属性ps->_dir = RIGHT; // 默认向右ps->_score = 0;		//总分数ps->_food_weight = 10; // 食物的分数ps->_sleep_time = 200;// 单位毫秒ps->_status = OK; // 蛇的状态}// 5.创建食物 
void CreateFood(pSnake ps)
{// 坐标int x = 0;int y = 0;// 生成x是2的倍数// x: 2-54// y: 1-25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);// x和y的坐标不可以和蛇冲突pSnackNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}// 创建食物节点pSnackNode pFood = (pSnackNode)malloc(sizeof(SnackNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);//定位if (ps->_score <= 50){wprintf(L"%lc", FOOD);}else if (ps->_score > 50 && ps->_score <= 100){wprintf(L"%lc", FOOD1);ps->_food_weight += 2;}else if (ps->_score > 100){wprintf(L"%lc", FOOD2);ps->_food_weight += 4;}ps->_pFood = pFood;
}// 初始化游戏
void GameStart(pSnake ps)
{// 0.光标隐藏和窗口的设置//窗口的设置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);// 1.打印环境界面和功能介绍WelcomeToGame(); 
// 3.绘制地图 CreateMap();
// 4.创建蛇 InitSnack(ps);
// 5.创建食物 CreateFood(ps);
}//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用 ↑. ↓. ←. → 来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L",按F3加速,F4减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");SetPos(64, 18);wprintf(L"%ls", L"@阳区欠出品");}#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}// 判断下一个坐标是否是食物
int NextIsFood(pSnackNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}// 下一个是食物,吃掉
void EatFood(pSnackNode pn, pSnake ps)
{// 头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;// 释放下一个位置的节点free(pn);pn = NULL;// 打印pSnackNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;ps->_anger_num += 1;// 重新创建食物CreateFood(ps);
}// 下一个位置不是食物
void NoFood(pSnackNode pn, pSnake ps)
{// 头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnackNode cur = ps->_pSnake;while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}// 把最后一个节点打印为空格SetPos(cur->next->x, cur->next->y);printf("  ");// 释放最后一个节点free(cur->next);// 把倒数第二个节点的地址置为NULLcur->next = NULL;
}// 检测蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = KILL_BY_WALL;}
}// 检测蛇是否撞到自己
void KillBySelf(pSnake ps)
{pSnackNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;}cur = cur->next;}
}//贪吃蛇走一步->蛇的移动
void SnakeMove(pSnake ps)
{pSnackNode pNextNode = (pSnackNode)malloc(sizeof(SnackNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}// 检查下一个坐标是否是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}// 检测蛇是否撞墙KillByWall(ps);// 检测蛇是否撞到自己KillBySelf(ps);
}// 运行游戏
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{// 打印总分数和食物的分数SetPos(64, 10);printf("总分数:%04d", ps->_score);SetPos(64, 11);printf("当前食物的分数:%02d", ps->_food_weight);SetPos(64, 12);printf("当前蛇的速度:%4d(毫秒)", ps->_sleep_time);// 检测按键if (KEY_PRESS(VK_UP) && ps->_dir != DOWN){ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){// 暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){// 正常退出游戏ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_F3)){// 加速if (ps->_sleep_time > 100){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){// 减速if (ps->_sleep_time >= 80 && ps->_sleep_time <= 300){ps->_sleep_time += 30;ps->_food_weight -= 2;}}//贪吃蛇走一步SnakeMove(ps);Sleep(ps->_sleep_time);} while (ps->_status == OK);
}// 结束游戏 - 善后工作
void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"你主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"你撞到了墙上,游戏结束了\n");break;case KILL_BY_SELF:wprintf(L"你撞到了自己,游戏结束了\n");break;}SetPos(0, 26);// 释放蛇的身体pSnackNode cur = ps->_pSnake;while (cur){pSnackNode del = cur;cur = cur->next;free(del);}}

 测试文件 - > text.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "snack_game.h"
#include <locale.h>// 游戏测试的逻辑
void text()
{int ch = 0;do{system("cls");// 创建贪吃蛇Snake snake = { 0 };// 初始化游戏// 0.光标隐藏和窗口的设置// 1.打印环境 // 2.功能介绍 // 3.绘制地图 // 4.创建蛇 // 5.创建食物 // 6.设置游戏相关信息GameStart(&snake);// 运行游戏GameRun(&snake);// 结束游戏 - 善后工作GameEnd(&snake);SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 27);system("cls");}int main()
{// 设置适配本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));text();return 0;
}

 

 

 

 

 

 

 

 

 

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

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

相关文章

使用AOP切面做防止用户重复提交功能

在我们的项目中&#xff0c;需要考虑到有时候因为网络原因或者其他原因用户对同一个接口进行同一批数据的重复性操作&#xff0c;如果不做这样的处理很可能会在数据库中添加多条同样的数据。 我们可以通过使用aop来解决这样的问题&#xff0c;接下来看看具体怎么做吧~ 自定义…

Java8中常用的stream方法,工作中经常用到【持续更新】

Java 8 中的 Stream API 提供了一种更加函数式和便捷的方式来处理集合数据。以下是一些常用的 Stream 方法&#xff0c;以及它们的简要说明和对应的例子。 filter(Predicate)&#xff1a;根据指定的条件过滤集合中的元素。 List<Integer> numbers Arrays.asList(1, 2, 3…

网络原理-UDP和TCP

在传输层中有两个非常重要的协议&#xff0c;UDP和TCP&#xff0c;现在就来研究一下这两个协议。 UDP 报文格式 我们观察可以发现&#xff0c;里面UDP报文长度为2个字节&#xff0c;那么是多少呢&#xff1f;我们需要快速反应如下固定字节数据类型的取值范围&#xff1a; 字…

数据结构与算法学习笔记四---队列的表示和实现(C++)

目录 前言 1.队列的顺序存储方式的实现 1.定义 2.队列初始化 3.销毁 4.队列是否为空 5.队列长度 6.清空队列 7.队列头元素 8.入队 9.出队 10.完整代码 2.队列的链式存储方式的实现 1.定义 2.队列初始化 3.销毁 4.队列是否为空 5.队列长度 6.清空队列 7.队列…

Jenkins服务器IP更换,Jenkins URL地址更换

服务器的网络地址发生变动&#xff0c;修改jenkins服务器IP地址后&#xff0c;jenkins网页能够打开&#xff0c;但是job中的配置钩子没有自动改变&#xff0c;如图所示&#xff1a; 经过查询资料了解&#xff0c;需要修改jenkins本地化配置地址才可以显示正确&#xff1a; 1、…

初识ansible变量及实例配置

目录 1、为什么要使用变量 2、变量分类 3、 变量详解 3.1 vars,vars_files , group_vars 3.1 .1 vars 剧本中定义变量 3.1.2 vars_file 将变量存放到一个文件中&#xff0c;并在剧本中引用 3.1.3 group_vars 创建一个变量文件给某个组使用 实例1-根据不同的主机…

Java关键字和API

1 this和super关键字 1.this和super的意义 this&#xff1a;当前对象 在构造器和非静态代码块中&#xff0c;表示正在new的对象 在实例方法中&#xff0c;表示调用当前方法的对象 super&#xff1a;引用父类声明的成员 无论是this和super都是和对象有关的。 2.this和sup…

通过实例学C#之序列化与反序列化XmlSerializer类

简介 可以将类序列化成xml文件&#xff0c;或者将xml文件反序列化成类对象&#xff0c;一般用于保存或加载项目参数。 构造函数 XmlSerializer() 不使用函数创建一个xmlSerializer对象。 XmlSerializer(Type type) 使用type对象创建一个xmlSerializer对象&#xff0c;注意&…

lv_table

通过点击lv_table的某一行来选中这一行&#xff0c;以及通过点击另外创建的按钮来删除选中的这一行数据。在table_event_cb回调函数中&#xff0c;我们通过检测点击事件发生的行和列来确定被点击的行&#xff0c;然后在按钮的事件处理器btn_event_cb中&#xff0c;根据之前保存…

洛谷 P3811 [模板] 模意义下的乘法逆元

【模板】模意义下的乘法逆元 题目描述 给定 n , p n,p n,p 求 1 ∼ n 1\sim n 1∼n 中所有整数在模 p p p 意义下的乘法逆元。 这里 a a a 模 p p p 的乘法逆元定义为 a x ≡ 1 ( m o d p ) ax\equiv1\pmod p ax≡1(modp) 的解。 输入格式 一行两个正整数 n , p n,…

Qt——Qt网络编程之获取本机网络信息(通过QHostInfo和QNetworkInterface 类获取本地网络所有接口信息)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》

asp.net core mvc 路由

在ASP.NET Core MVC中&#xff0c;路由是一项核心功能&#xff0c;它负责将进来的HTTP请求映射到相应的控制器和动作方法上。ASP.NET Core支持两种路由方式&#xff1a;传统的基于模板的路由&#xff08;约定路由&#xff09;和属性路由。 1. 基于约定的路由 在Startup.Confi…

【001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂】

001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂 文章目录 001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂创作背景通信模型ISO/OSI七层模型 和 TCP/IP四层模型网络通信数据包格式&#xff08;Ethernet II&…

【opencv GPU】测测你的opencv-GPU 版本每秒处理帧数 大概多少?

在使用OpenCV的cv2.dnn.Net类从ONNX模型文件创建网络时&#xff0c;如果你想要启用GPU加速&#xff0c;你需要确保OpenCV在安装时已经包含了GPU支持。这通常意味着你需要从源代码编译OpenCV&#xff0c;并确保在编译过程中启用了CUDA支持。 以下是如何在OpenCV中启用GPU加速的…

20240421阿夏的CSDN创作纪念日(3周年)

缘 提示&#xff1a;可以和大家分享最初成为创作者的初心 3年前我写下第一篇CSDN&#xff0c;开启了Python研究之旅win10系统64位&#xff08; 惠普&#xff09;台式电脑自动开机、关机图文详细解&#xff08;一&#xff09;_惠普主板设置自动开机-CSDN博客文章浏览阅读4.2k次…

对观察者模式的理解

目录 一、场景1、题目描述 【[案例来源](https://kamacoder.com/problempage.php?pid1075)】2、输入描述3、输出描述4、输入示例5、输出示例 二、实现三、更复杂的场景 【[案例来源](https://refactoringguru.cn/design-patterns/observer/java/example#example-0--listeners-…

深入OceanBase内部机制:资源隔离实现的方式总结

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 1. 为何HTAP需要资源隔离2. OceanBase的资源隔离机制概述租户间资源隔离租户内资源隔离物理资源隔离大查询请求的隔离优先级…

26.ELF文件解析

ELF文件及objdump/readelf命令 文章目录 ELF文件及objdump/readelf命令ELF文件结构分析使用od命令读取ELF文件使用readelf命令读取ELF文件使用objdump命令分析ELF文件reference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; ELF(Executa…

4月21敲一篇猜数字游戏,封装函数,void,无限循环,快去体验体验

今天敲一篇猜数字游戏 目录 今天敲一篇猜数字游戏 1.打开先学goto语句&#xff1a; 2.开干&#xff1a; 首次我们学习随机数&#xff1a; 讲解一下&#xff1a; 改用srand; 加入时间变量&#xff1a; 获取时间&#xff1a;哈​编辑 3.我本来想已近够完美了&#xff0…

Flink学习(七)-单词统计

前言 Flink是流批一体的框架。因此既可以处理以流的方式处理&#xff0c;也可以按批次处理。 一、代码基础格式 //1st 设置执行环境 xxxEnvironment env xxxEnvironment.getEnvironment;//2nd 设置流 DataSource xxxDSenv.xxxx();//3rd 设置转换 Xxx transformation xxxDS.…