C语言编写贪吃蛇-链表的使用

本文介绍了如何使用C语言在STM32微控制器上实现贪吃蛇游戏,利用链表数据结构管理蛇的身体。文章详细讲解了初始化设置、基本函数如更新地图、清理地图、链表操作等,并提供了游戏的关键代码,包括蛇的移动、吃到食物的判断及地图刷新等核心功能。
摘要由CSDN通过智能技术生成

C语言贪吃蛇游戏的实现-链表的使用

前言

本文使用C语言在STM32内实现贪吃蛇游戏,然后用LCD显示屏讲整个游戏显示出来。
在编写贪吃蛇的过程工涉及到只是有,结构体的定义,指针,链表,以及一些STM32微处理器的相关知识。贪吃蛇对流程控制以及相关知识运用是检验学习C语言的成果水平,以下让我们学习一下贪吃蛇的编写。

初始化定义

定义蛇身结构体并且此结构体包含链表。
解释:U8为unsigned char 。下面出现的U8同属:
此结构体包含XY以及下一个食物的地址。

struct snake_body{
	U8 Snake_X; 
	U8 Snake_Y;
	struct snake_body *snake_next;
}; //蛇身链表

typedef struct snake_body SNAKE_BODY;

然后规定一些主要边界参数:
主要以宏定义为主,由于LCD限制的原因只能设置蛇左右运动宽度为16,高度为5。
随机数也是,随机数用于控制食物地点。因此需要绘制出16*5的画布。

#define SNAKE_STRUCT_LEN sizeof(SNAKE_BODY)
#define SNAKE_AXIS_X  16           //XY轴设置
#define SNAKE_AXIS_Y  5
#define SNAKE_RAND_X  17           //随机数据上限
#define SNAKE_RAND_Y  6

变量定义

以下为变量定义以及相关解释

SNAKE_BODY* Sanke_Body;   //蛇身链表
U8 Sanke_Len = 1;         //蛇身长度
U8 Direct_Flag = 4;       //方向标志位-默认向右
U8 snake_RefleshFg = 0;   //刷新标志位 
U8 Game_Result = 0;       //游戏结果
U8 Game_Score = 0;        //记分
U8 Snake_Speed = 1;       //速度控制              
U8 Sanke_Fool_Buf[2];     //食物数组

基本函数

以下有几个基本函数说明:
1.将当前吃到的食物更新到链表内:

// *****************************************************************************
// 函数名称:Convert_StructMap
// 功能描述:修改对应数据
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Convert_StructMap(SNAKE_BODY body_temp,U8 len)
{
	U8 i = 0;
	U8 *p_map;
	SNAKE_BODY struct_temp = body_temp;
	
	//处理食物-显示食物
	if(Sanke_Fool_Buf[0]!=0 && Sanke_Fool_Buf[1]!=0)
	{
		p_map = &Game_Map[(Sanke_Fool_Buf[1]-1)][(Sanke_Fool_Buf[0]-1)*8];
		*p_map++=0x3f;
		*p_map++=0x33;
		*p_map++=0x21;
		*p_map++=0x21;
		*p_map++=0x33;
		*p_map=0x3f;
	}
	
	for(i = 0;i<len;i++)
	{
		p_map = &Game_Map[(struct_temp.Snake_Y-1)][(struct_temp.Snake_X-1)*8];
		
		//链表数据处理
		*p_map++=0x3f;
		if(i==0)
		{
		  *p_map++=0x3f;
		  *p_map++=0x33;
		  *p_map++=0x33;
		  *p_map++=0x3f;
		}
		else
		{
		  *p_map++=0x3f;
		  *p_map++=0x3f;
		  *p_map++=0x3f;
		  *p_map++=0x3f;		
		}
		*p_map=0x3f;
		
		//获取下个链表
	  if(struct_temp.snake_next!=NULL)
		{
			struct_temp = *struct_temp.snake_next;
		}
	}
}

2.刷新地图

// *****************************************************************************
// 函数名称:Update_Map
// 功能描述:更新地图-贪吃蛇
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Update_Map(void)
{
	U8 draw_i = 0;
	if(!Game_Result)
	{
		for(draw_i = 0;draw_i<SNAKE_AXIS_Y;draw_i++)
		{
		    //将地图绘制到LCD屏幕里面的
			OLED_DrawBMP(0,draw_i+2,128,draw_i+3,Game_Map[draw_i]);
		}
  }
}

