【STM32】贪吃蛇小游戏

这是一个几个星期之前的小项目,参考修改了一下网上和正点原子的例程。
点击下载源码

功能:贪吃蛇小游戏
单片机:stm32f103c8t6




用到的硬件资源:
1.LED指示灯
2.矩阵键盘
3.OLED模块
4.通用定时器


软件设计:
1.矩阵键盘(中断方式)前面文章有介绍
2.定时器中断:用于更新游戏界面
3.贪吃蛇设计:

OLED的像素点是128*64的,为了可以显示清晰,在这里把游戏的点坐标映射为32x12(map【32】【12】),每个坐标占用16个像素点,前16行像素点用于显示分数。

#define MAXLENGTH  100    //蛇的最大长度
int map[32][12]={0};//地图大小  x,y(一个坐标为4*4个像素点)     实际按4倍尺寸放大后地图大小为128*48个像素点
int score;            //分数
bool eated=false;      //蛇吃到食物的标记
extern u8 KeyValue;        //获取按键值
struct {       
int snake_Grid[MAXLENGTH][2];      //二维数组,行坐标表示蛇节点,列表示当前节点的x,y坐标
int length;    //蛇的长度
int direction;//蛇的方向
}snake;                          //定义结构体变量snake

清除界面函数

void GUI_Clear(int map[32][12])//界面清除
{
   int i,j;
	for(i=0;i<32;i++)
	{
		for(j=0;j<12;j++)
		{
          map[i][j]=0; 
		}
	}
}

创建地图函数:填充游戏界面的边界坐标,并将边界坐标的值存入map数组
在这里插入图片描述

void Creat_map(int map[32][12])//创建地图
{
	int i,j;
	for(i=0;i<12;i++)
	{
		for(j=0;j<32;j++)
		{
  		 if(i==0||i==11)
		 {
		   map[j][i]=-2;
		 }
		 if(j==0||j==31)
		 {
		   map[j][i]=-2;
		 }
		}
	}
}

绘制地图函数:用画点函数将坐标绘出来,y坐标+16是因为前16行用来显示分数,16行以后为游戏界面
在这里插入图片描述

void Paint_Map(int x,int y)//绘制地图点坐标
{
	int i,j;
	for(i=4*y;i<4*y+4;i++)     
	{
	  for(j=4*x;j<4*x+4;j++)
	  {
		    OLED_DrawPoint(j,i+16); 
	  }
	
	}

}

绘制蛇头坐标函数:
在这里插入图片描述

void Paint_Head(int x,int y )//绘制蛇头点坐标
{
	int i,j;
	for(i=4*y;i<4*y+4;i++)     
	{
	  for(j=4*x;j<4*x+4;j++)
	  {
		if(i==4*y||i==4*y+3)
		{
			
			 OLED_DrawPoint(j,i+16);  
			
		}
		if(j==4*x||j==4*x+3)
		{
			
			 OLED_DrawPoint(j,i+16);  
		
		}
	  }
    }
}

绘制食物坐标函数:

void Paint_Food(int x,int y )//绘制食物点坐标
{
	int i,j;
	for(i=4*y;i<4*y+4;i++)     
	{
	  for(j=4*x;j<4*x+4;j++)
	  {
		
	   if(i==4*y+1||i==4*y+2)
		{
			
			 OLED_DrawPoint(j,i+16);  
			
		}
	   if(j==4*x+1||j==4*x+2)
		{
			
			 OLED_DrawPoint(j,i+16);  
		
		} 
	  }
	
	}
}

刷新界面函数:根据map数组里面的值一次性刷新游戏界面内的点坐标

void GUI_Refresh(int map[32][12])//界面刷新
{
	int i,j,temp;
	for(i=0;i<32;i++)                 
	{
		for(j=0;j<12;j++)
		{
		   temp=map[i][j];
         switch (temp)
		 {
			 case 2:		          
			   Paint_Body(i,j);  
		          break;
			 case  1:		   
		       Paint_Head(i,j);  
		          break;
			 case -2:		   
		       Paint_Map(i,j);  
		          break;
			 case-1:		   
		       Paint_Food(i,j);  
		          break;
			 case 0:		   
		       Paint_Clean(i,j);  
		    	  break;
	   }		   
		}
	}
	OLED_Refresh_Gram();
	
}

初始化蛇和地图函数:初定蛇长度为5、方向向右、蛇头坐标位于(7,5)

void Snake_Init()//蛇及地图初始化
{  
	int i;
   snake.length=5;
   snake.direction=RIGHT;
   score=0;
   snake.snake_Grid[0][0]=7;//x坐标,蛇头坐标
   snake.snake_Grid[0][1]=5;//y坐标,蛇头坐标
   for(i=1;i<snake.length;i++)
    {
        snake.snake_Grid[i][0]=snake.snake_Grid[0][0]-i;
        snake.snake_Grid[i][1]=snake.snake_Grid[0][1];  //给刚开始的蛇身几个初始坐标
    }
    Creat_map(map);
}

画蛇函数:将二维数组snake.snake_Grid里面的各个蛇坐标的值存入map数组

void drawSnake()            //画蛇
{
    int i,x,y;
	//蛇头
		x=snake.snake_Grid[0][0];
		y=snake.snake_Grid[0][1];
        map[x][y]=1; 
    //蛇身
	for(i=1;i<snake.length;i++)
    {
		x=snake.snake_Grid[i][0];
		y=snake.snake_Grid[i][1];
        map[x][y]=2;       
    }

}

