C语言----贪吃蛇(附源代码)

       说到贪吃蛇大家应该都不陌生吧,我觉得小时候大家都玩过,反正我记得我小时候对这个游戏还算是有印象的,毕竟在当年洛基亚横行的年代,这个可是手机上必不可少的应用了,那么今天我们就来搞一个相较于弱一点的贪吃蛇。

预处理

        大家都写过这么多的代码了,应该知道,也来我们方便运行和观看,我们通常先创建两个源文件后再创建一个头文件。

27610c10987545509f9257f52c67d278.png

蛇(链表)

蛇的节点

       好,那么我们想一想我们以前玩的贪吃蛇,是连在一起的吧,不是东一部分西一部分吧。所以我们创建一个链表然后来表示蛇的一个节点。那么我们知道链表中的除了下一个节点的地址还有数据是吧,就是我们节点的内容,那么我们要存什么嘞。那这就不得不引出一个新的知识点,我们的命令板其实是一个xy轴,虽然与我们学习过的xy轴是一样都有x,y但是我们这里的y轴在下面,但是我们的写法还是没有变化的还是(x,y)。那我为什么要说明这个东西嘞,是因为我们后面蛇和食物以及后面的一些提示肯定不能从(0,0)开始打印啊,我们要调整光标的位置,就是我们编译是那个一闪一闪的,那时我们打印下一个东西的地方。好了这里我只是向大家提及一下这个知识以便于我们后面使用的时候大家很疑惑。

c70129b7ddda41ec89d20f40934e3e23.png

       那么我们确定了节点里面的存储物了,那么我们创建一个结构体,并且为它改个名字,这样我们后面方便写:

1f1aa1cb962945008a0b565713392238.png

蛇的方向

      当我们创建好蛇的结构体后,我们蛇运动怎么走啊,这样我们是不是就要在创建一个枚举来表示蛇的运动方向呀,虽然我们知道当我们面向左边的时候不能向反方向走,但是我们要先创建出可能要走的方向啊,不管有没有用,我有肯定是好的呀,所以我们就再创建应该枚举表示蛇的运动方向:4927dfae08bb433497d01b27f4e895d7.png

蛇的状态

      我们写了蛇,蛇的方向,那么我们都知道蛇有三种状态,一种是正常情况,二是撞墙,三是咬到自己了。我们是不是也要写出来啊,不然我们就写了个bug出来嘛,然我我们都知道有时候我们玩着玩着不想玩了,想退出,那这是不是也是一种状态啊。那么我们这里就确认了四种状态,正常游戏,撞墙,咬自己和退出游戏。那么我们就再写一个枚举来表示这四种状态:190b22ff6707427b89151a45f7b550b6.png

蛇的数据

      ok,当我们写完这些前置准备后,我们是不是该考虑后面游戏内的数据了呀,例如什么要有地图吧(虽然以前玩的是没有边界的但是我们这里因为是阉割板所以我们这里是有地图的),要有开局前的提示什么吧,还是什么分数吧等等,这里是不是也要定义一个结构体嘞:c74c6b7b27cb483b9aa5bb156e28b54a.png

       那么上面就是我们贪吃蛇的一些前置条件,当如还有一些头文件,但是我会在后面把源代码发出来的。

光标

      当我们处理好前置条件后,我们需要再来处理一件事,就是我在前面给大家说过的,光标,大家现在都知道我们命名板是一个xy轴,那么我们如何移动光标和让光标消失嘞,毕竟我们也不想看到屏幕上始终有一个一闪一闪的东西打搅我们的思路吧。

void GameStart(psnake ps)
{
	//设置窗口大小,改变窗口名字
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标

	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取光标句柄
	CONSOLE_CURSOR_INFO cursorinfo;
	GetConsoleCursorInfo(houtput, &cursorinfo);//获取光标信息
	cursorinfo.bVisible = false;//隐藏光标
	SetConsoleCursorInfo(houtput, &cursorinfo);//设置光标状态
}

        上面嘞是关于光标的一些指令与步骤,我知道大家也许不是很理解上面代码的意思,我们现在的主题毕竟是贪吃蛇嘛,所以后面我会写一篇博客来向大家解析一下上面的代码作用与使用方法。好吧,大家就先知道有这么个东西和有什么有就好了。