3.清理地图以及链表数据

// *****************************************************************************
// 函数名称:Clear_Map
// 功能描述:根据链表清空地图
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Clear_Map(void)
{
	U8 i = 0;
	U8 *p_map;
	SNAKE_BODY struct_temp = *Sanke_Body;
	for(i = 0;i<Sanke_Len;i++)
	{
		p_map = &Game_Map[(struct_temp.Snake_Y-1)][(struct_temp.Snake_X-1)*8];
		
		//链表数据处理
		*p_map++=0x00;
		*p_map++=0x00;
		*p_map++=0x00;
		*p_map++=0x00;
		*p_map++=0x00;
		*p_map=0x00;
		
		//获取下个链表
	  if(struct_temp.snake_next!=NULL)
		{
			struct_temp = *struct_temp.snake_next;
		}
	}
	OLED_Clear_Row(2,6);
}

贪吃蛇游戏主体

话不多说了,直接上代码。

// *****************************************************************************
// 函数名称:Sanke_Init
// 功能描述:初始化-贪吃蛇
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Sanke_Init(void)
{	
	//释放空间
	free(Sanke_Body);	
	
	//申请空间
	Sanke_Body = (SNAKE_BODY *)malloc(SNAKE_STRUCT_LEN);
	Sanke_Body->snake_next = NULL;
	Sanke_Body->Snake_X = 1; 
	Sanke_Body->Snake_Y = 3;

	Sanke_Len = 1;     //初始化长度
	Direct_Flag = 4;   //初始化方向
	
	//初始化食物
	Sanke_Fool_Buf[0] = 8;
	Sanke_Fool_Buf[1] = 2;	
	
	//数据转换
	Convert_StructMap(*Sanke_Body,Sanke_Len);
	
	//画图
	Update_Map();
	
	snake_RefleshFg = 1;  //开始游戏
	Game_Result = 0;      //结果
	Game_Score = 0;       //分数
}

此函数主要是初始化一些长度以及方向,起始食物的规定等等。

当吃到食物后进行链表的添加,在链表的头部进行添加,如下:

// *****************************************************************************
// 函数名称:Snake_Add
// 功能描述:增加链表长度
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Snake_Add(U8 snake_x,U8 snake_y)
{	
    //申请内存
    SNAKE_BODY* snake_temp = (struct snake_body *)malloc(SNAKE_STRUCT_LEN);
	
    snake_temp->Snake_X = snake_x; 
	snake_temp->Snake_Y = snake_y;
	snake_temp->snake_next = Sanke_Body;  //将当前的链表赋值到刚申请的链表地址内
	
	Sanke_Body = snake_temp;
	Sanke_Len++;
}

蛇身的移动-上下左右方向的移动,注意当前方向不能向反方向移动。

// *****************************************************************************
// 函数名称:Key_Down_Move
// 功能描述:方向选择
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Key_Down_Move(U8 key_type)
{
	switch(Direct_Flag)
	{
		case 1://Up
			if(key_type!=2)Direct_Flag = key_type;
			break;
		case 2://Down
			if(key_type!=1)Direct_Flag = key_type;
			break;
		case 3://Left
			if(key_type!=4)Direct_Flag = key_type;
			break;
		case 4://Right
			if(key_type!=3)Direct_Flag = key_type;
			break;
	}
}

蛇身的偏移

// *****************************************************************************
// 函数名称:Snake_Offset
// 功能描述:蛇身偏移
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Snake_Offset(U8 direct)
{
	U8 snake_x = 0,snake_y = 0,i = 0;
	SNAKE_BODY *struct_temp = Sanke_Body;
	snake_x = Sanke_Body->Snake_X;
	snake_y = Sanke_Body->Snake_Y;
	switch(direct)
	{
		case 1:
			snake_y--;
			if(snake_y<1)snake_y = SNAKE_AXIS_Y;			
			break;
		case 2:
			snake_y++;
			if(snake_y>SNAKE_AXIS_Y)snake_y = 1;	
			break;
		case 3:
			snake_x--;
			if(snake_x<1)snake_x = SNAKE_AXIS_X;	
			break;
		case 4:
			snake_x++;
			if(snake_x>SNAKE_AXIS_X)snake_x = 1;
			break;
	}		
	//删除末端数据
	for(i = 0;i<Sanke_Len-1;i++)
	{
		 struct_temp = struct_temp->snake_next;
	}
	free(struct_temp->snake_next);
	struct_temp->snake_next = NULL;		
	Sanke_Len--; 
	
	//增加一个数据
	Snake_Add(snake_x,snake_y);  
}

