贪吃蛇游戏(C语言实现)

目录

  • 游戏效果展示
  • 文件
  • 代码的展示
    • test.c
    • Snake.c
    • Snake.h
  • 下一个坐标不是食物

游戏效果展示

QQ录屏20240507163633

文件

在这里插入图片描述

代码的展示

test.c

#define _CRT_SECURE_NO_WARNINGS

#include<locale.h>
//设置本地化
#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();

		while (getchar() != '\n');
		//防止输入太多的字符而出现的bug

	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}
int main()
{
	//设置本地化环境
	setlocale(LC_ALL,"");
	//生成随机数
	srand((unsigned int)time(NULL));
	//测试的逻辑
	test();

	return 0;
}

Snake.c

#define _CRT_SECURE_NO_WARNINGS

#include"Snake.h"

//坐标的定位
//定位光标的位置
void SetPos(short x, short y)
{
	//获取标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}


//欢迎界面的打印
void WelComeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 20);
	system("pause");//暂停
	system("cls");//清理屏幕信息

	SetPos(25, 14);
	wprintf(L"↑ . ↓ . ← . → .来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 15);
	wprintf(L"加速能够获得更高的分数\n");

	SetPos(42, 20);
	system("pause");
	system("cls");
} 

//创建地图
void CreatMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc",WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

//初始化蛇身
void InitSnake(psnake ps)
{
	int i = 0;
	psnakenode cur = NULL;

	for (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->_phead == NULL)//空链表
		{
			ps->_phead = cur;
		}
		else//非空链表
		{
			cur->next = ps->_phead;
			ps->_phead = cur;
		}
	}

	//打印蛇身
	cur = ps->_phead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//设置蛇的属性
	ps->_dir = RIGHT;//默认蛇的方向是向右的
	ps->_food_weight = 10;//每个食物的分数
	ps->_score = 0;//开始总分为0
	ps->_status = OK;//蛇的状态是正常的
	ps->_time_sleep = 200;//睡眠时间为200ms
}

//创建食物
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);
    
    psnakenode cur = ps->_phead;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//创建食物的节点
	psnakenode pfood = (psnakenode)malloc(sizeof(snakenode));
	if (pfood == NULL)
	{
		//开辟失败
		perror("CreatFood::malloc");
		return;
	}
	//开辟成功
	pfood->x = x;
	pfood->y = y;
	pfood->next = NULL;

	SetPos(x, y);
    //定位坐标,打印节点
	wprintf(L"%lc", FOOD);

	ps->_food = pfood;
	//食物的信息记录到food(贪吃蛇)里面去
}

//游戏的初始化
void GameStart(psnake ps)
{
	//1.先设置窗口的大小,再隐藏光标
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//设置窗口的名称
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//得到屏幕的句柄
	//隐藏光标操作
	CONSOLE_CURSOR_INFO cursoninfo;
	//控制光标信息的结构体
	GetConsoleCursorInfo(houtput, &cursoninfo);
	//得到控制台的光标信息
	cursoninfo.bVisible = false;
	//将光标设置为不可见(隐藏控制台光标)
	SetConsoleCursorInfo(houtput, &cursoninfo);
	//设置控制台光标的状态(光标为隐藏)

	//2.打印环境界面和功能介绍
	WelComeToGame();
	//3.创建地图
	CreatMap();
	//4.创建蛇
	InitSnake(ps);
	//5.创建食物
	CreatFood(ps);
}

//打印帮助信息
void PrintHelpInfo()
{
	SetPos(64, 15);
	wprintf(L"%ls",L"不能穿墙,不能咬到自己");
	SetPos(64, 16);
	wprintf(L"%ls", L"↑ . ↓ . ← . → .来控制蛇的移动");
	SetPos(64, 17);
	wprintf(L"%ls", L"按F3加速,F4减速");
	SetPos(64, 18);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");

	SetPos(64, 19);
	wprintf(L"%ls", L"英雄不问出处制作");
}

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
//获取按键的情况,按了是真,没按是假
//?在:之前