改变光标位置

       当我们看过上面的关于光标的一些基础知识了,那我们知道光标的位置不能是(0,0),那么我们需要改变把,并且我们使用的频率应该不会少吧,那么我们直接封装一个函数,就来表示改变光标位置,这样我们使用就不用再写一遍源代码了:86427b1eedff4f4bab7a5addb0f7124d.png

游戏

       以上就是真的我们贪吃蛇的前置条件了o(* ̄3 ̄)o 接下来我们就要正式写关于实现游戏的代码了。

游戏开始界面

       那现在我们就开始了,我们都知道你打开游戏前都有一个界面,毕竟我们肯定是点错了,打开游戏,并且我们写一个游戏界面来给那些没玩过这些游戏的朋友简绍游戏游戏的玩法。这样也是很好的。因为我打算在最后把源代码发出来,那么我最好的时候将整局游戏示意图也录下来。这里我就直接写代码。

47f9d76387de476e87539d5194e9c99e.png

        首先我们使用setpos函数来确定光标的位置,再使用wprintf函数来打印内容system(“pause”)语句则代表着暂停程序,按任意键表示继续程序,而system(“cls”)语句则代表着将当前控制台的页面全部清空,这样我们就打印出了我们的欢迎界面。后面大家的命令板名字会改变还会打印出上面我们写的字符。

游戏地图

        我们在博客开始前也说过我们的贪吃蛇是被阉割过的是有地图的,所以我们现在要开始把我们的地图打印出来。然后地图嘞,不就是一圈东西围着的嘛,那么我们想一想我们围一圈,从0行开始画,然后多少列,最好多少行多少列,然后画左右的,那是不是我们就简单理解为画上下左右的图形就可以了。我们用循环嘛,就是利用改变光标位置来打印就可以了。那么大家来看看如何打印地图的:075905fa6b5e42dcb52a54686ad5156b.png

       但是这里大家可能会问,什么啊,我们左右要比上下小,是因为我们画的是矩形嘛。这是数据不一样的原因之一,还有就是其实我们的c语言的xy轴比例大小是不一样的,y轴要占两个位置大小,但是x却只要一个大小:这里我截取了一部分,大家可以确认一下。就是因为y轴要比x轴大,所以我们要实现的时候要先确定地图的大小然后来思考应该是多少(x,y)。1863be47452947898d0a1dcfa3068a52.png

蛇的初始化

         好了,我们现在有开始界面和地图了,那么我们现在有请我们的主角蛇出场,我们嘞,想一下蛇开始要有点长度吧,是吧,毕竟不能只有一个头吧,所以我们把一开始的蛇的节点设置为5个,然后我们需要再注意的是,我们前面说过我们的命令面板x轴是y轴的两倍,那么我们设置x轴的时候就不能出现奇数,必须是偶数,不然的后面撞墙有一半在墙里面一半在外面,是不是,感觉就很奇怪。好。我们确定了蛇的节点位置,那么我们蛇长什么样子啊,是吧,我们也需要确定啊,如果我们地图是□那蛇的身体就●吧,然后食物就★。并且因为我们后面还会使用这些所以我们就在头文件中宏定义。aa31f21ea93648a98737e08f5abcc62f.png

       那么这样我们就可以开始写代码了:这里我只是先给大家看一下,最后大家看源代码就可以了

5166ded55a3743a6b69e164d359dc863.png

蛇的食物

      当我们创建后蛇后,我们就需要创建蛇的食物了吧,那我们想一想:食物不能再墙里面吧,食物不能出现在蛇的身体内部吧,还有就是食物肯定也不能卡在中间坐标x是奇数吧并且我们的食物是随机刷新的不能一直在一个地方吧,这里就与我以前写过的扫雷游戏中的随机生成一个地雷一样。需要产生随机值。1f6e328e95cb4970bc0367d2e2d54bef.png

