项目小游戏-贪吃蛇

目录

 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;接下来看看具体怎么做吧~ 自定义…

网络原理-UDP和TCP

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

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;注意&…

【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&…

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

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

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

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

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.…

Spring Boot中接收各种各样的参数

一、接收json参数&#xff0c;封装为Map 1.1、核心代码 /*** 接收json参数&#xff0c;封装为Map* param servletRequest* return* throws Exception*/ PostMapping("/getParam") public R getParam(HttpServletRequest servletRequest) throws Exception {Map<…

Bootstrap 5 保姆级教程(十二):弹出框 消息弹窗

一、弹出框 1.1 创建弹出框 通过向元素添加 data-bs-toggle"popover" 来来创建弹出框。 title 属性的内容为弹出框的标题&#xff0c;data-bs-content 属性显示了弹出框的文本内容&#xff1a; 注意: 弹出框要写在 JavaScript 的初始化代码里。 以下实例可以在文…

投入产出表的分析要点有哪些

投入产出分析是利用投入产出表、投入产出系数和投入产出模型&#xff0c;对国民经济各部门之间的技术经济联系和影响进行分析的一种经济数据分析方法。 一、什么是投入产出表 我国的投入产出表是描述国民经济中各种产品的来源与使用去向的棋盘式平衡表 , 是产品部门 产品部门…

【已解决】电脑设置notepad++默认打开txt

1、以管理员的方式打开notepad 步骤&#xff1a;打开设置 -> 首选项 -> 文件关联 2、 设置Notepad默认打开 按照以下步骤将Notepad设置为默认打开.txt文件&#xff1a; 右键单击任何一个.txt文件。选择“属性”。在“常规”选项卡中&#xff0c;找到“打开方式”&#…

【Interconnection Networks 互连网络】Dragonfly Topology 蜻蜓网络拓扑

蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数2. Topology Description 拓扑描述3. Topology Variations 拓扑变体 蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数 Dragonfly拓扑参数&#xff1a; N N N: 网络中终端(terminal)的总数量 p p p: 连接到每个路由器的终端数量 a a a: 每…

VR全景:为户外游玩体验插上科技翅膀

随着VR全景技术的愈发成熟&#xff0c;无数人感到惊艳&#xff0c;也让各行各业看到了一片光明的发展前景。尤其是越来越多的文旅景区开始引入VR全景技术&#xff0c;相较于以往的静态风景图&#xff0c;显然现在的VR全景结合了动态图像和声音更加吸引人。 VR全景技术正在逐步改…

Dijkstra算法求最短路

Dijkstra算法可以在图中寻找一个节点&#xff08;称为“源节点”&#xff09;到所有其它节点的最短路径。 文章目录 前言 一、Dijkstra算法是什么&#xff1f; 二、问题介绍 三、朴素版Dijkstra算法 1.图的存储 2.算法实现 四、使用步骤 1.代码如下&#xff08;示例&#xff09…

Linux的UDEV机制

udev 机制引入&#xff1a; 手机接入Linux热拔插相关 a. 把手机接入开发板 b. 安装adb工具&#xff0c;在终端输入adb安装指令&#xff1a; sudo apt-get install adb c. dmeg能查看到手机接入的信息&#xff0c;但是输入adb devices会出现提醒 dinsufficient permissions for …

【Java】HashMap、HashTable和ConcurrentHashMap的区别

文章目录 区别一、HashMap1.1基本定义与特性1.2工作原理与实现1.3常用方法1.4性能与优化 二、HashTable三、ConcurrentHashMap3.1基本特点3.2实现原理3.3常用方法3.4适用场景3.5性能优化 HashTable、HashMap和ConcurrentHashMap之间的区别主要体现在线程安全、继承关系与实现接…