C语言结课实战项目_贪吃蛇小游戏

news2024/11/25 7:32:55

目录

最终实现效果:

实现基本的功能:

根据游戏进程解释代码:

游戏初始化:

首先进入游戏,我们应该将窗口名称改为 “贪吃蛇” 并将光标隐藏掉。再在中间打印游戏信息。

 之后我们要把地图打印出来:

然后我们将贪吃蛇创建出来,将蛇有关的信息用结构体和枚举类型封装起来,将蛇身用链表维护。

创建食物

 游戏开始:

用dowhile循环对主体进行不断刷新

打印相关信息

按键检测

蛇的移动

判断下一位置是否为食物:

 下一位置是食物:

下一位置不是食物:

 检测是否撞墙:

检测是否撞到自己:

 蛇移动一步的总函数:

 游戏结束:

后续改进:


游戏源代码链接:function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)

最终实现效果:

实现基本的功能:

void set_pos(short x, short y);//定位光标位置

void Game_Start(pSnake ps);//初始化

void WelcomeToGame(void);//打印欢迎界面

void CreateMap(void);//创建地图

void InitSnake(pSnake ps);//初始化蛇身

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

void Game_Run(pSnake ps);//游戏运行逻辑

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

bool 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 Game_End(pSnake ps);//游戏善后

• 贪吃蛇地图绘制
• 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏

运用到的知识:C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等

根据游戏进程解释代码:

这里分为下面几个函数对游戏进行实现:

system("cls");
//创建贪吃蛇
pSnakeNode pSnake = NULL;

Snake snake = { 0 };

//初始化游戏
Game_Start(&snake);

//运行游戏
Game_Run(&snake);

结束游戏
Game_End(&snake);

游戏初始化:

void Game_Start(pSnake ps)//初始化
{
	//0.设置窗口大小/名字
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//1.隐藏光标
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput,&CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(houtput,&CursorInfo);
	
	//2.打印欢迎界面,介绍功能
	WelcomeToGame();

	//3.绘制地图
	CreateMap();

	//4.创建蛇
	InitSnake(ps);

	//5.创建食物
	CreateFood(ps);
}

首先进入游戏,我们应该将窗口名称改为 “贪吃蛇” 并将光标隐藏掉。再在中间打印游戏信息。

这里用到的函数有:

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

将窗口设置为100列,30行

(2)system("title 贪吃蛇");

将title设置为贪吃蛇

(3)system("pause");

暂停程序,按下任意键继续

(4)system("cls");

清理屏幕

/*******************************************/
//0.设置窗口大小/名字
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
/*******************************************/
void set_pos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL; 
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput,pos);
}
/*******************************************/
void WelcomeToGame()
{
	set_pos(40, 12);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	set_pos(42,18);
	system("pause");
	system("cls");
	set_pos(30, 12);
	wprintf(L"用上下左右控制蛇的移动,按 {[ 加速, ]} 减速\n");
	set_pos(38, 13);
	wprintf(L"加速可以得到更高的分数\n");
	set_pos(42, 18);
	system("pause");
	system("cls");
}
/********************************************/

 之后我们要把地图打印出来:

分别打印上下左右的墙,将墙宏定义为WALL,字符为’□‘

void CreateMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	set_pos(0,25);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i < 25; i++)
	{
		set_pos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 25; i++)
	{
		set_pos(56, i);
		wprintf(L"%lc", WALL);
	}
}

然后我们将贪吃蛇创建出来,将蛇有关的信息用结构体和枚举类型封装起来,将蛇身用链表维护。

蛇头指针:指向链表头节点的指针,方便对蛇身进行维护

食物指针:从开发角度来说,其实食物也是蛇的一个节点,当蛇头的下一个位置为食物时,将食物的节点头插到蛇身上面。

方向:对蛇的方向进行枚举

游戏状态:方便判断蛇的状态:(1)正常(2)撞墙(3)撞到自己(4)正常退出每一次while循环后判断游戏状态

食物权重:每次加速食物权重+2,减速-2。

