项目小游戏-贪吃蛇

目录

 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.hqwc.cn/news/637189.html

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

相关文章

Java发送邮件 启用SSL

使用的maven依赖: <dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.4.7</version> </dependency> 配置文件mail.properties如下: # 邮箱配置 email.username=your-email@exa…

[USACO1.5] 八皇后 Checker Challenge

题目描述 检查一个如下的6 x 6的跳棋棋盘&#xff0c;有六个棋子被放置在棋盘上&#xff0c;使得每行&#xff0c;每列&#xff0c;每条对角线(包括两条主对角线的所有对角线)上都至多有一个棋子&#xff0c;如下例&#xff0c;就是一种正确的布局。 上面的布局可以用序列2 4…

在protobuf里定义描述rpc方法的类型

service UserServiceRpc //在test.proto中定义 { rpc Login(LoginRequest)returns(LoginResponse); rpc GetFriendLists(GetFriendListRequest)returns(GetFriendListResponse); } test.proto文件生成test.pb.cc protoc test.proto --cpp_out./ 将生成的…

货币银行学-货币政策

货币政策 货币政策目标体系货币政策工具如何通过提高短期货币市场利率来缩减货币供应量&#xff1f;法定存款准备金率的作用是什么&#xff1f;它的调整有哪些影响&#xff1f;在货币政策的操作程序中&#xff0c;如何通过公开市场操作影响基础货币&#xff1f;基础货币的构成是…

【网站项目】校园商铺系统小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

数据结构_时间复杂度

✨✨所属专栏&#xff1a;数据结构✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 什么是时间复杂度&#xff1f; 时间复杂度的定义&#xff1a;在计算机科学中&#xff0c;算法的时间复杂度是一个函数&#xff0c;它定量描述了该算法的运行时间。一个算法执行所耗费的时间&#xff0…

亚马逊---设计安全架构

会从以下三个方面展开&#xff1a; 1、AWS资源访问安全 2、应用程序负载的网络安全 3、云中数据的安全 责任共担模式 就像租房子&#xff08;房东和你的责任&#xff09; AWS资源访问安全 需要掌握以下几点&#xff1a; 1、跨多个账户的访问控制和管理 2、AWS联合访问和身份服…

【Linux】系统安全及应用

目录 一、账号安全基本措施 1.系统账号清理 2.密码安全控制 3.历史命令安全管理 4.限制su切换用户 1&#xff09;将信任的用户加入到wheel组中 2&#xff09;修改su的PAM认证配置文件 5.ssh远程登录输入三次密码错误则锁定用户 二、Linux中的PAM安全认证 1.su命令的…

竞逐智能家居大模型:美的“蓄力”,海尔“疾行”

配图来自Canva可画 随着ChatGPT火热出圈&#xff0c;AI大模型便成为了各行各业必争的高地。“BAT”等互联网大厂、华为、小米等通讯巨头&#xff0c;以及一些垂直AI公司&#xff0c;都开始在大模型市场积极布局。众所周知&#xff0c;发展大模型的关键在于应用场景的落地&…

常见现代卷积神经网络(ResNet, DenseNet)(Pytorch 11)

一 批量规范化&#xff08;batch normalization&#xff09; 训练深层神经网络是十分困难的&#xff0c;特别是在较短的时间内使他们收敛更加棘手。批量规范化&#xff08;batch normalization&#xff09;是一种流行且有效的技术&#xff0c;可持续加速深层网络的收敛速度。 …

谷歌google广告新手教程,看这一篇就足够!

谷歌Google广告是企业触达广大潜在客户、推动业务增长的强大渠道&#xff0c;对于初涉此领域的广告新手而言&#xff0c;掌握谷歌广告的基本操作、策略规划到优化技巧&#xff0c;无疑是开启成功营销之旅的关键。本文不仅为您提供一份详尽的谷歌广告入门指南&#xff0c;还将特…

单位信息宣传通讯稿件用对投稿方法轻松就能发表

在过去的日子里,作为单位的一员,我肩负着向各大媒体投放通讯稿件的重任。然而,这一过程却充满了曲折与辛酸。每次撰写完精心打磨的稿件,随之而来的并非是成就感,而是繁复冗长的投稿流程。我不得不花费大量时间登录各个媒体平台,逐个填写邮件主题、正文以及附件,不仅耗时费力,还…