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

目录

游戏效果展示

QQ录屏20240507163633

文件

代码的展示

test.c

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

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

c 复制代码
#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个节点,节点不会消失,所以倒数第二个节点存在

相关推荐
爱吃生蚝的于勒43 分钟前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
神仙别闹5 小时前
基本MFC类框架的俄罗斯方块游戏
c++·游戏·mfc
ChoSeitaku6 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
DdddJMs__1356 小时前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
娃娃丢没有坏心思7 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
workflower7 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
ahadee8 小时前
蓝桥杯每日真题 - 第11天
c语言·vscode·算法·蓝桥杯
No0d1es9 小时前
2024年9月青少年软件编程(C语言/C++)等级考试试卷(九级)
c语言·数据结构·c++·算法·青少年编程·电子学会
Che3rry10 小时前
C/C++|关于“子线程在堆中创建了资源但在资源未释放的情况下异常退出或挂掉”如何避免?
c语言·c++