//按了空格键的情况
void Pause()
{
	while (1)
	{
		Sleep(200);
		//睡眠200ms
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//判断下一个坐标是否是食物
//返回值判断真假,是否有食物
int NextIsFood(psnakenode pn, psnake ps)
{
	return (pn->x == ps->_food->x && pn->y == ps->_food->y);
	//如果下一个节点是食物就吃掉食物
}

//下一个坐标是食物就吃掉食物
void EatFood(psnakenode pn, psnake ps)
{
	//把食物的节点头插
	ps->_food->next = ps->_phead;
	ps->_phead = ps->_food;

	//释放下一个节点的地址,因为下一个节点和食物的节点冲突了
	free(pn);
	pn = NULL;

	//把蛇打印出来
	psnakenode cur = ps->_phead;
	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->_phead;
	ps->_phead = pn;

	psnakenode cur = ps->_phead;
	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);

	//把倒数第二个节点的下一个节点的地址置为NULL
	cur->next = NULL;
}

//检测蛇是否撞墙
void KillByWall(psnake ps)
{
	if (ps->_phead->x == 0 || ps->_phead->y == 0 ||
		ps->_phead->x == 56 || ps->_phead->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

//检测蛇是否撞到自己
void KillByMyself(psnake ps)
{
	psnakenode cur = ps->_phead->next;
	//蛇头的下一个节点和蛇头的节点的坐标是否相同
	while (cur)
	{
		if (cur->x == ps->_phead->x && cur->y == ps->_phead->y)
		{
			ps->_status = KILL_BY_MYSELF;
			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->_phead->x;
		pNextnode->y = ps->_phead->y - 1;
		break;
	case DOWN:
		pNextnode->x = ps->_phead->x;
		pNextnode->y = ps->_phead->y + 1;
		break;
	case LEFT:
		pNextnode->x = ps->_phead->x - 2;
		pNextnode->y = ps->_phead->y;
		break;
	case RIGHT:
		pNextnode->x = ps->_phead->x + 2;
		pNextnode->y = ps->_phead->y;
		break;
	}

	//检查下一个节点是否是食物
	if (NextIsFood(pNextnode, ps))
	{
		EatFood(pNextnode, ps);
	}
	else
	{
		NoFood(pNextnode, ps);
	}

	//判断蛇是否撞墙
	KillByWall(ps);
	//判断蛇是否撞到自己
	KillByMyself(ps);

}

//游戏运行的逻辑
void GameRun(psnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		//打印总分数和食物的分数
		SetPos(64, 10);
		printf("总分数是:%d\n", ps->_score);
		SetPos(64, 11);
		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 = EXIT;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_time_sleep > 80)
			{
				ps->_time_sleep -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_time_sleep += 30;
				ps->_food_weight -= 2;
			}
		}
		
		//蛇走一步的过程
		SnakeMove(ps);

		Sleep(ps->_time_sleep);
		//蛇走完一步再让蛇睡眠一下,可以达到蛇动态的移动过程	

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

//游戏善后的工作
//游戏的状态是因为什么而结束的
void GameEnd(psnake ps)
{
	SetPos(24, 12);
	//定位到屏幕的正中央然后打印
	switch (ps->_status)
	{
	case KILL_BY_MYSELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case EXIT:
		wprintf(L"您主动结束游戏\n");
		break;
	}

	//释放掉蛇身的节点
	psnakenode cur = ps->_phead;
	while (cur)
	{
		psnakenode Del = cur;
		cur = cur->next;
		free(Del);
	}
}

Snake.h

#define _CRT_SECURE_NO_WARNINGS
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.h>

//#define 后面不用打括号

//定位,Y是X的两倍
#define POS_X 24
#define POS_Y 5

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


//类型的声明 

//蛇的方向
enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//蛇的状态
//撞到自己,撞到墙,正常运行,正常退出
enum STATUS
{
	OK,//正常运行
	EXIT,//正常退出
	KILL_BY_WALL,//撞到墙
	KILL_BY_MYSELF//撞到自己
};

//蛇身的节点类型
typedef struct SnakeNode
{
	int x;
	int y;
	//坐标
	struct SnakeNode* next;
	//指向下一个节点的指针
}snakenode,* psnakenode;

//typedef struct SnakeNode* pSnake; == 上面的写法 

//贪吃蛇
typedef struct SnakeList
{
	psnakenode _phead;//指向蛇头节点的指针
	psnakenode _food;//指向食物节点的指针
	enum DIRECTION _dir;//蛇的方向
	enum STATUS _status;//蛇的状态
	int _food_weight;//一个食物的分数
	int _score;//总分数
	int _time_sleep;//休息的时间,时间越短,速度越快,时间越长,速度越慢
}snake,* psnake;


//函数的声明

//坐标的定位
//定位光标的位置
void SetPos(short x, short y);

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

//欢迎界面的打印
void WelComeToGame();

//创建地图
void CreatMap();

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

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

//游戏运行的逻辑
void GameRun(psnake ps);

//蛇的移动 -- 走一步
void SnakeMove(psnake ps);

//判断下一个坐标是否是食物
//pn是下一个节点的指针
int NextIsFood(psnakenode pn, psnake ps);

//下一个坐标是食物就吃掉食物
void EatFood(psnakenode pn, psnake ps);

//下一个坐标不是食物
void NoFood(psnakenode pn, psnake ps);

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

//检测蛇是否撞到自己
void KillByMyself(psnake ps);

//游戏善后的工作
void GameEnd(psnake ps);

下一个坐标不是食物

在这里插入图片描述
打印前四个节点,打印完第四个节点退出循环,把cur下一个节点打印成空,也就是最后一个节点打印成空释放最后一个节点,把倒数第二个节点的下一个节点的地址置为NULL,倒数第二个节点不用打印,之前打印了前5个节点,节点不会消失,所以倒数第二个节点存在

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

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

相关文章

【服务治理中间件】consul介绍和基本原理

目录 一、CAP定理 二、服务注册中心产品比较 三、Consul概述 3.1 什么是Consul 3.2 Consul架构 3.3 Consul的使用场景 3.4 Consul健康检查 四、部署consul集群 4.1 服务器部署规划 4.2 下载解压 4.3 启动consul 五、服务注册到consul 一、CAP定理 CAP定理&#xff…

STM32使用ADC单/多通道检测数据

文章目录 1. STM32单片机ADC功能详解 2. AD单通道 2.1 初始化 2.2 ADC.c 2.3 ADC.h 2.4 main.c 3. AD多通道 3.1 ADC.c 3.2 ADC.h 3.3 main.c 3.4 完整工程文件 1. STM32单片机ADC功能详解 STM32单片机ADC功能详解 2. AD单通道 这个代码实现通过ADC功能采集三脚电…

掌握Android Fragment开发之魂:Fragment的深度解析(上)

Fragment是Android开发中用于构建动态和灵活界面的基石。它不仅提升了应用的模块化程度&#xff0c;还增强了用户界面的动态性和交互性&#xff0c;允许开发者将应用界面划分为多个独立、可重用的部分&#xff0c;每个部分都可以独立于其他部分进行操作。本文将从以下几个方面深…

ttkbootstrap界面美化系列之PanedWindow(七)

在界面设计中经常用PanedWindow控件来对整个界面进行切割布局&#xff0c;让整个界面看上去有层次感&#xff0c;不至于说杂乱无章。在我之前的文章中有对tkinter的该控件做了详细的介绍&#xff0c;链接如下基于Tkinter的PanedWindow组件进行窗口布局-CSDN博客 本文主要是介绍…

无人直播需要什么软件系统?最新AI实景自动无人直播软件:智能化引领直播拓客新时代

随着互联网的快速发展&#xff08;无人直播招商加盟&#xff1a;hzzxar&#xff09;直播行业已经成为商家品牌推广和商品销售的热门方式。近年来&#xff0c;人工智能技术的飞速发展&#xff0c;催生了一款令人惊叹的AI实景自动无人直播软件&#xff0c;为商家提供了全新的直播…

2024视觉与学习青年学者研讨会(VALSE 2024)热点推文预告

视觉与学习青年学者研讨会&#xff08;VALSE&#xff09;是国内人工智能领域顶尖学者一年一度的研讨会。该会议的特点是大、全、新。会议的规模大&#xff0c;参会者达到五千人以上&#xff1b;会议的主题全&#xff0c;全面覆盖人工智能的各大领域&#xff1b;会议的内容新&am…

SQL奇难怪状知识点分享

SQL执行顺序 select 语句的完整结构&#xff1a; select 去重 要查询的字段 from表&#xff08;注意&#xff1a;表和字段可以取别名&#xff09; xxxx&#xff08;left/right/full&#xff09; join 要连接的表 on 等值判断&#xff08;顺序&#xff1a;先on再where&#x…

内存卡罢工,数据危机?别急,有救!

在日常生活和工作中&#xff0c;我们越来越依赖于各种电子设备来存储重要数据。其中&#xff0c;内存卡因其便携性和大容量而广受欢迎。然而&#xff0c;当内存卡突然损坏打不开时&#xff0c;我们该如何应对&#xff1f;本文将为您详细解析这一问题&#xff0c;并提供有效的解…

JAVA版本的ATM编程问题记录

前段时间用C语言写了个银行ATM系统&#xff0c;还写了一篇文章记录了一些&#xff0c;C语言的ATM文章。后来又用IDEA写了一个JAVA版本的银行ATM。有人就会问为啥浪费这个时间写ATM呢&#xff1f;&#x1f9d0;其实是我本科代码没学好&#xff0c;所以现在想利用比较熟悉的ATM系…

探秘编程之旅:Baidu Comate 智能代码助手的魔法揭秘

目录 Baidu Comate智能代码助手1.场景需求2.安装步骤3.功能介绍3.1 /指令3.2 插件3.3 #知识 4.使用体验5.总结 Baidu Comate智能代码助手 智能编程助手的意义在于提升编程体验和效率&#xff0c;使开发人员能够更轻松、更快速地完成编码任务&#xff0c;是如今人工智能技术的一…

Flink DataSink介绍

介绍 Flink DataSink是Apache Flink框架中的一个重要组件&#xff0c;它定义了数据流经过一系列处理后最终的输出位置。以下是关于Flink DataSink的详细介绍&#xff1a; 概念&#xff1a;DataSink主要负责对经过Flink处理后的流进行一系列操作&#xff0c;并将计算后的数据结…

Linux学习笔记1

1.背景认知 可能很多人还没有接触Linux&#xff0c;会有点畏惧&#xff0c;我们可以把Linux类比成Windows&#xff0c; 下面是Windows和Linux的启动对比 Windows&#xff1a;上电后一开始屏幕是黑黑的---bios在启动Windows----Windows之后找到c盘启动各种应用程序 Linux&am…

OFDM802.11a的FPGA实现(十)导频插入(含verilog和matlab代码)

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 目录 1.前言2.插入导频原理3.硬件实现4.Matlab仿真5.ModelSim仿真6.结果对比验证7.verilog代码 1.前言 前面一篇文章完成了星座图的映射&#xff0c;今天继续设计后面的模块。在接收机…

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析 下图为keil编译后的结果&#xff1a; 单位为Byte。Code是程序大小。RO是常量大小。RW是读写变量占用大小&#xff0c;如已初始化的静态变量和全局变量。ZI是全零变量占用大小&#xff0c;如未初始化的static修饰的静态变量、全局…

聊聊BitLocker

最近有消息称微软决定在Windows 11 24H2中默认开启BitLocker&#xff0c;这个消息在网上引起了不小的波澜。有人说&#xff0c;对于我们这些普通用户来说&#xff0c;BitLocker真的有必要吗&#xff1f; 什么是BitLocker BitLocker 是一项 Windows 安全功能&#xff0c;可为整…

如何使用多协议视频汇聚/视频安防系统EasyCVR搭建智慧园区视频管理平台?

智慧园区作为现代化城市发展的重要组成部分&#xff0c;不仅承载着产业升级的使命&#xff0c;更是智慧城市建设的重要体现。随着产业园区竞争的逐渐白热化&#xff0c;将项目打造成完善的智慧园区是越来越多用户关注的内容。 然而我们往往在规划前期就开始面临众多难题&#…

如何制作有趣的gif?这个方法别错过

是否在社交媒体上看到过很多有趣好玩的gif动图&#xff0c;有的搞笑有趣有的又很可爱。大家有没有想过自己动手制作gif动画呢&#xff1f;接下来&#xff0c;就给大家分享一招gif在线制作&#xff08;https://www.gif5.net/&#xff09;的方法&#xff0c;超简单不需要下载任何…

什么牌子的洗地机质量最好?四款耐用高分产品推荐

洗地机具备了吸尘、擦拭、除菌等多种功能&#xff0c;可以一次完成多种清洁任务&#xff0c;帮助用户更高效地保持家居整洁&#xff0c;节省时间和精力&#xff0c;备受人们的喜爱。但是怎么挑选到优质的洗地机一直是大家关注的问题。今天&#xff0c;笔者将结合自己在家电行业…

什么是驱动数字签名?如何获取驱动数字签名?

Windows 驱动程序承载着计算机实现的各种内核和用户模式功能。如果驱动程序被黑客攻击&#xff0c;可能会产生很多问题。Windows通过数字签名来验证驱动程序包的完整性及发布者的身份。2020年10月的安全更新中&#xff0c;微软加强了对驱动软件的验证&#xff0c;如果Windows无…

【微积分听课笔记】全微分,二元极值,Double Integral

6.6 二元函数的极值_哔哩哔哩_bilibili 此笔记为听课笔记&#xff0c;宋浩老师微积分~ 最近诸事缠身&#xff0c;会有种会不会只做一件事好些。实际上&#xff0c;关键在于动力&#xff0c;我不可能每次都准备充分。动力&#xff0c;分配&#xff0c;这是目前进入大学我正在学…
最新文章