检查随机数据是否在链表

// *****************************************************************************
// 函数名称:Check_Rand
// 功能描述:检查随机数据是否在链表
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
U8 Check_Rand(U8 rand_x,U8 rand_y,U8 *rand_buf)
{
	U8 i = 0,result = 0;
	SNAKE_BODY *struct_temp = Sanke_Body;
	
	if(rand_x==0)rand_x+=1;
	if(rand_y==0)rand_y+=1;
	
	for(i = 0;i<Sanke_Len;i++)
	{	
		//链表数据处理
		if(struct_temp->Snake_X==rand_x && struct_temp->Snake_Y==rand_y)
		{
			result = Check_Rand((rand()%SNAKE_RAND_X),(rand()%SNAKE_RAND_Y),rand_buf);
		}		
		//获取下个链表
	  if(struct_temp->snake_next!=NULL)
		{
			struct_temp = struct_temp->snake_next;
		}
	}
	result = 1;
	*rand_buf++=rand_x;
	*rand_buf = rand_y;
	
	return result;	
}

检查蛇身是否碰撞

// *****************************************************************************
// 函数名称:Check_Snake
// 功能描述:检查蛇身是否出现碰撞
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
U8 Check_Snake(U8 sanke_x,U8 sanke_y)
{
	SNAKE_BODY *struct_temp = Sanke_Body->snake_next;
	U8 i = 0;
	
	for(i = 0;i<Sanke_Len-1;i++)
	{
		if(sanke_x == struct_temp->Snake_X && sanke_y == struct_temp->Snake_Y)return 1;		
		if(struct_temp->snake_next!=NULL)struct_temp = struct_temp->snake_next;		
	}
	return 0;
}

吃到食物

// *****************************************************************************
// 函数名称:Snake_EatFool
// 功能描述:蛇身增加
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Snake_EatFool(U8 snake_x,U8 snake_y)
{
	switch(Direct_Flag)
	{
		case 1:
			snake_y--;
			if(snake_y<1)snake_y = SNAKE_AXIS_Y;			
			break;
		case 2:
			snake_y++;
			if(snake_y>SNAKE_AXIS_Y)snake_y = 1;	
			break;
		case 3:
			snake_x--;
			if(snake_x<1)snake_x = SNAKE_AXIS_X;	
			break;
		case 4:
			snake_x++;
			if(snake_x>SNAKE_AXIS_X)snake_x = 1;
			break;
	}
	Snake_Add(snake_x,snake_y);
}

关键部分-游戏执行

如下注释:

// *****************************************************************************
// 函数名称:Snake_RunStep
// 功能描述:贪吃蛇-蛇的移动
// 输入参数: /
// 输出参数: /
// 返回参量: /
// *****************************************************************************
void Snake_RunStep(void)
{
	static U8 time_count = 0;
		
	srand(time_count);        //初始化随机数据
	time_count++;
	if(SysParam.SysMode==MODE_GAME && time_count%Snake_Speed==0 && snake_RefleshFg == 1)
	{		
    //清理屏幕	  
		Clear_Map();		
		
		//判断蛇头是否吃到食物
		if(Sanke_Body->Snake_X==Sanke_Fool_Buf[0] && Sanke_Body->Snake_Y==Sanke_Fool_Buf[1])
		{
			//链表增加
			Snake_EatFool(Sanke_Fool_Buf[0],Sanke_Fool_Buf[1]);
						
			//产生随机数据
		  Check_Rand((rand()%SNAKE_RAND_X),(rand()%SNAKE_RAND_Y),Sanke_Fool_Buf);	

			//记分
			Game_Score++;		

			if(Game_Score>=29)//胜利-由于RAM有限(4K),只能申请到29个链表数据
			{
				Game_Stop("  GAME WINNER!!! ");
			}
		}
		else
		{
			//进行偏移
			Snake_Offset(Direct_Flag);
			
			//检查是否碰撞
			if(Check_Snake(Sanke_Body->Snake_X,Sanke_Body->Snake_Y))
			{
        Game_Stop("  GAME OVER !!!   ");
			}
		}		
	  if(!Game_Result)Convert_StructMap(*Sanke_Body,Sanke_Len);		
	}
}

以上为贪吃蛇的主要代码,希望对大家有帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值