【c语言】简单贪吃蛇的实现

2024-02-26 1324阅读

温馨提示:这篇文章已超过408天没有更新,请注意相关的内容是否还可用!

目录

一、游戏说明

​编辑

二、地图坐标​

​编辑

三、头文件

四、蛇身和食物​

五、数据结构设计​

蛇节点结构如下:

封装一个Snake的结构来维护整条贪吃蛇:​

蛇的方向,可以一一列举,使用枚举:

游戏状态,可以一一列举,使用枚举:

六、Snake.c

5.1、游戏开始函数

定位控制台的光标位置

欢迎来到贪吃蛇游戏

创建一个地图

初始化创建蛇身的节点

创建第一个食物​

5.2、游戏运行函数

检测按键状态,我们封装了一个宏

打印帮助信息

暂停函数

下一个是否是食物

下一步要走的位置处就是食物,就吃掉食物

如果下一步不是食物

检测是否撞墙

检测是否撞自己

蛇移动的函数

七、游戏结束的资源释放

八、Test.c


一、游戏说明

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

    【c语言】简单贪吃蛇的实现

    【c语言】简单贪吃蛇的实现

    【c语言】简单贪吃蛇的实现

    二、地图坐标​

    我们假设实现一个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙,如下:

    【c语言】简单贪吃蛇的实现

    三、头文件

    #include
    #include
    #include
    #include
    #include
    #define Case break;case
    #define WALL L'□'
    #define BODY L'●'
    #define FOOD L'☆'
    //默认的起始坐标
    #define POS_X 24
    #define POS_Y 5
    #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
    //贪吃蛇,蛇身节点的定义
    typedef struct SnakeNode
    {
    	int x;
    	int y;
    	struct SnakeNode* next;
    }SnakeNode, * pSnakeNode;
    //typedef struct SnakeNode* pSnakeNode;
    enum GAME_STATUS
    	//游戏状态
    {
    	OK = 1,//正常运行
    	ESC,//按了ESC键退出,正常退出
    	KILL_BY_WALL,//撞墙
    	KILL_BY_SELF//撞到自身
    };
    //行走的方向
    enum DIRECTION
    	//方向
    {
    	UP = 1,
    	DOWN,
    	LEFT,
    	RIGHT
    };
    //贪吃蛇
    typedef struct Snake
    {
    	pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头
    	pSnakeNode pFood;//指向食物的指针
    	int Score;//当前累积的分数
    	int FoodWeight;//一个食物的分数,默认每个食物10分
    	int SleepTime;//每走一步休眠时间?
    	//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢
    	enum GAME_STATUS status;//游戏当前的状态
    	enum DIRECTION dir;//蛇当前走的方向,蛇头的方向默认是向右
    	//...
    }Snake,* pSnake;
    //typedef struct Snake* pSnake;
    //定位控制台的光标位置
    void SetPos(int x, int y);
    //游戏开始的准备
    void GameStart(pSnake ps);
    //打印欢迎界面
    void welcomeToGame();
    //绘制地图
    void CreateMap();
    //初始化蛇
    void InitSnake(pSnake ps);
    //创建食物
    void CreateFood(pSnake ps);
    //游戏运行的整个逻辑
    void GameRun(pSnake ps);
    //打印帮助信息
    void PrintHelpInfo();
    //蛇移动的函数- 每次走一步
    void SnakeMove(pSnake ps);
    //判断蛇头的下一步要走的位置处是否是食物
    int NextIsFood(pSnake ps, pSnakeNode pNext);
    //下一步要走的位置处就是食物,就吃掉食物
    void EatFood(pSnake ps, pSnakeNode pNext);
    //下一步要走的位置处不是食物,不吃食物
    void NotEatFood(pSnake ps, pSnakeNode pNext);
    //检测是否撞墙
    void KillByWall(pSnake ps);
    //检测是否撞自己
    void KillBySelf(pSnake ps);
    //游戏结束的资源释放
    void GameEnd(pSnake ps);

    四、蛇身和食物​

    初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。

    注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有一半儿出现在墙体中,另外一般在墙外的现象,坐标不好对齐。

    关于食物,就是在墙体内随机生成一个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。

    五、数据结构设计​

    在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信

    息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行。

    #define WALL L'□'  墙
    #define BODY L'●'  蛇身
    #define FOOD L'☆' 食物

    蛇节点结构如下:

    //贪吃蛇,蛇身节点的定义
    typedef struct SnakeNode
    {
    	int x;
    	int y;
    	struct SnakeNode* next;
        //是一个指向下一个 SnakeNode 类型节点的指针,用于构建链表来表示蛇的身体。
    }SnakeNode, * pSnakeNode;
    //typedef struct SnakeNode* pSnakeNode;

    封装一个Snake的结构来维护整条贪吃蛇:​

    pSnakeNode pSnake:这是一个指向 SnakeNode 类型的指针,代表蛇的头部。通常,贪吃蛇的实现会用一个链表来表示蛇的身体,其中每个节点(SnakeNode)代表蛇身体的一部分,而 pSnake 指向这个链表的第一个节点,即蛇头。

    pSnakeNode pFood:这是一个指向 SnakeNode 类型的指针,代表食物的位置。在贪吃蛇游戏中,食物会被随机放置在游戏区域内,当蛇吃到食物时,这个食物会被移除,并且蛇的身体会增长。

    enum GAME_STATUS status;:这是一个枚举类型,表示游戏当前的状态。具体的枚举值没有在代码中给出,但可能包括“游戏中”、“游戏结束”等状态。

    enum DIRECTION dir;:这是一个枚举类型,表示蛇当前移动的方向。具体的枚举值也没有在代码中给出,但通常包括“向上”、“向下”、“向左”、“向右”等方向。

    typedef struct Snake
    {
    	pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头
    	pSnakeNode pFood;//指向食物的指针
    	int Score;//当前累积的分数
    	int FoodWeight;//一个食物的分数,默认每个食物10分
    	int SleepTime;//每走一步休眠时间?
    	//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢
    	enum GAME_STATUS status;//游戏当前的状态
    	enum DIRECTION dir;//蛇当前走的方向,蛇头的方向默认是向右
    	//...
    }Snake,* pSnake;
    //typedef struct Snake* pSnake;
    

    蛇的方向,可以一一列举,使用枚举:

    //行走的方向
    enum DIRECTION
    	//方向
    {
    	UP = 1,
    	DOWN,
    	LEAF,
    	RIGHT
    };

    游戏状态,可以一一列举,使用枚举:

    enum GAME_STATUS
    	//游戏状态
    {
    	OK = 1,//正常运行
    	ESC,//按了ESC键退出,正常退出
    	KILL_BY_WALL,//撞墙
    	KILL_BY_SELF//撞到自身
    };

    六、Snake.c

    5.1、游戏开始函数

    void GameStart(pSnake ps)
    {
    	//设置控制台的信息,窗口大小,窗口名
    	system("mode con cols=120 lines=40");
    	system("title 贪吃蛇");
    	//隐藏光标
    	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    	CONSOLE_CURSOR_INFO CursorInfo;
    	GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
    	CursorInfo.bVisible = false;
    	SetConsoleCursorInfo(handle, &CursorInfo);//设置光标信息
    	//打印欢迎信息
    	welcomeToGame();
    	//绘制地图
    	CreateMap();
    	//初始化蛇
    	InitSnake(ps);
    	//创建食物
    	CreateFood(ps);
    }

    定位控制台的光标位置

    //定位控制台的光标位置
    void SetPos(int x, int y)
    {
    	//获得设备句柄
    	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    	//根据句柄设置光标的位置
    	COORD pos = { x,y };
    	SetConsoleCursorPosition(handle, pos);
    }

    欢迎来到贪吃蛇游戏

    void welcomeToGame()
    {
    	//欢迎信息
    	SetPos(35, 10);
    	printf("欢迎来到贪吃蛇小游戏\n");
    	SetPos(38, 20);
    	system("pause");
    	system("cls");
    	//功能介绍信息
    	SetPos(15, 10);
    	printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速\n");
    	SetPos(15, 11);
    	printf("加速能得到更高的分数");
    	SetPos(38, 20);
    	system("pause");
    	system("cls");
    }

    创建一个地图

    创建地图就是将墙打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使用L​

    打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

    void CreateMap()
    {
    	int i = 0;
    	//上
    	SetPos(0, 0);
    	for (i = 0; i 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->FoodWeight = 10;
    	ps->pFood = NULL;
    	ps->Score = 0;
    	ps->SleepTime = 200;
    	ps->status = OK;
    }

    创建第一个食物​

    • 先随机生成食物的坐标
    • x坐标必须是2的倍数​
    • 食物的坐标不能和蛇身每个节点的坐标重复
    • 创建食物节点,打印食物
      void CreateFood(pSnake ps)
      {
      	int x = 0;//x范围: 2~54 -> 0~52 + 2 -> rand()%53 + 2
      	int y = 0;//y范围: 1~25 -> 0~24 + 1 -> rand()%24 + 1
      again:
      	do {
      		x = rand() % 53 + 2;
      		y = rand() % 24 + 1;
      	} while (x % 2 != 0);
      	
      	//坐标和蛇的身体的每个几点的坐标比较
      	pSnakeNode cur = ps->pSnake;
      	while (cur)
      	{
      		if (x == cur->x && y == cur->y)
      		{
      			goto again;
      		}
      		cur = cur->next;
      	}
      	//创建食物
      	pSnakeNode pFood = malloc(sizeof(SnakeNode));
      	if (pFood == NULL)
      	{
      		perror("CreateFood:malloc()");
      		return;
      	}
      	pFood->x = x;
      	pFood->y = y;
      	ps->pFood = pFood;
      	SetPos(x, y);
      	wprintf(L"%lc", FOOD);
      }

      5.2、游戏运行函数

      游戏运行期间,右侧打印帮助信息,提示玩家

      根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。​

      如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。

      确定了蛇的方向和速度,蛇就可以移动了。

      void GameRun(pSnake ps)
      {
      	//打印帮助信息
      	PrintHelpInfo();
      	//检测按键
      	do
      	{
      		//当前的分数情况
      		SetPos(62, 10);
      		printf("总分:%5d\n", ps->Score);
      		
      		SetPos(62, 11);
      		printf("食物的分支:%02d\n", ps->FoodWeight);
      		//检测按键
      		//上、下、左、右、ESC、空格、F3、F4
      		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_ESCAPE))
      		{
      			ps->status = ESC;
      			break;
      		}
      		else if (KEY_PRESS(VK_SPACE))
      		{
      			//游戏要暂停
      			pause();//暂停和开始
      		} 
      		else if (KEY_PRESS(VK_F3))
      		{
      			if (ps->SleepTime >= 80)
      			{
      				ps->SleepTime -= 30;
      				ps->FoodWeight += 2;
      			}
      		}
      		else if (KEY_PRESS(VK_F4))
      		{
      			if (ps->FoodWeight > 2)
      			{
      				ps->SleepTime += 30;
      				ps->FoodWeight -= 2;
      			}
      		}
      		
      		//走一步
      		SnakeMove(ps);
      		//睡眠一下
      		Sleep(ps->SleepTime);
      		
      	}while(ps->status == OK);
      	
      }

      检测按键状态,我们封装了一个宏

      #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

      打印帮助信息

      //打印帮助信息
      void PrintHelpInfo()
      {
      	SetPos(62, 15);
      	printf("1. 不能穿墙. 不能咬到自己");
      	SetPos(62, 16);
      	printf("2. 用 ↑ . ↓ . ← . → 来控制蛇的移动");
      	SetPos(62, 17);
      	printf("3. F3是加速,F4是减速");
      	SetPos(62, 19);
      	printf(" ");
      }

      暂停函数

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

      下一个是否是食物

      //pSnakeNode psn 是下一个节点的地址​
      //pSnake ps 维护蛇的指针
      int NextIsFood(pSnake ps, pSnakeNode pNext)
      {
      	if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
      		return 1;
      	else
      		return 0;
      }

      下一步要走的位置处就是食物,就吃掉食物

      //下一步要走的位置处就是食物,就吃掉食物
      void EatFood(pSnake ps, pSnakeNode pNext)
      {
      	pNext->next = ps->pSnake;
      	ps->pSnake = pNext;
      	pSnakeNode cur = ps->pSnake;
      	//打印蛇身
      	while (cur)
      	{
      		SetPos(cur->x, cur->y);
      		wprintf(L"%lc", BODY);
      		cur = cur->next;
      	}
      	ps->Score += ps->FoodWeight;
      	//释放旧的食物
      	free(ps->pFood);
      	//新建食物
      	CreateFood(ps);
      }
      

      如果下一步不是食物

      将下一个节点头插入蛇的身体,并将之前蛇身最后一个节点打印为空格,放弃掉蛇身的最后一个节点

      //pSnakeNode psn 是下一个节点的地址​
      //pSnake ps 维护蛇的指针​
      void NotEatFood(pSnake ps, pSnakeNode pNext)
      {
      	//头插法
      	pNext->next = ps->pSnake;
      	ps->pSnake = pNext;
      	//释放尾结点
      	pSnakeNode cur = ps->pSnake;
      	while (cur->next->next)
      	{
      		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 == 25)
      	{
      		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;
      			return;
      		}
      		cur = cur->next;
      	}
      }

      蛇移动的函数

      先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。

      确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理

      (EatFood),如果不是食物则做前进一步的处理(NoFood)。​

      蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。

      void SnakeMove(pSnake ps)
      {
      	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
      	if (pNext == NULL)
      	{
      		perror("SnakeMove():malloc()");
      		return;
      	}
      	pNext->next = NULL;
      	switch (ps->dir)
      	{
      	case UP:
      		pNext->x = ps->pSnake->x;
      		pNext->y = ps->pSnake->y - 1;
      		break;
      	Case DOWN:
      		pNext->x = ps->pSnake->x;
      		pNext->y = ps->pSnake->y + 1;
      	Case LEFT:
      		pNext->x = ps->pSnake->x - 2;
      		pNext->y = ps->pSnake->y;
      	Case RIGHT:
      		pNext->x = ps->pSnake->x + 2;
      		pNext->y = ps->pSnake->y;
      		break;
      	}
      	//下一个坐标是否是食物
      	if (NextIsFood(ps, pNext))
      	{
      		//是食物就吃掉
      		EatFood(ps, pNext);
      	}
      	else {
      		//不是食物就正常一步
      		NotEatFood(ps, pNext);
      	}
      	//检测撞墙
      	KillByWall(ps);
      	
      	//检测是否撞自己
      	KillBySelf(ps);
      }

      七、游戏结束的资源释放

      游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。​

      //游戏结束的资源释放
      void GameEnd(pSnake ps)
      {
      	SetPos(20, 15);
      	switch (ps->status)
      	{
      	case ESC:
      		printf("主动退出游戏,正常退出\n");
      	Case KILL_BY_WALL:
      		printf("很遗憾,撞墙了,游戏结束\n");
      	Case KILL_BY_SELF:
      		printf("很遗憾,撞到自己了,游戏结束\n");
      		break;
      	}
      	
      	//释放贪吃蛇的链表资源
      	pSnakeNode cur = ps->pSnake;
      	pSnakeNode del = NULL;
      	while (cur)
      	{
      		del = cur;
      		cur = cur->next;
      		free(del);
      	}
      	free(ps->pFood);
      	ps = NULL;
      }

      八、Test.c

      void test()
      {
      	//创建贪食蛇
      	Snake snake = { 0 };
      	//GameStart(&snake);//游戏开始前的初始化
      	//GameRun();//玩游戏的过程
      	//GameEnd();//善后的工作
      	int ch = 0;
      	do
      	{
      		Snake snake = { 0 };
      		GameStart(&snake);//游戏开始前的初始化
      		GameRun(&snake);//玩游戏的过程
      		GameEnd(&snake);//善后的工作
      		SetPos(15, 20);
      		printf("再来一局吗?(Y/N):");
      		ch = getchar();
      		getchar();// 清理\n
      	} while (ch == 'Y' || ch == 'y');
      }
      
      int main()
      {
      	//修改适配本地的环境
      	setlocale(LC_ALL, "");
      	test();//贪吃蛇游戏的测试
      	SetPos(0, 30);
      	return 0;
      }

      今天就先到这了!!!

      【c语言】简单贪吃蛇的实现

      看到这里了还不给博主扣个:

      ⛳️ 点赞☀️收藏 ⭐️ 关注!

      你们的点赞就是博主更新最大的动力!

      有问题可以评论或者私信呢秒回哦。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]