贪吃蛇大作战(C语言--实战项目)

朋友们!好久不见。经过一段时间的沉淀,我这篇文章来和大家分享贪吃蛇大作战这个游戏是怎么实现的。

(一).贪吃蛇背景了解及效果展示

首先相信贪吃蛇游戏绝对称的上是我们00后的童年,不仅是贪吃蛇还有俄罗斯⽅块,扫雷等都是以前十分流行的游戏,下面我们就通过代码的形式进行贪吃蛇的实现,在进行代码实现之前我们先来看看实现的效果看看有些什么,相信大家一定会对它十分感兴趣的。

贪吃蛇游戏

上面这个就是关于贪吃蛇游戏的实现效果图,相信大家都十分的想了解到底它是怎样实现的,那么下面我们就来学习关于它的实现。

(二).预了解知识(相关Win32API介绍)

首先在实现贪吃蛇之前我们要先了解一些关于Win32API的知识。

2.1.控制台程序

在这里我们需要改变控制台,才能完成贪吃蛇游戏,如图:

这里我们首先要将默认终端应用程序改成如图所示,

mode命令:指的是改变控制台界面大小

如图所示:

title命令:改变控制台的名字

总的来说这两个命令实际运用如图所示:

改变的就是如图中的两个东西实际代码如下:

#include<stdio.h>
int main()
{
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");

    return 0;
}

 2.2.控制台屏幕上的坐标COORD

COORD也就是控制台屏幕上的坐标

也就是如图所示的坐标。

COORD类型的声明:

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

 给坐标赋值:

COORD pos = { 10, 15 };

2.3.GetStdHandle

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

这里简而言之就是和我们鼠标相同的东西。

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

 这里运用GetStdHandle来获取句柄。

2.4.GetConsoleCursorInfo 函数

作用:检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

 GetConsoleCursorInfo 函数的实际运用就是如图所示,用来获取控制台光标的信息。

2.5.CONSOLE_CURSOR_INFO

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

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

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

 bVisible:游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。

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

2.6.SetConsoleCursorInfo 

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

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

这里就是用 SetConsoleCursorInfo函数来设置控制台光标状态, 只有运用了这个函数光标才能够被隐藏。

2.7.SetConsoleCursorPosition

作用:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。简而言之就是将光标设置在自己想要它在的地方通过坐标的形式。

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

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

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

 2.8GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(
 int vKey
);

 实例:检测数字键

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
#include <stdio.h>
#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");
 }
 else if (KEY_PRESS(0x38))
 {
 printf("8\n");
 }
 else if (KEY_PRESS(0x39))
 {
 printf("9\n");
 }
 }
 return 0;
}

这里就是用来检测键盘上面的键是否被按过。

2.9设置本地化环境

<locale.h>本地化

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

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

2.10.setlocale

C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。

在我们实现的时候通常我们会先把它设置为本地模式,以便进行环境的匹配。

setlocale(LC_ALL, "C"); 注意这里使用setlocale函数要有头文件<locale.h>。

2.11.宽字符的打印

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

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

这三个内容其实是连在一起用的,要先进行本地环境的设置再来进行宽字符的打印。

总结:上面的全部都是我们在完成贪吃蛇之前需要了解的知识,可能我们就这样直接来看可能不容易理解下面我们进行贪吃蛇的实现将这些知识带进去我们大家可能更容易理解。 

(三).项目实践

 项目进行之前我们要先进行准备工作,首先定义一个结构体,来包装贪吃蛇

typedef struct Snake
{
	pSnakeNode _psnake;//指向蛇头的指针
	pSnakeNode _pfood;//指向食物节点的指针
	enum DIRECTION _dir;//蛇的方向
	enum GAME_STATUS _status;//游戏的状态
	int _food_weight;//一个食物的分数
	int _score;//总分数
	int _sleep_time;//休息时间,休息时间越短,速度越快
}Snake,* pSnake;

这里面就是贪吃蛇的全部东西我们这样将它的七个内容定义成一个结构体的好处是有利于后期的管理和实现,同样里面蛇的方向有多个,游戏状态也有多种,这两个我们就可以利用枚举的方式来包装起来,蛇的节点类型其实就是一个链表同样这里我们也需要定义一个结构体来表示蛇的节点,这三个实现的代码我总结到下面:

//枚举蛇的方向
enum DIRECTION
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL //正常退出
};

//蛇身节点类型
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;

}SnakeNode,* pSnakeNode;

