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

news2024/11/23 11:26:51

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

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

首先相信贪吃蛇游戏绝对称的上是我们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.coloradmin.cn/o/1649958.html

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

相关文章

YAML如何操作Kubernetes核心对象

Pod Kubernetes 最核心对象Pod Pod 是对容器的“打包”&#xff0c;里面的容器&#xff08;多个容器&#xff09;是一个整体&#xff0c;总是能够一起调度、一起运行&#xff0c;绝不会出现分离的情况&#xff0c;而且 Pod 属于 Kubernetes&#xff0c;可以在不触碰下层容器的…

Day 63:单调栈 LeedCode 84.柱状图中最大的矩形

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&a…

Linux系统使用Docker安装青龙面板并实现远程访问管理面板

文章目录 一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用。本教程使用Docker部署青龙&#xff0c;如何安装Docker详见&#xff1a; 二、安装青龙面板三、映射本地部署的青龙面板至公网四、使用固定公网地址访问本地部署的青龙面板 青龙…

Codeforces Round 942 (Div. 2) A-D1

题目&#xff1a; Codeforces Round 942 (Div. 2) D2有缘再补吧… A. Contest Proposal 题意 两个升序&#xff08;不降&#xff09;的序列a和b&#xff0c;可以在a的任意位置插入任意数&#xff08;要保持升序&#xff09;&#xff0c;使对任意i&#xff0c;有a[i] < b[…

ENVI下实现遥感矿物蚀变信息提取

蚀变岩石是在热液作用影响下&#xff0c;使矿物成分、化学成分、结构、构造等发生变化的岩石。由于它们经常见于热液矿床的周围&#xff0c;因此被称为蚀变围岩&#xff0c;蚀变围岩是一种重要的找矿标志。利用围岩蚀变现象作为找矿标志已有数百年历史&#xff0c;发现的大型金…

ldap对接jenkins

ldap结构 配置 - jenkins进入到 系统管理–>全局安全配置 - 安全域 选择ldap - 配置ldap服务器地址&#xff0c;和配置ldap顶层唯一标识名 配置用户搜索路径 - 配置管理员DN和密码 测试认证是否OK

Java | Leetcode Java题解之58题最后一个单词的长度

题目&#xff1a; 题解&#xff1a; class Solution {public int lengthOfLastWord(String s) {int index s.length() - 1;while (s.charAt(index) ) {index--;}int wordLength 0;while (index > 0 && s.charAt(index) ! ) {wordLength;index--;}return wordL…

《设计一款蓝牙热敏打印机》

主控芯片用易兆威蓝牙ic&#xff0c;通讯接口&#xff1a;蓝牙、串口、usb 安卓apk用java kotlin编写、上位机用Qt编写。

基于51单片机的自动售货机系统

一、项目概述 本文设计了一款以AT89C51单片机为核心的自动售货机系统&#xff0c;并且着重详细地介绍了自动售货机的整体系统设计方案、硬件选择基础、软件使用方法及技巧。 以AT89C51作为CPU处理单元连接各个功能模块&#xff1b;以44矩阵键盘作为输入控制模块对货物进行种类…

一文了解栈

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、栈是什么&#xff1f;二、栈的实现思路1.顺序表实现2.单链表实现3.双向链表实现 三、接口函数的实现1.栈的定义2.栈的初始化3.栈的销毁4.入栈5.出栈6.返回栈…

爆赞好文之java反序列化之CB超详细易懂分析

java反序列化之CB超详细易懂分析 CB1环境搭建前言分析PropertyUtilsBeanComparatorPriorityQueue CB2环境搭建前言exp CB1 环境搭建 pom.xml <dependencies><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils&l…

回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法

回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法 目录 回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法预测效果基本介绍程序设计参考资料 预测效果 基本介绍 【模型简介】CNN-SE_Attention结合了卷积神经网络&#xff…

12V系统车灯电源口浪涌过压防护方案及保护器件选型推荐

12V系统车灯驱动电源口浪涌过压防护方案图 12V系统车灯驱动电源口浪涌过压防护方案详解 从图中可知&#xff0c;方案针对车灯驱动电路电源输入口的浪涌过压保护。在车载12V系统中&#xff0c;电源线上面的瞬态浪涌主要来源于抛负载。在12V系统车灯驱动电源输入端&#xff0c;东…

Scroll生态项目Penpad,再获Presto Labs的投资

Penpad是Scroll生态的LaunchPad平台&#xff0c;其整计划像收益聚合器以及RWA等功能于一体的综合性Web3平台拓展&#xff0c;该平台在近期频获资本市场关注&#xff0c;并获得了多个知名投资者/投资机构的支持。 截止到本文发布前&#xff0c;Penpad已经获得了包括Scroll联合创…

6.移除元素

文章目录 题目简介题目解答解法一&#xff1a;双指针代码&#xff1a;复杂度分析&#xff1a; 解法二&#xff1a;双指针优化代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 相关的讲解&#xff01;&#x1f600; 题目简…

【计组OS】访存过程以及存储层次化结构

苏泽 本专栏纯个人笔记作用 用于记录408 学习的笔记记录&#xff08;敲了两年码实在不习惯手写笔记了&#xff09; 如果能帮助到大家当然最好 但由于是工作后退下来备考 很多说法和想法都会结合实际开发的思想 可能不是那么的纯粹应试哈 希望大家挑选自己喜欢的口味食用…

语音识别--光谱门控降噪

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计7267字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

QT程序简单国际化实验

文章目录 第一步&#xff1a;新建一个QT工程第二步&#xff1a;添加控件第三步&#xff1a;在pro文件中添加内容第四步&#xff1a;更新文件第五步&#xff1a;打开QT的Linguist第六步&#xff1a;添加翻译内容第七步&#xff1a;回到QT Creator中添加文件第八步&#xff1a;给…

Linux 进程间通信之共享内存

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; ​ 目录 ​编辑​ 前言 共享内存直接原理…

标准IO学习

思维导图&#xff1a; 有如下结构体 struct Student{ char name[16]; int age; double math_score; double chinese_score; double english_score; double physics_score; double chemistry_score; double bio_score; }; 申请该结构体数组&#xff0c;容量为5&#xff0c;初始…