维护

       好了到现在,我们看看是不是写的都是给我们玩家看的,我们现在有一个壳子,那内在我们是不是要有很多代码来维护游戏运行。

提醒

       我们现在思考一下,我们是玩过贪吃蛇,并且我们现在的玩法是可以点击F3加速F4减速的所以我们需要在地图旁边打印出来,反正我们要打印分数要不在把我们的玩法也打印出来吧。这样更可以提现我们的细心。

蛇的方向以及分数

      好了,我们写了游戏的前置和提醒了。我们现在就来写蛇的运动了。大家应该都看到了前面我们写了蛇可能的运动方向。所以我们需要用上。1:但其实我们想一想蛇的方向是不是要检查是否按下过按键吧。因为只有你按下了方向键蛇才移动嘛。然后我们在移动的时候需要判断方向因为我们不可能向反方向移动吧。所以这是我们需要考虑的一个点,然后就是我们移动其实是不是就是一次只能移动一下,就是你不能同一时间向两个方向移动吧,那么我们判断方向的时候就只能用if,elseif。我们在判断按键除了方向键外,是不是还有暂停和退出,加速减速啊,我们这里就一下子全部写出来吧:2:但我们还有一个问题没有讨论就是我们的速度上限,我们没想过啊,我们都知道像手动挡都是有一个速度标准的,速度到了但档位没到就有问题吧,但我们不考虑这么多,我们就考虑一个速度最高是多少,和每一个速度代表多少分就可以了。3:当我们考虑了蛇移动应该怎么走,那么我们是不是需要再考虑一个事情就是,在我们没有吃到食物的前,我们蛇的长度没有变,但是我们移动了,那么我们蛇尾部的节点是不是就应该没有了呀,我们知道链表,我们每一个节点都是重新申请的一个节点,那既然我们没有申请新节点,并且我们移动了,那我们以前位置的尾节点是不是应该释放啊,不然的话,我们控制蛇移动,尾部一直没有释放那它就会拖得很长就是一个bug了。4:大家也看到了上面的标题我们写的是方向以及分数,那我们前3步都是方向,所以我们第4步就写文关于分数的代码了。但其实这是很简单的啊,我们只需要改变光标位置然后,因为我们在开头定义结构体中写过游戏分数和食物分数,我们开头只需要打印出来,然后在后面的判断中改变并且打印出来。那么这步我们是不是也处理好了

        但是啊,我们在最后的时候怎么写了一个蛇走一步啊,开始前面我们没讲过啊,没讲过那么我们马上来讲。

蛇移动

      首先我需要解释一下为什么我将蛇的移动放在蛇的方向代码里面,因为我是觉得你蛇移动肯定要先确定移动方向,假如我把蛇移动放在外面的话感觉不是很合适,所以我把这个放在判断蛇方向里面了。好那么我们现在看看蛇移动是如何进行的。1:我们前面讲过这就是一个链表,移动一下就要申请一个新节点,并且判断这个节点是否申请成功,这个步骤大家应该不陌生了吧。2:我们申请了直接那么就需要赋值了呀,是吧。因为我们是头在动,那么我们就采用头插法,把申请的新节点指向原头节点,然后需要注意的是,横向是移动两个字节纵向是一个字节。3:前面我们想的是不是蛇直接走啊,没考虑走了这一步后会遇见什么啊,我们要考虑前面是食物?墙?还是自己?我们是不是也要判断啊。

      哎呀,我们这里怎么又有没有写完的代码啊,因为我们如果将这些全部都写在一个代码的话,这个代码是不是就很臃肿啊,这一个就有快100多行的代码。