按键处理函数:自定义矩形键盘值控制蛇的方向

void Get_Command()//获取键盘值
{
	u8 key;
	key=KeyValue;	
		switch(key)
		{
			case 5:if(snake.direction!=RIGHT)         //左
                   snake.direction=LEFT;			
                   break;
			case 7:if(snake.direction!=LEFT)           //右
                   snake.direction=RIGHT;			
                   break;
			case 2:if(snake.direction!=DOWN)        //上
                   snake.direction=UP;
                   break;
			case 6:if(snake.direction!=UP)         //下
                   snake.direction=DOWN;
                   break;		
		}    
}

蛇的移动函数:蛇身移动就让其坐标等于前一个坐标的值,蛇头则根据蛇的方向就行移动

void Move()//移动
{
    int i;
	map[snake.snake_Grid[snake.length-1][0]][snake.snake_Grid[snake.length-1][1]]=0;//清除尾巴
	if(eated)               //如果吃到了食物
    {
        snake.length++;
        eated=false;        //设置为false,不然无限变长
    }
    for(i=snake.length-1;i>0;i--)    //从尾巴开始,蛇身每一个点的位置等于它前面一个点的位置
    {
        snake.snake_Grid[i][0]=snake.snake_Grid[i-1][0];
        snake.snake_Grid[i][1]=snake.snake_Grid[i-1][1];
    }
    switch(snake.direction)       //根据蛇的方向处理蛇头坐标
    {
    case UP:
        snake.snake_Grid[0][1]--;
        break;
    case DOWN:
        snake.snake_Grid[0][1]++;
        break;
    case LEFT:
        snake.snake_Grid[0][0]--;
        break;
    case RIGHT:
        snake.snake_Grid[0][0]++;
        break;
    }	
}

生成食物函数:在游戏界面内空位生成食物点坐标,并将食物坐标存入map数组

int Chek(int i,int j)//检查地图空位
{
	if(map[i][j]!=0)
	{
		return 0;
	}
	return 1;   //是空位就返回1
}

void Food()//生成食物
{
	int i,j;
	do
	{
		i=(rand()%30)+1;                 //生成1~30之间的一个数
        j=(rand()%10)+1;                 //生成1~10之间的一个数
	
	}
	while(Chek(i,j)==0);                //检查该点是否为空位
	map[i][j]=-1;//画出食物
}

判断蛇吃食物:蛇吃到食物后通过eated标记状态(传递给move函数使蛇身+1)、分数+1、重新生成食物

void Eat_Food()//吃食物
{
	if(map[snake.snake_Grid[0][0]][snake.snake_Grid[0][1]]==-1) //如果蛇头碰到食物,就重新投放食物,并且把食物点重置为0
        {
            eated=true;            //标记已经吃到食物
			score+=1;
            Food();
            map[snake.snake_Grid[0][0]][snake.snake_Grid[0][1]]=0;  //去掉食物
        }
}

判断游戏是否结束:

bool GameOver()//游戏结束
{
	bool isGameOver=false;
	int sx=snake.snake_Grid[0][0],sy=snake.snake_Grid[0][1],i;//蛇头坐标
	for(i=1; i<snake.length; i++)    //判断有没有吃到自己
    {
        if(snake.snake_Grid[i][0]==sx&&snake.snake_Grid[i][1]==sy)
            isGameOver=true;
    }
	if(snake.snake_Grid[0][0]==31||snake.snake_Grid[0][0]==0|| \
		snake.snake_Grid[0][1]==11||snake.snake_Grid[0][1]==0)        //判断有没有撞墙
		isGameOver=true;
    return isGameOver;
}

显示分数:

void Show_Score()//显示分数
{
	OLED_ShowString(30,0,"Score:",16);
	OLED_ShowNum(80,0,score,2,16);

}

定时器中断:TIM4_Int_Init(4999,7199);//500ms,使用通用定时器进行游戏刷新,如果想增加控制蛇前进速度的功能可以通过修改定时器初始化函数实现。

void TIM4_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
	
	//定时器TIM4初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM4中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器


	TIM_Cmd(TIM4, ENABLE);  //使能TIMx					 
}
//定时器4中断服务程序
void TIM4_IRQHandler(void)   //TIM4中断
{
	  if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
		 TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIMx更新中断标志
		 Move();
		 Eat_Food();
		 drawSnake();
		}
}

若大家要下载源码可以留言邮箱(因为资源那里设置了0C币发现过几天会自动升价)。还有其他建议,欢迎交流沟通呀!!!

版权声明:本文为CSDN博主「Yeah__binbin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yechongbinbin/article/details/108348518

生成海报
点赞 0

Yeah__binbin

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

GD32F103基础教程—外部中断实验(八)

一、教程简介 本章主要是讲解GPIO输入实验,通过按键触发外部中断,控制LED2闪烁。 二、实验流程 1、工程配置 外部中断触发实验工程配置方法与第五章的配置方法一致,具体请查看第五章教程&#xff0c

MCU串行通讯和并行通讯的区别以及UART的理解

假如我们需要从一个MCU发送一段数据到另一个MCU,我们可以选择两种通信方式,串行通信或者并行通信。 假如我们要发送的数据是数字198转化为二进制,就是11000110,如果使用串行通信

8522A+7段数码管显示实验

1、8255A简介 8255A是Intel公司生产的可编程并行I/O接口芯片,有3个8位并行I/O口。具有3个通道3种工作方式的可编程并行接口芯片(40引脚)。 其各口功能可由软件选择,