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);
}
}
以上为贪吃蛇的主要代码,希望对大家有帮助。