好了当我们把这些准备工作做好以后我们就可以来进行贪吃蛇的实现了,首先我们来看游戏开始部分。

3.1.游戏开始(GameStart)

游戏开始阶段一共有七个内容我们来逐一学习。 

3.1.1.设置游戏窗⼝的⼤⼩

设置游戏窗口的大小,就是我们上面说的mode命令 

system("mode con cols=100 lines=30");就是有system函数来设置控制台窗口的大小。

3.1.2.设置窗⼝的名字

设置窗口的名字就是通过title命令来设置窗口的名字

 system("title 贪吃蛇");就是运用system函数来设置窗口的名字

当这两个程序完成之后的窗口样子就是如下:

3.1.3.隐藏屏幕光标

隐藏光标实际上就是把如图所示的东西给隐藏掉

 那么这个程序我们该如何去写呢?接下来紧跟我们脚步我们一起来学习

通过上面我们了解的一些知识我们先看看下面这段代码:


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



//获取输出设备的句柄
HANDLE hOutput = NULL;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标  
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息

 这里面就解释了我们如何来隐藏光标,先获取句柄,定义一个houtput来接收,再通过定义一个结构体,在获取控制台光标信息,最后再来隐藏控制台光标。

3.1.4.打印欢迎界⾯-WelcomeToGame

 

欢迎界面就是如图所示,同样我们的代码程序如下:

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

 

3.1.5.创建地图-CreateMap

地图创建我们需要我们来进行创建以下的界面

 

 

创建一个x轴为58y轴为26的一个游戏地图,这里我们就可以运用循环语句来打印□从而创建游戏窗口。

实际代码如图所示:

void CreatMap()
{
	//上
	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 < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	
}

上面的SetPos是指的是定位光标的位置。

3.1.6.初始化蛇⾝-InitSnake

//创建蛇身
void InitSnack(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		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;

}

3.1.7.创建⻝物

//创建食物
void CreatFood(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不能与蛇身体的位置冲突
	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("pFood::malloc");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_pfood = pFood;
}

3.2.游戏进行(GameRun)

3.2.1.右侧打印帮助信息-PrintHelpInfo

//打印运行游戏旁边的提示信息
void printfhelpInfo()
{
	SetPos(64, 12);
	wprintf(L"%ls", L"不能撞墙,不能咬到自己");
	SetPos(64, 13);
	wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");
	SetPos(64, 14);
	wprintf(L"%ls", L"按F3加速,按F4减速");
	SetPos(64, 15);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");

}

3.2.2.打印当前已获得分数和每个⻝物的分数 

//打印总分数和食物的分值
	SetPos(64, 9);
	printf("总分数:%d\n", ps->_score);
	SetPos(64, 10);
	printf("当前食物的分数:%2d\n", ps->_food_weight);

3.2.3.获取按键情况-KEY_PRESS

//按键的设置
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>80)
	{
		ps->_sleep_time -= 30;
		ps->_food_weight += 2;
	}
	
}
else if(KEY_PRESS(VK_F4))
{
	if (ps->_food_weight > 2)
	{
		ps->_sleep_time += 30;
		ps->_food_weight -= 2;
	}
}

3.2.4.根据按键情况移动蛇-SnakeMove 2~4循环,直到游戏是结束状态

void SnakeMove(pSnake ps)
{
	//创建一个节点表示蛇即将要到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("pNextNode()::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);

}
3.2.4.1. 根据蛇头的坐标和⽅向,计算下⼀个节点的坐标
void SnakeMove(pSnake ps)
{
	//创建一个节点表示蛇即将要到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("pNextNode()::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);

}
3.2.4.2. 判断下⼀个节点是否是⻝物-NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}
3.2.4.3.是⻝物就吃掉-EatFood
void EatFood(pSnakeNode pn, pSnake ps)
{
	//采用头插法将食物放上去
	ps->_pfood->next = ps->_psnake;
	ps->_psnake = ps->_pfood;

	free(pn);
	pn = NULL;

	pSnakeNode cur = ps->_psnake;
	//打印
	while (cur)
	{
		SetPos(cur->x,cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreatFood(ps);
}
3.2.4.4.不是⻝物,吃掉⻝物,尾巴删除⼀节-NoFood
void NoFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->_psnake;
	ps->_psnake = pn;

	pSnakeNode 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);
	cur->next = NULL;
}
3.2.4.5.判断是否撞墙-KillByWall
//检测是否撞墙
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;
	}
}
3.2.4.6.判断是否撞上⾃⼰-KillBySelf
//检测是否撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode 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;
	}
}

