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://Upif(key_type!=2)Direct_Flag = key_type;break;case 2://Downif(key_type!=1)Direct_Flag = key_type;break;case 3://Leftif(key_type!=4)Direct_Flag = key_type;break;case 4://Rightif(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);}}
以上为贪吃蛇的主要代码,希望对大家有帮助。
如果觉得《C语言编写贪吃蛇-链表的使用》对你有帮助,请点赞、收藏,并留下你的观点哦!