判断食物

       继承上面说的,我们写了下一步是咬到食物,还是墙还是自己。我们就按顺序来写,先写判断是否是吃到食物。但是啊,我们判断是否是吃到食物有两个结果啊,前面是或者不是,我们这里是不是又要分为两个小部分,前面是吃到食物,后面是没吃到食物。但还有一个前置条件就是判断啊,代码怎么知道前面的代码是不是我们所说的是食物啊,那么我们是不是要再写一个代码来判断前面是不是食物,然后再分类啊1:判断前面是不是食物。其实这个我们是很简单的,我们只需要将下一个节点的地址与我们先前申请的食物节点判断x,y是不是一样的,如果是一样的,我返回一个1吧,来表示前面是食物,如果不一样的话,我们返回一个0来表示下一个节点不是食物:

2:吃到食物。因为我们前置的判断返回的是1,那么下一个节点是食物,我们蛇去咬了。我们就需要申请一个新字节(头插),然后将这个食物节点释放掉,因为这样我们才能再次打印蛇的时候,不会出现问题吧。然后我们我们吃到食物了,所以我们的尾节点就不用释放了吧,因为生长了一个节点所以不需要释放了呀。还有就是重新申请一个食物节点,我们不能吃到一个食物就游戏结束了吧。

3:没吃到食物。返回的不是1就是0了哦,那么我们没有吃到食物。因为我们没有吃到食物,首先我的头节点要变吧,因为如论如何我头都改变位置了呀,所以这个是必要的吧,然后就是我们把原本的尾节点改变吧,因为没吃到食物那么如果不释放尾节点就是我们开始说的bug了,但是还不够我们我们只是释放了,但我原本的东西没变啊,我们需要强制的打印尾空,不然也是尾巴增长了啊,是吧。

判断撞墙

       OK,我们写了好的情况,那么坏的也要来了。但其实这个很简单啊,是吧。我们只需要判断我们的头的坐标是否与我们开始创建地图的上下左右任意一方相同就可以了,是吧这个应该很容易就可以想出来了

判断咬自己

       我们写了撞墙后,我们就要判断下一个了,如果我们不小心让蛇的头部撞到了自己的身体如何判断。我们思考一下我们判断是否咬到自己就与我们判断是否撞墙是差不多的呀。只需要判断是否与我们的判断撞墙一样啊,就是判断坐标是否一样。区别就是我们判断是否咬直接是需要循环,为什么要循环嘞,因为我们编译器也不能直接判断蛇会咬到我们身体的哪一个节点,是吧。所以当我们到了判断要自己后遍历一遍看节点是否是与我们身体的某一个节点坐标相同,如果有一个坐标相同的话,那么我们就可以判断是咬到自己了,那么我们就可以结束游戏了吧。

游戏暂停

       写了上面的这些后,关于游戏的维护后就只剩下我们前面写的游戏暂停了吧,就是我们在玩的时候可能要暂时停止,那么我们也需要写吧,但是这个其实也简单啊。是吧,我们以前也学习过的,暂停就是休息嘛,让编译器暂时停止,sleep。但是我们停止时间不确定吧,因为我们停止就一直停止嘛,或者退出,那么我们是不是死循环sleep这个指令,但是我们也不能一直停止啊,如果一直死循环停止的话,我们回来要玩的话,就跳不出来了啊。那么我们就要在循环中写一个如果按下空格键后跳出循环游戏继续。

游戏结束

       上面的代码差不多就是我们游戏的所有代码了。但是我们都知道我们申请了内存都要释放吧,并且我们在上面也写过我们判断是否撞墙,咬自己等等。我们也说过,我们游戏结束不能就结束吧,玩家可以不管但是我们程序员不能不管啊。

游戏结束语和释放内存

       我们在玩家死亡后礼貌性的给玩家说明一下是因为什么才导致游戏结束的吧,这个可以有吧。并且这个也不难,我们只需要先前面一样。首先判断是玩家自己结束的游戏还是因为其他的结束游戏的。接着我们知道了是因为什么原因结束的游戏后,我们要告诉玩家啊。那我们打印出来这样的话是不是很直观啊。那么这个就可以引用我们前面的确认光标然后打印了。最后我们告诉玩家是如何结束的游戏后,就到了我们开始说过的释放内存了,虽然我们申请创建需要很多时间,但是我们释放就很快了呀,首先因为我们的单链表,我们释放只能从头开始,但是我们不能只释放头节点啊,不然我们下一次玩的话。因为上一次的数据没有完全释放,那么我们是不是就是一个有问题的代码了呀。既然要完全释放我们直接一个循环一个一个的释放直到释放尾节点后,就结束,这样我们的内存释放是不是也完全解决了呀。