总成绩:每吃掉一个食物,蛇身长度+1,分数+=食物权重。

每走一步的缓冲时间:缓冲时间越短,蛇走得越快;反之越慢。

#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	//下一个节点
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

enum DRECCTION//方向
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//蛇的状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞自己
	END_NORMAL//正常退出
};

typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物节点的指针
	enum DRECCTION _dir;//蛇的方向
	enum GAME_STAYUS _status;//游戏状态
	int _food_weight;//一个食物都分数
	int _score;//总成绩
	int _sleep_time;//休息时间

}Snake,*pSnake;
/************************************/
void InitSnake(pSnake ps)//初始化蛇
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::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)
	{
		set_pos(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;//单位为ms
	ps->_status = OK;
}

创建食物

生成随机数,赋给x,y。

这里x和y都有范围,不能超出地图边界,并且不能与蛇身重合。

void CreateFood(pSnake ps)//创建食物
{
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 52 + 2;//2~54
		y = rand() % 24 + 1;//1~25
	} while (x % 2 != 0);//x为2的倍数
	//不能和蛇身的坐标相同
	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("CreateFood()::malloc");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;
	set_pos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_pFood = pFood;
}

 游戏开始:


void Game_Run(pSnake ps)//游戏运行逻辑
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		//打印分数,食物权重
		set_pos(64, 8);
		printf("总分数:%d\n",ps->_score);
		set_pos(64, 9);
		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_OEM_6))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_OEM_4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}

		SnakeMove(ps);//蛇走一步的过程
		Sleep(ps->_sleep_time);

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

用dowhile循环对主体进行不断刷新

每次循环后让系统暂停一段时间(初始为200ms)

打印相关信息

蛇每走一步,分数都有可能变化,每次循环都打印一次。

按键检测

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)

(1)用虚拟键值检测是否按下上下左右键,按下相应键并且蛇的当前方向不能与之相反。

(2)检测是否按下空格,按下就进函数:

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

 再次按下空格退出函数。

(3)检测加速减速,按下加速键就将缓冲时间变短,食物权重增加;反之变长,食物权重减少。(这里也是有范围的,食物权重不能为负数,也不能过大)

蛇的移动

进入函数,创建蛇头的下一个位置所在的节点,并根据方向算出所在位置。

判断下一位置是否为食物:
bool 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)
	{
		set_pos(cur->x, cur->y);
		wprintf(L"%lc",BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreateFood(ps);
}
下一位置不是食物:

创建下一位置的节点,也是头插,但是在打印蛇身之后,将蛇尾位置打印两个空格(不打印空格蛇身就不会清除一直留在屏幕上:拖尾),将蛇尾的节点释放掉。(cur->next一定要置空,不能让它为野指针)

void NoFood(pSnakeNode pn, pSnake ps)//下一个位置不是食物
{
	//头插
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next)
	{
		set_pos(cur->x,cur->y);
		wprintf(L"%lc",BODY);

		cur = cur->next;
	}
	//把最后一个节点打印空格
	set_pos(cur->next->x,cur->next->y);
	printf("  ");
	//将最后一个节点释放
	free(cur->next);
	//将倒数第二个节点置为空
	cur->next = NULL;
}
 检测是否撞墙:

判断蛇头坐标位置是否超出范围,若超出范围,将蛇的状态改为KILL_BY_WALL

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;
	}
}
检测是否撞到自己:

遍历蛇身链表,若坐标重合,将蛇的状态改为KILL_BY_SELF,并且跳出循环。

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;
			break;
		}
		cur = cur->next;
	}
}
 蛇移动一步的总函数:
void SnakeMove(pSnake ps)//蛇的移动
{
	//创建蛇头的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	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 Game_End(pSnake ps)//游戏善后
{
	set_pos(24,12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		printf("您被墙单杀了\n");
		break;
	case KILL_BY_SELF:
		printf("您被自己单杀了\n");
		break;
	}

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

后续改进:

1.穿墙

2.食物分类

3.多个食物

4.双人游戏

……

 本期博客到这里就结束了,如果有什么错误,欢迎指出,如果对你有帮助,请点个赞,谢谢!

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

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

相关文章

数据可视化插件echarts【前端】

数据可视化插件echarts【前端】 前言版权开源推荐数据可视化插件echarts一、如何使用1.1 下载1.2 找到js文件1.3 入门使用1.4 我的使用 二、前后端交互&#xff1a;入门demo2.1 前端htmljs 2.2 后端entitycontrollerservicemapper 三、前后端交互&#xff1a;动态数据3.1 前端j…

书生·浦语大模型全链路开源体系-第6课

书生浦语大模型全链路开源体系-第6课 书生浦语大模型全链路开源体系-第6课相关资源Lagent & AgentLego 智能体应用搭建环境准备创建虚拟环境安装LMDeploy安装 Lagent安装 AgentLego Lagent 轻量级智能体框架使用 LMDeploy 部署启动并使用 Lagent Web Demo使用自定义工具获取…

mysql的mgr集群的网络不可达之后脑裂的问题

此时主节点上的dml和ddl操作都会挂死&#xff0c;由于脑裂问题&#xff0c;无法判断谁是主谁是备&#xff0c;所以节点无法写操作。 此时需要手动介入处理&#xff1a; mysql> show variables like %group_replication_member_expel_timeout%; ---------------------------…

Java上机实验报告(7)

实验 &#xff08;7&#xff09; 项目名称&#xff1a;组件及事件处理-注册页面 一、 实验报告内容一般包括以下几个内容&#xff1a; 实验项目名称 实验7 组件及事件处理-注册页面 实验目的和要求 本实验的目的&#xff1a; 本实验的目的是让学生掌握常用的组件类JButton&am…

空气源热泵电控系统部分控制功能的逻辑

空气源热泵的电控系统&#xff0c;是一台空气源热泵设备的大脑&#xff0c;电控系统逻辑的合理性、执行的精准性&#xff0c;直接决定设备运行的稳定、能效高低、运行寿命。 控制功能 &#xff08;1&#xff09;压缩机防频繁起停 压缩机再次启动间隔时间为3分钟&#xff0c;即压…

果园系统养殖游戏喂养偷菜种植浇水养成小程序功能介绍

以下是上述功能介绍的重写版本&#xff1a; 装扮 使用丰富的材料&#xff0c;为您的房屋增添独特魅力&#xff0c;展现个性化装饰风格。 土地升级 投入不同数量的材料&#xff0c;提升房屋与土地的品质&#xff0c;打造独一无二的庄园。 日志 通过日志记录&#xff0c;清…

Flask实战

from flask import Flask appFlask(__name__)点击Flask同时点击键盘ctrl即可查看Flask的默认初始化函数 def __init__(self,import_name: str,static_url_path: str | None None,static_folder: str | os.PathLike[str] | None "static",static_host: str | None …

LeetCode-31-下一个排列问题

题目说明 实现获取下一个排列的函数&#xff0c;算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 如果不存在下一个更大的排列&#xff0c;则将数字重新排列成最小的排列&#xff08;即升序排列&#xff09;。 必须原地修改&#xff0c;只允许使用额外常数空间。…

考研日常记录

由于实在太无聊了 &#xff0c; 所以记录以下考研备考日常 &#xff0c; 增加一点成就感 &#xff0c; 获得一点前进动力。 2024.4.18 周四 课程情况&#xff1a; 无课 时间规划&#xff1a; 上午&#xff1a;休息 下午&#xff1a; 事项耗时进度备注写作业1h复习英语单词…

SayNav: 利用大语言模型在未知环境中导航

论文标题&#xff1a;SayNav: Grounding Large Language Models for Dynamic Planning to Navigation in New Environments 论文地址&#xff1a;https://arxiv.org/pdf/2309.04077.pdf 项目地址&#xff1a;https://www.sri.com/ics/computer-vision/saynav SayNav介绍 SayNa…

ExpandableListView可扩展列表视图,可折叠列表视图

目录 1. XML代码 2. ExpandableListView的使用 3. ExpandableListAdapter的使用 4. 数据观察者的使用 5. 案例代码一览 ExpandableListView&#xff08;可扩展/可折叠列表视图&#xff09;是一种多功能的高级控件&#xff0c;每个子项都可以展开一个孙子列表。点击一个分组…

科研基础与工具(笔记)

​ 免责申明&#xff1a; 本文内容只是学习笔记&#xff0c;不代表个人观点&#xff0c;希望各位看官自行甄别 参考文献 科研基础与工具&#xff08;YouTube&#xff09; 专业名词 影响因子&#xff08;Impact Factor&#xff09;&#xff0c;一般是特指SCI的影响因子&#…

Qt实现Mysql数据库的连接,查询,修改,删除,增加功能

Qt实现Mysql数据库的连接&#xff0c;查询&#xff0c;修改&#xff0c;删除&#xff0c;增加功能 安装Mysql数据库&#xff0c;QtCreator Mysql选择Mysql Server 8.1版本安装。 Mysql Server 8.1安装过程 1.首先添加网络服务权限&#xff1a; WinR键输入compmgmt.msc进入…

论文阅读:BEVBert: Multimodal Map Pre-training for Language-guided Navigation

BEVBert&#xff1a;语言引导导航的多模态地图预训练 摘要 现存的问题&#xff1a;目前大多数现有的预训练方法都采用离散的全景图来学习视觉-文本关联。这要求模型隐式关联全景图中不完整、重复的观察结果&#xff0c;这可能会损害智能体的空间理解。 本文解决方案&#xf…

PCBA镀金工艺介绍

PCBA&#xff08;印制电路板组装&#xff09;的镀金工艺是一种常见的表面处理方法&#xff0c;用于提高印制电路板&#xff08;PCB&#xff09;的性能和耐用性。本文将详细介绍PCBA的镀金工艺&#xff0c;包括其类型、工艺流程、优点及应用。 1. 镀金工艺的类型 PCBA的镀金主要…

Mac电脑版鼠标连点工具 RapidClick for Mac

RapidClick for Mac是一款方便实用的自动点击工具&#xff0c;可以帮助Mac用户在使用电脑时轻松、快速地进行鼠标操作。它可以模拟人类手指点击的动作&#xff0c;让您的鼠标点击变得更加快捷高效&#xff0c;大大提升了工作和游戏的效率。 RapidClick for Macv1.5.1激活版下载…

【windows-搭建Ubuntu22LTS】

一、环境要求 1. windows版本要求 至少Windows 10 2020年5月(2004) 版, Windows 10 2019年5月(1903) 版&#xff0c;或者 Windows 10 2019年11月(1909) 版 2. 控制面板开启相关的程序(需要重启) 二、Microsoft store安装unbuntu 下载后直接运行&#xff08;稍微等会&#…

mac: docker安装及其Command not found: docker

已经安装了docker desktop&#xff0c;没安装的 点击安装 傻瓜式安装即可 接着打开终端&#xff1a;好一个 Comand not found:docker 看我不把你整顿&#xff0c;解决如下&#xff1a; 如果你在 macOS 上安装了 Docker Desktop&#xff0c;但是终端无法识别 docker 命令&…

【ARM 裸机】汇编 led 驱动之烧写 bin 文件

1、烧写概念 bin 文件烧写到哪里呢&#xff1f;使用 STM32 的时候烧写到内部 FLASH&#xff0c;6ULL 没有内部 FLASH&#xff0c;是不是就不能烧写呢&#xff1f;不&#xff0c;6ULL 支持 SD卡、EMMC、NAND FLASH、NOR FLASH 等方式启动&#xff0c;在裸机学习的工程中&#x…

C++算法题 - 区间

目录 228. 汇总区间56. 合并区间57. 插入区间452. 用最少数量的箭引爆气球 228. 汇总区间 LeetCode_link 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所…