3.3游戏结束(GameEnd)

3.3.1.告知游戏结束的原因

3.3.2.释放蛇⾝节点

//结束游戏--善后工作
void GameEnd(pSnake ps)
{
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("主动结束游戏\n");
		break;
	case KILL_BY_SELF:
		printf("撞到了自己,游戏结束\n");
		break;
	case KILL_BY_WALL:
		printf("撞到了墙游戏结束\n");
		break;
	}

	//释放链表
	pSnakeNode cur = ps->_psnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

(四).代码汇总

4.1.snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>

#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//枚举蛇的方向
enum DIRECTION
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL //正常退出
};

//蛇身节点类型
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;

}SnakeNode,* pSnakeNode;

//贪吃蛇
typedef struct Snake
{
	pSnakeNode _psnake;//指向蛇头的指针
	pSnakeNode _pfood;//指向食物节点的指针
	enum DIRECTION _dir;//蛇的方向
	enum GAME_STATUS _status;//游戏的状态
	int _food_weight;//一个食物的分数
	int _score;//总分数
	int _sleep_time;//休息时间,休息时间越短,速度越快
}Snake,* pSnake;
//定位光标位置
void SetPos(short x, short y);

//初始化游戏
void GameStart(pSnake ps);

//打印欢迎界面和功能介绍
void WelcomeToGame(pSnake ps);

//2.绘制地图
void CreatMap();

//初始化蛇
void InitSnack(pSnake ps);

//创建食物
void CreatFood(pSnake ps);

//运行游戏
void GameRun(pSnake ps);

//蛇的移动
void SnakeMove(pSnake ps);

//实现暂停逻辑
void pause();

//检测下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);

//下一个位置是食物吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);

//如果下一个坐标不是食物
void NoFood(pSnakeNode pn, pSnake ps);

//检测是否撞墙
void KillByWall(pSnake ps);

//检测是否撞到自己
void KillBySelf(pSnake ps);

//结束游戏--善后工作
void GameEnd(pSnake ps);

4.2.snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"


//设置光标的坐标
void SetPos(short x, short y)
{
	HANDLE hOutput = NULL;
	//获取标准输出的句柄(⽤来标识不同设备的数值)
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置标准输出上光标的位置为pos
	COORD pos = { x, y };
	SetConsoleCursorPosition(hOutput, pos);
}
//1.打印欢迎界面和功能介绍
void WelcomeToGame()
{
	SetPos(38,14);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(40, 18);
	system("pause");
	system("cls");//清理界面
	SetPos(20, 14);
	wprintf(L"用↑ . ↓ . ← . →来控制蛇的移动,按F3加速,按F4减速\n");
	SetPos(20, 15);
	wprintf(L"加速能够获得更高的分数\n");
	SetPos(40, 20);
	system("pause");
	system("cls");
}

//2.绘制地图
void CreatMap()
{
	//上
	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 < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	
}

//创建蛇身
void InitSnack(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		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;

}

//创建食物
void CreatFood(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不能与蛇身体的位置冲突
	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("pFood::malloc");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_pfood = pFood;
}




void GameStart(pSnake ps)
{
	//首先设置窗口的大小,进行光标隐藏
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//获取输出设备的句柄
	HANDLE hOutput = NULL;
	hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体
	GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标  
	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息


	//1.打印欢迎界面和功能介绍
	WelcomeToGame();
	//2.绘制地图
	CreatMap();
	//3.创建蛇
	InitSnack(ps);
	//4.创建食物
	CreatFood(ps);
}

//打印运行游戏旁边的提示信息
void printfhelpInfo()
{
	SetPos(64, 12);
	wprintf(L"%ls", L"不能撞墙,不能咬到自己");
	SetPos(64, 13);
	wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");
	SetPos(64, 14);
	wprintf(L"%ls", L"按F3加速,按F4减速");
	SetPos(64, 15);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");

}

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

int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}