总结

        因为我的贪吃蛇代码是阉割版所以与我们以前玩过的贪吃蛇是有一点差异的。但是大家还可以依据自己的想法进行优化和延伸。这里鄙人就简单的提供一下拙见,希望大家见谅。

代码

     下面就是鄙人的源代码了,大家稍微需要注意的是因为鄙人的博客与代码时间是不一样的,鄙人的代码后面稍微修改过的,所以大家如果需要对代码尽量以下面的源代码为准:

yun.h

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



#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 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 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();

//创建地图
void CreateMap();

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

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

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

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

//判断下一个坐标是否是食物
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);

yun.c

#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 CreateMap()
{
	//上
	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 <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}

	//右
	for (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->_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 CreateFood(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("CreateFood()::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)
{
	//0. 先设置窗口的大小,再光标隐藏
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput= GetStdHandle(STD_OUTPUT_HANDLE);
	//影藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false; //隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

	//1. 打印环境界面和功能介绍
	WelcomeToGame();
	//2. 绘制地图
	CreateMap();
	//3. 创建蛇
	InitSnake(ps);
	//4. 创建食物
	CreateFood(ps);
}

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

	SetPos(64, 18);
	wprintf(L"%ls", L"比特就业课制作");
}

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


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

//int NextIsFood(pSnakeNode pn, pSnake ps)
//{
//	if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
//		return 1;
//	else
//		return 0;
//}

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;

	//重新创建食物
	CreateFood(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);

	//把倒数第二个节点的地址置为NULL
	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;
			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 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 = 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)
{
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}

	//释放蛇身的链表

	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

text.c

#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');

	} while (ch=='Y' || ch=='y');
	SetPos(0, 27);
}

