0.96寸OLED贪吃蛇算法思路分享—代码开源—简单易懂超详细
一个星期前,我把单片机带回了寝室,然后。。。就诞生了个小小的贪吃蛇小游戏程序先提前说好,在这个函数中涉及到几个函数需要替换的清手动替换OLED_Clear();//oled清屏函数OLED_Rect();//oled画空心矩形函数OLED_ShowString();//oled显示字符串函数OLED_DrawDot();//oled描点函数OLED_ShowNum();//oled显示变量值函数d
一个星期前,我把单片机带回了寝室,然后。。。就诞生了个小小的贪吃蛇小游戏程序
先提前说好,在这个函数中涉及到几个函数需要替换的清手动替换
OLED_Clear(); //oled清屏函数
OLED_Rect(); //oled画空心矩形函数
OLED_ShowString(); //oled显示字符串函数
OLED_DrawDot(); //oled描点函数
OLED_ShowNum(); //oled显示变量值函数
delay_ms(); //延时函数
那么,让我们先看一下这个.h文件
#ifndef __OLED_SNAKE_H
#define __OLED_SNAKE_H
#define Key_UP P00
#define Key_DOWN P01
#define Key_Right P02
#define Key_Left P03
#define Key_UP_number 0
#define Key_DOWN_number 1
#define Key_Right_number 2
#define Key_Left_number 3
//按键宏定义
void OLED_snake();
#endif
这个.h文件是把下面的.c文件里用到的按键和一些特殊定义的值给宏定义一下,然后整个直接在main函数里调用OLED_snake();这个函数就行了。
所以.h就完成了一些片面操作,主要还得看.c里面的算法
#include "oled.h"
#include "oled_snake.h"
#include "stdlib.h" //用随机数函数=rand();
char snake_x[100];
char snake_y[100];
char snake_food[2];
char fraction = 0; //分数
char fraction_temporary = 0; //分数临时值
void Game_Over();
void OLED_snake()
{
char x = 20,y = 31; //蛇初始位置
char i = 0; //蛇增量
char Key_number = Key_Right_number; //默认向右
char a = 1;
OLED_Clear(); //清除上一局的残骸
snake_x[0] = x;
snake_y[0] = y;
snake_food[0] = 60;
snake_food[1] = 31;
OLED_Rect(0,0,95,63); //设置贪吃蛇内部范围
OLED_ShowString(96,0,"GET:",0); //显示分数文字
//这个设置大概是,蛇在(1,1)到(94,62)[不包括线]内有效-----------------------------------------------
OLED_DrawDot(snake_food[0],snake_food[1],1); //先默认生成出来一个食物
while(1)
{
OLED_ShowNum(100,1,fraction,3,16); //显示分数文字
//把分数值显示出来
if(Key_number == Key_UP_number | Key_number == Key_DOWN_number) //检测当前的蛇是不是向上或者向下
{
if(Key_Right == 1)
{
Key_number = Key_Right_number; //向右
}else if(Key_Left == 1)
{
Key_number = Key_Left_number; //向左
}
}
if(Key_number == Key_Right_number | Key_number == Key_Left_number) //检测当前的蛇是不是向左或者向右
{
if(Key_UP == 1)
{
Key_number = Key_UP_number;
}else if(Key_DOWN == 1)
{
Key_number = Key_DOWN_number;
}
}
//这里是通过判断蛇当前走向进行转向的控制,毕竟没有哪个贪吃蛇是可以回头的吧--------------------------
OLED_DrawDot(snake_x[fraction],snake_y[fraction],0); //把尾巴消除掉
if(Key_UP != 1 | Key_DOWN != 1 | Key_Right != 1 | Key_Left != 1)
{
switch(Key_number)
{
case Key_Right_number:
x = x + 1;
y = y;
break;
case Key_Left_number:
x = x - 1;
y = y;
break;
case Key_UP_number:
x = x;
y = y + 1;
break;
case Key_DOWN_number:
x = x;
y = y - 1;
break;
}
}
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>0;fraction_temporary--) //通过分数决定循环次数
{
snake_x[fraction_temporary] = snake_x[fraction_temporary - 1];
snake_y[fraction_temporary] = snake_y[fraction_temporary - 1];
}
snake_x[0] = x;
snake_y[0] = y; //计算好的坐标存入数组头
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>=0;fraction_temporary--) //
{
OLED_DrawDot(snake_x[fraction_temporary],snake_y[fraction_temporary],1);
}
//把蛇的身体完整的显示出来-------------------------------------------------------------
if(snake_x[0] == snake_food[0] & snake_y[0] == snake_food[1])
{
fraction++;
while(a)
{
snake_food[0] = rand();
snake_food[1] = rand();
if(snake_food[0]>=92 | snake_food[1]>=61 | snake_food[1]<=3 | snake_food[0]<=3)
{
a++;
}
a--;
}
a = 1;
}
OLED_DrawDot(snake_food[0],snake_food[1],1);
//判断蛇是不是吃到食物了,吃到了就生成下一个food----------------------------------------
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>0;fraction_temporary--)
{
if(snake_x[0] == snake_x[fraction_temporary] & snake_y[0] == snake_y[fraction_temporary])
{
Game_Over();
}
}
//判断蛇头是不是跟蛇身体重叠了----------------------------------------------------------
i++;
if(fraction >= 50)
{
}else{
delay_ms(50 - fraction);//算是速度吧,分越高速度越快
}
if(x>=94|x<=1|y>=62|y<=1)
{
Game_Over();
}
}
}
void Game_Over()
{
OLED_ShowString(12,2,"GAME OVER",0);
fraction_temporary = fraction;
while(1)
{
//delay_ms(500);
if(Key_UP == 1 | Key_DOWN == 1 | Key_Right == 1 | Key_Left == 1)
{
while(Key_UP|Key_DOWN|Key_Right|Key_Left);
for(fraction_temporary;fraction_temporary>=0;fraction_temporary--) //
{
OLED_DrawDot(snake_x[fraction_temporary],snake_y[fraction_temporary],0);
}
OLED_DrawDot(snake_food[0],snake_food[1],0);
fraction = 0;
OLED_Clear();
OLED_snake();
}
}
}
嚯嚯,看起来可真长,实则不然,都是一个一个区域的功能叠加出来的功能罢了;看到昂,这个.c里一共有两个函数名,分别是Game_Over();OLED_Snake();,不用看,前面的gameover是给后面OledSnake服务的,也就是显示"GameOver"的功能,
那,让我们把OLED_Snake();这个大函数拆开来看看吧
一、基础参数定义部分
char snake_x[100];
char snake_y[100];
char snake_food[2];
char fraction = 0; //分数
char fraction_temporary = 0; //分数临时值
在整个大函数开始前,我是定义了三个数组和两个cahr变量,三个数组的前两个snake_x[100];snake_y[100];是保存整个蛇的身体的数组,这个数组内部的赋值是根据下面的fraction分数值来决定的,就好比分数是0,那么蛇的身体只有1,那么保存蛇身体位置坐标信息只保存一次即可;在如果我分数是10,那么蛇的身体就是11,那这个数组就会保存11组蛇的身体的坐标。fraction_temporary也是储存了分数值,只是后面经常要对fraction分数值进行操作,所以就衍生出来一个"分身"来代替原来的分数进行操作。
讲解完定义部分之后,我们就进入void OLED_snake()内部看看(其实吧整个函数看一遍之后会发现,我已经大概写好了一部分到才一部分的范围都是从哪儿到哪儿了)
二、对蛇以及食物各方面的初始化
char x = 20,y = 31; //蛇初始位置
char i = 0; //蛇增量
char Key_number = Key_Right_number; //默认向右
char a = 1;
OLED_Clear(); //清除上一局的残骸
snake_x[0] = x;
snake_y[0] = y;
snake_food[0] = 60;
snake_food[1] = 31;
OLED_Rect(0,0,95,63); //设置贪吃蛇内部范围
OLED_ShowString(96,0,"GET:",0); //显示分数文字
//这个设置大概是,蛇在(1,1)到(94,62)[不包括线]内有效-----------------------------------------------
OLED_DrawDot(snake_food[0],snake_food[1],1); //先默认生成出来一个食物
这一段开头先是定义了蛇头的坐标x,y,并赋值初始值是在(20,31)的位置;
紧接着定义了一个键值Key_number来保存现在蛇应该往哪个方向去,还记得之前.h文件里那一堆#define宏定义吗,在这里就派上用场了,把数字宏定义成文字,是大大增加了程序的可读性;
至于这个a,是后面生成蛇食物的时候用到的一个参数,到那个地方的时候在说;
在之后就把刚才初始化的蛇头位置赋值给了蛇身体数组的第"0"个位置里;
之后就把蛇的食物的初始位置(60,31)也赋值给了食物数组里,因为食物就一个,一个也就一个坐标也就是两个数固定了,所以用1个数组就够了;
OLED_Rect(0,0,95,63);这个函数就是画了个矩形,让玩家能看到边界;
OLED_ShowString(96,0,"GET:",0); 在矩形范围的右边显示分数信息;
OLED_DrawDot(snake_food[0],snake_food[1],1);把刚刚初始的食物显示出来
至此,初始化的内容就完成了,接下来就是进入整个函数的核心while循环里了,整个程序运行也就是在这个while循环里实现的
三、按键检测控制蛇头方向
if(Key_number == Key_UP_number | Key_number == Key_DOWN_number) //检测当前的蛇是不是向上或者向下
{
if(Key_Right == 1)
{
Key_number = Key_Right_number; //向右
}else if(Key_Left == 1)
{
Key_number = Key_Left_number; //向左
}
}
if(Key_number == Key_Right_number | Key_number == Key_Left_number) //检测当前的蛇是不是向左或者向右
{
if(Key_UP == 1)
{
Key_number = Key_UP_number;
}else if(Key_DOWN == 1)
{
Key_number = Key_DOWN_number;
}
}
//这里是通过判断蛇当前走向进行转向的控制,毕竟没有哪个贪吃蛇是可以回头的吧--------------------------
这里通过两个if来控制蛇头变化的方向,我们常见的蛇的转向就是上转右,右转下之类的,不管你们玩没玩过,反正我是没玩过直接掉头的贪吃蛇;这个区域就是通过判断蛇头方向来控制检测的按键:假如蛇头现在方向向上,那么只有向左向右的按键可以使用。
下面就是蛇移动和显示的关键部分之一了!
四、蛇头,蛇身体坐标位置计算和储存
OLED_DrawDot(snake_x[fraction],snake_y[fraction],0); //把尾巴消除掉
if(Key_UP != 1 | Key_DOWN != 1 | Key_Right != 1 | Key_Left != 1)
{
switch(Key_number)
{
case Key_Right_number:
x = x + 1;
y = y;
break;
case Key_Left_number:
x = x - 1;
y = y;
break;
case Key_UP_number:
x = x;
y = y + 1;
break;
case Key_DOWN_number:
x = x;
y = y - 1;
break;
}
}
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>0;fraction_temporary--) //通过分数决定循环次数
{
snake_x[fraction_temporary] = snake_x[fraction_temporary - 1];
snake_y[fraction_temporary] = snake_y[fraction_temporary - 1];
}
snake_x[0] = x;
snake_y[0] = y; //计算好的坐标存入数组头
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>=0;fraction_temporary--) //
{
OLED_DrawDot(snake_x[fraction_temporary],snake_y[fraction_temporary],1);
}
//把蛇的身体完整的显示出来-------------------------------------------------------------
从开头的一个擦除就能看出来,整个蛇的身体应该是先擦在写的显示方法
那么开头为什么说是先擦除蛇尾呢?OLED_DrawDot(snake_x[fraction],snake_y[fraction],0);这个函数可以看到,它是读取蛇身体坐标数组里第"fraction"个数来进行擦除行为,这时候进行模拟一下,此时分数是0,那么之前固定写进第0位的数在这里会因为分数"fraction"为0而擦除,当然,如果分数为1,那擦的就是第1个数。
这个地方解释起来很迷,是因为这个地方和下面的地方连接的太紧密了,容我接着解释
紧跟着尾巴清除的就是一个通过识别Key_number的值得一个switch来控制蛇头坐标的变化的地方,四个方向对应着四种坐标变化方式,在下次有效按键按下之前,坐标都会以固定方法变化。
Ok到目前为止还能解释的清楚,下面的两个for说实话我认为还挺复杂的,我们在拆开来说
接着,fraction_temporary = fraction;分数的分身继承了分数的值,来进行下面for的操作
for(fraction_temporary;fraction_temporary>0;fraction_temporary--) //通过分数决定循环次数
{
snake_x[fraction_temporary] = snake_x[fraction_temporary - 1];
snake_y[fraction_temporary] = snake_y[fraction_temporary - 1];
}
这个for,把坐标数组里的值整体向后移动了一位,这样就能把之前"蛇头"的坐标转变成"蛇身";加入分数为0,那么这个for就不会运行。如果分数为10,那么这个for就会运行10次,数组里面的数也会向后移动10位,加上蛇头,一共就是11个数。
在这个for下面snake_x[0] = x; snake_y[0] = y;是把刚刚蛇头计算好的坐标重新赋值给数组0位里,成为了新的"蛇头"
哎嘿,光把这些坐标信息保存起来没用啊,得让它显示出来啊,于是就有了下面一个for
同样的,fraction_temporary = fraction;分数的分身继承了分数的值,来进行下面for的操作
for(fraction_temporary;fraction_temporary>=0;fraction_temporary--) //
{
OLED_DrawDot(snake_x[fraction_temporary],snake_y[fraction_temporary],1);
}
这个for和上面的for有一个不同就是,这个for的判断变成了>=,这样就算分数值是0也会运行一次用来显示蛇。
至此,整条蛇的显示是可以根据分数来显示了,单贪吃蛇贪吃蛇,不吃怎么行呢
五、蛇吃食物的判定和新食物的生成
if(snake_x[0] == snake_food[0] & snake_y[0] == snake_food[1])
{
fraction++;
while(a)
{
snake_food[0] = rand();
snake_food[1] = rand();
if(snake_food[0]>=92 | snake_food[1]>=61 | snake_food[1]<=3 | snake_food[0]<=3)
{
a++;
}
a--;
}
a = 1;
}
OLED_DrawDot(snake_food[0],snake_food[1],1);
//判断蛇是不是吃到食物了,吃到了就生成下一个food----------------------------------------
哦哦!是a!之前定义的a在这里出现了!
开头一个if先判定蛇头坐标是否和食物坐标重合,如果重合就执行下面的食物随机生成
其实在我看来这段代码很傻,秉承着随机的态度,我使用了stdlib.h里的rand();函数,但通过翻阅相关文档发现,rand();这个函数生成的随机数非常非常大,范围为1-32767,所以在while里我放了个a,a的初始值是1,那么while(a)就会循环,在随机完后就a--变成了0,理论就不会循环了,但我又加了个判断,判断这个食物的坐标在不在判定的矩形范围内,如果不在就a++给它加回去,可以说是很弱智了。。。在while满足循环完后,立马重置a=1作为下一次循环,毕竟食物不止一个嘛。
OLED_DrawDot(snake_food[0],snake_food[1],1);把刚刚随机出来的食物坐标显示出来
光吃食物不行啊,如果能一直吃的话玩得好到最后整个屏幕都是蛇就不行了,贪吃蛇的经典玩法里有个蛇吃到身体就会死的设定,所以这个也不能少啦
六、蛇吃到自己的判定
fraction_temporary = fraction;
for(fraction_temporary;fraction_temporary>0;fraction_temporary--)
{
if(snake_x[0] == snake_x[fraction_temporary] & snake_y[0] == snake_y[fraction_temporary])
{
Game_Over();
}
}
//判断蛇头是不是跟蛇身体重叠了----------------------------------------------------------
经典的分数的分身继承分数的值,这个for的思路就是对比蛇头坐标和蛇身体坐标是否吻合,吻合就会判定为吃到了身子,游戏就会结束。这里的for用的判断方法是>是为了防止出现蛇头坐标和蛇头坐标对比的情况,那这样游戏还没开始就会结束了。
七、最后的收尾(刷新速度和出界判定)
i++;
if(fraction >= 50)
{
}else{
delay_ms(50 - fraction);//算是速度吧,分越高速度越快
}
if(x>=94|x<=1|y>=62|y<=1)
{
Game_Over();
}
i的值是早期调试的时候用的变量,在程序几乎完善的时候基本就没用到,所以我们忽略这个i++(其实是我忘了删了);delay_ms(50 - fraction);这个延时会随着分数的增加越来越短,当分数到0的时候,延迟也变成了0,但是程序运行本身就是有延迟的,所以这个时候蛇移动的会快一些,但还不至于到瞬移的程度,但分数超过50之后延时负值程序会崩溃的,所以加了个判断分数是不是大于50来跳过那个函数防止程序崩溃;
在之后就是判断蛇头是不是跟边框框重合了,重合即为撞墙,就会判断为游戏结束
至此,整个贪吃蛇的核心就已经讲完了,还有一个函数
八、Game_Over函数
void Game_Over()
{
OLED_ShowString(12,2,"GAME OVER",0);
fraction_temporary = fraction;
while(1)
{
//delay_ms(500);
if(Key_UP == 1 | Key_DOWN == 1 | Key_Right == 1 | Key_Left == 1)
{
while(Key_UP|Key_DOWN|Key_Right|Key_Left);
for(fraction_temporary;fraction_temporary>=0;fraction_temporary--) //
{
OLED_DrawDot(snake_x[fraction_temporary],snake_y[fraction_temporary],0);
}
OLED_DrawDot(snake_food[0],snake_food[1],0);
fraction = 0;
OLED_Clear();
OLED_snake();
}
}
}
只要一调用这个函数,就会在矩形中间显示"GAME OVER"的字样,此时重制所有的值并检测按键,通过调用核心函数OLED_snake();来开始下一局游戏。
我在上周六的时候写完了这个小游戏,从想法诞生到完成实践也就用了将近三个小时的时间,还是挺简单的
最后经典运行一下看看结果,因为没法发视频,就发个截图算了
我定义蛇身体数组的时候只定义100,所以理论蛇的身体最大也就100长度,而char变量最大可以到255,嘛,反正我最大就这样了。
这个算法好处就是,只要对应屏幕有描点,画矩形函数,这套就能移植到任何屏幕上去(没试过android),算法并没有进行优化,有什么不好的地方还请指出
源码公开,仅供学习
更多推荐
所有评论(0)