void EatFood(pSnakeNode pn, pSnake ps)
{
	//采用头插法将食物放上去
	ps->_pfood->next = ps->_psnake;
	ps->_psnake = ps->_pfood;

	free(pn);
	pn = NULL;

	pSnakeNode cur = ps->_psnake;
	//打印
	while (cur)
	{
		SetPos(cur->x,cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreatFood(ps);
}

void NoFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->_psnake;
	ps->_psnake = pn;

	pSnakeNode 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);
	cur->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)
{
	pSnakeNode 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)
{
	//创建一个节点表示蛇即将要到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("pNextNode()::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)
{
	printfhelpInfo();

	do {
		//打印总分数和食物的分值
		SetPos(64, 9);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 10);
		printf("当前食物的分数:%2d\n", ps->_food_weight);

		//按键的设置
		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>80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
			
		}
		else if(KEY_PRESS(VK_F4))
		{
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnakeMove(ps);

		Sleep(ps->_sleep_time);

	} while (ps->_status==OK);
}


//结束游戏--善后工作
void GameEnd(pSnake ps)
{
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("主动结束游戏\n");
		break;
	case KILL_BY_SELF:
		printf("撞到了自己,游戏结束\n");
		break;
	case KILL_BY_WALL:
		printf("撞到了墙游戏结束\n");
		break;
	}

	//释放链表
	pSnakeNode cur = ps->_psnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

4.3.test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"