int main()
{
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();

	return 0;
}

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
用windows api 做的贪吃蛇 #include #include"resource.h" #include"Node.h" #include #include TCHAR szAppname[] = TEXT("Snack_eat"); #define SIDE (x_Client/80) #define x_Client 800 #define y_Client 800 #define X_MAX 800-20-SIDE //点x的范围 #define Y_MAX 800-60-SIDE //点y的范围 #define TIME_ID 1 #define SECOND 100 #define NUM_POINT 10 //点的总个数 #define ADD_SCORE 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口类 HACCEL hAccel;//加速键句柄 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口的水平和垂直尺寸被改变时,窗口被重绘 wndclass.lpfnWndProc = WndProc; //窗口过程为WndProc函数 wndclass.cbClsExtra = 0; //预留额外空间 wndclass.cbWndExtra = 0; //预留额外空间 wndclass.hInstance = hInstance; //应用程序的实例句柄,WinMain的第一个参数 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //设置图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //载入预定义的鼠标指针 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置画刷 wndclass.lpszMenuName = szAppname; //设置菜单 wndclass.lpszClassName = szAppname; //设置窗口类的名字 if (!RegisterClass(&wndclass))//注册窗口类 { MessageBox(NULL, TEXT("这个程序需要windows NT!"), szAppname, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppname, TEXT("Snack_eat"),//CreateWindow函数调用时,WndProc将受到WM_CREATE WS_OVERLAPPEDWINDOW&~WS_THICKFRAME& ~WS_MAXIMIZEBOX,//普通的层叠窗口&禁止改变大小&禁止最大化 CW_USEDEFAULT, //初始x坐标(默认) CW_USEDEFAULT, //初始y坐标 x_Client, //初始x方向尺寸 770 y_Client, //初始y方向尺寸 750 NULL, //父窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 WinMain函数中第二个参数 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口,iCmdShow是WinMain的第四个参数,决定窗口在屏幕中的初始化显示形式,例:SW_SHOWNORMAL表示正常显示 UpdateWindow(hwnd);//使窗口客户区重绘,通过向WndProc发送一条WM_PAINT消息而完成的 hAccel = LoadAccelerators(hInstance, szAppname);//加载加速键 while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }/* while (GetMessage(&msg, NULL, 0, 0))//GetMessage函数从消息队列中得到消息,填充msg。如果msg.message等于WM_QUIT,返回0,否则返回非0 { TranslateMessage(&msg);//将msg返回给windows已进行某些键盘消息的转换 DispatchMessage(&msg);//将msg再次返回给windows }*/ return msg.wParam;//msg.wParam是PostQuitMessage函数的参数值,通常是0 } ...
好的,这是一个非常有趣的项目!首先,你需要了解一些基本的数据结构和算法,例如链表和队列。然后,你需要设计游戏逻辑,包括蛇的移动、食物的生成和吃掉、游戏结束等。下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define WIDTH 20 #define HEIGHT 20 typedef struct Node { int x, y; struct Node *next; } Node; typedef struct Snake { Node *head, *tail; int size; int direction; // 0: up, 1: right, 2: down, 3: left } Snake; typedef struct Food { int x, y; } Food; Node* create_node(int x, int y) { Node *node = (Node*)malloc(sizeof(Node)); node->x = x; node->y = y; node->next = NULL; return node; } void destroy_node(Node *node) { free(node); } Snake* create_snake(int x, int y) { Snake *snake = (Snake*)malloc(sizeof(Snake)); Node *node = create_node(x, y); snake->head = snake->tail = node; snake->size = 1; snake->direction = 1; return snake; } void destroy_snake(Snake *snake) { Node *node = snake->head; while (node) { Node *next = node->next; destroy_node(node); node = next; } free(snake); } void move_snake(Snake *snake) { int dx[] = {0, 1, 0, -1}; int dy[] = {-1, 0, 1, 0}; int x = snake->head->x + dx[snake->direction]; int y = snake->head->y + dy[snake->direction]; Node *node = create_node(x, y); node->next = snake->head; snake->head = node; if (x == snake->tail->x && y == snake->tail->y) { snake->tail = snake->tail->next; destroy_node(snake->tail); } else { snake->size--; } } void generate_food(Food *food, Snake *snake) { srand(time(NULL)); do { food->x = rand() % WIDTH; food->y = rand() % HEIGHT; } while (food->x == snake->head->x && food->y == snake->head->y); } int check_collision(Snake *snake) { int x = snake->head->x; int y = snake->head->y; if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { return 1; // hit the wall } Node *node = snake->head->next; while (node) { if (x == node->x && y == node->y) { return 1; // hit itself } node = node->next; } return 0; } void print_board(Food *food, Snake *snake) { int board[HEIGHT][WIDTH] = {0}; board[food->y][food->x] = 2; Node *node = snake->head; while (node) { board[node->y][node->x] = 1; node = node->next; } for (int i = 0; i < HEIGHT; i++) { for (int j = 0; j < WIDTH; j++) { if (board[i][j] == 0) { printf("."); } else if (board[i][j] == 1) { printf("O"); } else if (board[i][j] == 2) { printf("X"); } } printf("\n"); } } int main() { Snake *snake = create_snake(WIDTH / 2, HEIGHT / 2); Food food; generate_food(&food, snake); while (1) { system("clear"); print_board(&food, snake); move_snake(snake); if (snake->head->x == food.x && snake->head->y == food.y) { snake->size++; generate_food(&food, snake); } if (check_collision(snake)) { printf("Game over!\n"); break; } usleep(100000); } destroy_snake(snake); return 0; } ``` 这段代码使用链表来存储蛇的身体,使用队列的方式来实现蛇的移动。每次移动时,新的节点插入到头部,而尾部的节点被删除。生成食物时,随机生成一个坐标,直到它不在蛇的身体上。检测碰撞时,分别检查是否撞墙或者撞到自己的身体。 当然,这只是一个简单的实现,你可以根据自己的需要进行修改和扩展。祝你好运!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值