//完成游戏的测试逻辑
void test()
{
	int ch = 0;
	do {
		system("cls");//在每次开始前先清理屏幕
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1.打印欢迎界面
		//2.功能介绍
		//3.绘制地图
		//4.创建蛇
		//5.创建食物
		//6.设置游戏相关信息
		GameStart(&snake);

		//运行游戏
		GameRun(&snake);
		//结束游戏--善后工作
		GameEnd(&snake);

		SetPos(20,15);
		printf("只来一局?(Y/N):");
		ch=getchar();
		getchar();
		while (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.mfbz.cn/a/599825.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

找不到模块“vue-router”。你的意思是要将 moduleResolution 选项设置为 node,还是要将别名添加到 paths 选项中?

在tsconfig.app.json中添加&#xff0c;记得一定是 tsconfig.app.json 中&#xff0c;如添加到 tsconfig.node.json 还是会报错的 哈哈哈哈&#xff0c;不瞒你们&#xff0c;我就添加错了&#xff0c;哈哈哈。所以这也算写一个demo提醒自己 "compilerOptions": {&qu…

【牛客】排列计算

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 如果直接涂色来计算单点权重&#xff0c;2e5*2e5必然超时。 所以用差分进行优化。 3. 代码实现 #include<bits/stdc.h> using name…

Go语言fmt包深度探索:格式化输入输出的利器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3ad; 引言一、基础输出函数fmt.Print与fmt.Println&#x1f4cc; fmt.Print&#xff1a;纯粹输出&#xff0c;不带换行&#x1f4cc; fmt.Println&#xff1a;输出后自动添加换行符 二、格式化输出fmt.Printf&…

鸿蒙开发接口Ability框架:【@ohos.application.missionManager (missionManager)】

missionManager missionManager模块提供系统任务管理能力&#xff0c;包括对系统任务执行锁定、解锁、清理、切换到前台等操作。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 impo…

“Postman 中文版使用教程:如何切换到中文界面?”

Postman 的很好用的接口测试软件。但是&#xff0c;Postman 默认是英文版的&#xff0c;也不支持在软件内切换为中文版。很多同学的英语并不是很好&#xff0c;看到一堆的英文很是头痛。 今天我们来介绍下&#xff1a;切换到 Postman 中文版的方法。想要学习更多的关于 Postma…

Type-C转音频(USB2.0数据传输)+PD充电芯片乐得瑞LDR6500/LDR6023

LDR6500 USB-C DRP 接口 USB PD 通信芯片概述 Type-C转音频(USB2.0数据传输)PD充电芯片乐得瑞LDR6500LDR6500是乐得瑞科技针对USB Type-C标准中的Bridge设备而开发的USB-C DRP&#xff08;Dual Role Port&#xff0c;双角色端口&#xff09;接口USB PD&#xff08;Power Deliv…

Qt---day2-信号与槽

1、思维导图 2、 拖拽式 源文件 #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //按钮2 this->btn2new QPushButton("按钮2",th…

LeetCode 面试经典150题 252.会议室

题目&#xff1a;给定一个会议时间安排的数组 intervals &#xff0c;每个会议时间都会包括开始和结束的时间 intervals[i] [starti, endi] &#xff0c;请你判断一个人是否能够参加这里面的全部会议。 思路&#xff1a;因为一个人在同一时刻只能参加一个会议&#xff0c;因此…

选择适用的无尘棉签:保障洁净生产环境下的高效擦拭

随着洁净生产条件的日益普及和无尘级别要求的提高&#xff0c;无尘擦拭用品成为广大用户追捧的必备工具。在这个领域&#xff0c;无尘棉签作为一种高效的擦拭工具&#xff0c;扮演着重要的角色。然而&#xff0c;面对市场上种类繁多的无尘棉签&#xff0c;如何选择最合适的产品…

linux 调试-kdb 调试内核-1

目标&#xff1a;打印bcm2835_spi_transfer_one 是如何从用户空间开始调用的 1. kernel 配置 KDB配置选项 添加 spi 控制器驱动 和 spi 设备驱动 2. 调试流程 调试内核-系统启动之后 1. 开发板进入kdb,等待pc 连接 rootraspberrypi:~# echo "ttyS0,115200"…

Python做自动化测试必知必会思维导图

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

DetectoRS:门控融合

论文标题&#xff1a;DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution 论文地址&#xff1a;https://arxiv.org/pdf/2006.02334 代码地址&#xff1a;https://github.com/joe-siyuan-qiao/DetectoRS/blob/612916ba89ad6452b07…

高项第四版 十大管理及49个过程【背】作业分享

项目管理 1.十大管理【背】 包括&#xff08;口诀:范进整狗子&#xff08;沟质&#xff09; 才&#xff08;采&#xff09;干成疯子&#xff08;风资&#xff09;&#xff09;: &#xff08;1&#xff09;项目整合管理:识别、定义、组合、统一和协调各项目管理过程组的各个过…

OpenCV 入门(三)—— 车牌筛选

OpenCV 入门系列&#xff1a; OpenCV 入门&#xff08;一&#xff09;—— OpenCV 基础 OpenCV 入门&#xff08;二&#xff09;—— 车牌定位 OpenCV 入门&#xff08;三&#xff09;—— 车牌筛选 OpenCV 入门&#xff08;四&#xff09;—— 车牌号识别 OpenCV 入门&#xf…

【SSM进阶学习系列丨分页篇】PageHelper 分页插件集成实践

文章目录 一、说明什么是分页PageHelper介绍 二、导入依赖三、集成Spring框架中四、编写Service五、编写Controller六、编写queryAllByPage页面展示数据 一、说明 什么是分页 ​ 针对分页&#xff0c;使用的是PageHelper分页插件&#xff0c;版本使用的是5.1.8 。 ​ 参考文档…

【typescript 小秘籍 - 类型自动推导】

今天发现个typescript的小技巧&#xff0c;原来在vscode里面 typescript是可以根据数据&#xff0c;自动推导其类型的&#xff0c;这样就不用自己去手敲定义了。比如 鼠标移动到person上&#xff0c;可以看到 其自动推导了person的类型 然后直接复制下来 直接使用即可。

新华三VRRP配置

新华三VRRP配置 配置步骤 (1).基础配置&#xff1a; CORE1&#xff1a; [CORE1]vlan 10 //创建vlan10 [CORE1-vlan10]int vlan 10 //进入vlanif 10 [CORE1-Vlan-interface10]ip add 192.168.10.1 24 //配置ip [CORE1-Vlan-interface10]int g1/0/2 //进入接口 [C…

Map集合的实现类~TreeMap

重复依据&#xff1a;通过对键进行排序 先创建Student类&#xff0c;并在主函数new对象&#xff0c;然后创建TreeMap&#xff1a; 建立红黑树&#xff0c;需要在Student类后面实现类的接口&#xff1a; 重写其中的compareTo方法&#xff1a; 或者可以自定义比较器&#xff1a; …

2024五一劳动节活动策划方案

2024五一劳动节打工人青松游园大会&#xff08;劳动节放青松主题&#xff09;活动策划方案-51P.pptx 活动策划信息&#xff1a; 方案页码&#xff1a;51页 文件格式&#xff1a;PPT 方案简介&#xff1a; 劳动是世界上最伟大的事 所以我们该把一些劳动留给明天&#xff0…

windows系统远程执行脚本部署项目操作手册

windows系统远程执行脚本部署项目操作手册 windows系统远程执行脚本部署项目 如果频繁的需要部署项目到远程的服务器上,每次要手动上传项目,然后停止项目,启动项目,很麻烦,像Linux天生支持远程执行脚本 Windows借助工具也可以做到. 安装WinSCP软件 自行下载软件或关注我的公…
最新文章