基于51单片机和OLED屏幕的贪吃蛇游戏
本文章在于利用51单片机和OLED屏幕实现一个简易的贪吃蛇游戏
所用的51单片机为普中51系列,OLED屏幕属中景园电子,具体实物均可以在某宝购买
OLED模块:
关于OLED模块的相关函数及其.h和.c文件均可以使用由中景园电子商家提供的历程中的代码,经验证都是可靠的
在这里简单提一些OLED屏幕的特性:
一、OLED屏幕是由128*64的像素点组成的,长128列,而宽64的像素点又被分为了八个page,在单片机向其传输数据时,是一次性传输八个位的数据,也就是一个page中的一列,所以我干脆将8*8总共64个像素点当作贪吃蛇的一节蛇身
二、相信使用过OLED屏幕的人都会发现另一个一个特性,就是当你点亮某一块区域,如果你不对这一区域进行清屏再显示其他图像时,那麽之前被点亮的部分在没有被新的数据覆盖的情况下将会继续显示,具体现象可以自行验证
三、中景园商家提供的库函数里面,有两个函数,一个是OLED_Set_Pos(x,y),还有一个是OLED—_WR_Byte()函数,这两个函数一个用于定位OLED屏幕中的位置,其中x是列数,而y是page数,在定位之后,再利用第二个函数向定位处传输图像数据,而且OLED屏幕在传输完一列之后会自动向下一列传输数据,非常方便。
代码部分:
大概阐述一下代码思想,我们使用的是8*8的一块区域作为一节蛇身,所以整个屏幕被我们划分成长16*8,我们将在这个区域内显示我们的蛇身,由于显示区域较小,可以考虑无墙,也可以有墙,完全看个人喜好,最主要的是蛇身的移动以及加长。
首先,定义一个可以表示蛇身坐标的结构体,包含横纵坐标即可,然后定义一个结构体数组以及一个变量用于存储所有蛇身以及食物的位置
struct snake //结构体,用于储存蛇身的位置;
{unsigned char x;unsigned char y;
} ;
struct snake Pos[20]; //蛇的身体坐标数据;
struct snake food ;//食物的坐标位置;
由于在单片机中,我们能用到的存储空间非常有限,所以定义的最大长度就是20,在接下来的蛇身加长以及移动模块中还将提及有关51内存的问题。在解决了蛇身位置存储问题之后,就要把蛇身打印在屏幕上
蛇身打印函数:
在OLED模块中已经提及了两个比较实用的函数,我们就可以通过这两个函数来实现蛇身打印
const unsigned char code body[8]={0xFF,0xFF,0xC3,0xC3,0xC3,0xC3,0xFF,0xFF};void OLED_SnakeBody(unsigned char x, unsigned char y)
{u8 i;OLED_Set_Pos(x*8,y);for(i=0;i<8;i++){OLED_WR_Byte(body[i],OLED_DATA);}}
(清除蛇身的函数和打印蛇身函数类似,内部实现无非就是把body[i]换成了0x00而已)
其中body[8]是用来打印蛇身时传输的数据,显示的效果如下图所示:
食物生成函数:
对于我个人而言,食物生成函数是贪吃蛇游戏中比较难的一个部分,因为食物的随机生成意味着要产生一个随机数,但是怎么样的数是随机的呢?在借鉴了部分大佬的代码之后,我决定利用定时器来生成两个随机数,作为食物的横纵坐标位置。(在51单片机中,大可以把THX和TLX看作两个常数,只不过这两个常数是在随着时间做自加和清零动作罢了)具体代码如下:
void Food_Init() //食物初始化函数;
{u8 i;while(!foodflag){food.x=count%16;food.y=count%8;for(i=0;i<head;i++){if((food.x==Pos[i].x&&food.y==Pos[i].y)||food.x>=15||food.y>=7||food.x<=1||food.y<=1)//一旦满足这个条件,这个食物//数据就应该被舍弃,因为在蛇身或者在地图上;giveup=1;}if(giveup==1) {foodflag=0;giveup=0;}else{foodflag=1;OLED_SnakeBody(food.x,food.y);giveup=0;break;}}
}
其中有一个标志位:foodflag,这个标志位的作用在于标记食物状态,当食物被吃掉,即foodflag为0的时候,就将进入循环一直对count进行运算,直到取出一个合适的食物坐标为止(其中的count被我放在定时器中做自加运算并且按时清零)。但是,这样的食物生成方式会有一个缺陷,就是当你吃掉食物之后,可能会出现短暂的延时之后才会生成食物,所以可以考虑其他方式来生成随机食物,总之,仁者见仁,智者见智。
独立按键改变蛇的移动方向:
贪吃蛇移动过程中,需要不断改变方向,而51单片机有四个独立按键,虽然位置对于游戏来说不太友好,但是可以减小代码复杂度,所以推荐使用独立按键而非矩阵键盘。在此,我引入了一个变量mode来表示蛇的运动方向,1左2右3上4下,,并且通过独立按键来改变mode的值,在改变方向的时候要注意当蛇在向左运动的时候,不能直接向右,也不能再向左,只能上或者下,其他情况亦如是。
void leftkey(){if(left==0&&mode!=1&&mode!=2)delay(25);if(left==0&&mode!=1&&mode!=2)mode=1;while(!left);}void rightkey(){if(right==0&&mode!=1&&mode!=2)delay(25);if(right==0&&mode!=1&&mode!=2)mode=2;while(!right);}void upkey(){if(up==0&&mode!=3&&mode!=4)delay(25);if(up==0&&mode!=3&&mode!=4)mode=3;while(!up);}void downkey(){if(down==0&&mode!=3&&mode!=4)delay(25);if(down==0&&mode!=3&&mode!=4)mode=4;while(!down) ;}void keypros()
{leftkey();rightkey();upkey();downkey();
}
接下来就是整个程序中比较核心的部分,当然,也是个人认为比较简单的部分:
贪吃蛇移动函数:
在上面的部分中我已经提及了一个OLED屏幕的特性,就是未被清除的部分仍将继续显示,也就是说,在每一次蛇移动的时候,我们仅仅需要清除蛇尾,更新蛇头即可,而中间的身体节点可以保持不变。那吃到食物之后该怎么办呢,更好办,连蛇尾都可以不用去掉,只需更新头部即可。但是,在每一次更新位置的时候,要注意,将数组里的每一节蛇身坐标进行改变(要注意数据移动方向),在吃到食物之后,还需要将蛇身加长,即length++。
具体代码示下,仅提供左移状态函数,其他类似:
void modeleft()
{u8 i,j;if(mode==1){//首先判断是否撞墙;if(Pos[head-1].x==0)page=2;//再判断是否咬到自己的身体;if(page==1)for(i=0;i<head-1;i++) //这里要注意,千万不要把头部坐标也挤上去,省一点时间;{if((Pos[head-1].x-1)==Pos[i].x&&Pos[head-1].y==Pos[i].y)page=2;}//如果既没有撞墙也没有咬到自己 ,那就考虑吃到食物和没有吃到的情况;if(page==1){//吃到食物的情况;if((Pos[head-1].x-1)==food.x&&Pos[head-1].y==food.y){head++;//长度加一;foodflag=0;//刷新下一次食物;Pos[head-1].x=Pos[head-2].x-1; //改变头部之后,把之前头部的位置运算之后进行赋值;Pos[head-1].y=Pos[head-2].y;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新的狗头,就完事了;}else //没吃到食物的情况‘{OLED_CLR_Body(Pos[tail].x,Pos[tail].y);//砍断旧尾巴;for(j=0;j<head-1;j++){Pos[j].x=Pos[j+1].x;Pos[j].y=Pos[j+1].y;}Pos[head-1].x--;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新狗头;不能先点亮狗头,不然会造成数据丢失;}}}
}
至此,所有重要的函数部分都已经结束。
剩下的就是蛇身初始化函数,游戏结束函数,重新开始函数等等,但要注意的是,如果有重新开始函数,不仅仅要对蛇身重新初始化,还要对蛇身长度等重要变量和标志位进行重置,不然就等着修bug吧。
当然,本程序存在许多较繁琐的步骤和函数,也是纯属由于博主水平有限,如若文章存在错别字啥的……那就骂我好啦(语文功底是在不行)。
为了方便大家参考,就把所有代码全部放出吧:
#include"reg51.h"
#include"oled.h"
sbit left=P3^1;
sbit right=P3^0;
sbit up=P3^2;
sbit down=P3^3;//按键定义;bit moveflag=0,foodflag=0,giveup=0,start=0,fun=0; //giveup是食物被舍弃标志位,foodflag是食物刷新标志位,moveflag是移动标志位;
u8 yanshi=60,count=0,page=0,mode=0,time=150,point=1;//page用于显示不同的页面,比如游戏结束页面,开始页面等;mode用于识别蛇的运动状态;
//time分两档,一是150,二是90;struct snake //结构体,用于储存蛇身的位置;
{unsigned char x;unsigned char y;
} ;
u8 head=3,tail=0;//蛇身长度;
struct snake Pos[20]; //蛇的身体坐标数据;
struct snake food ;//食物的坐标位置;
void TimerInit()
{TMOD=0x01;TH0=0xec;TL0=0x77; //10毫秒计时; //还是要改定时初值;选用4毫秒;ET0=1;EA=1;TR0=0; //只有在page2的时候才打开;}void delay(u8 i)
{while(i--);
}
void ShowTen()
{OLED_ShowChar(0,3,'T');
}void Game_Restart()
{if(left==0)delay(25);if(left==0){page=0;OLED_Clear();}while(!left);
}void Snake_Over()//蛇死亡函数,在死亡之后显示game over,同时不断检测复原键是否被按下;
{if(page==2){mode=0;//必须清零,否则就会在下一次游戏开始时造成蛇的乱移动;TR0=0;//关闭定时器;OLED_Clear();//先清屏;while(page==2){OLED_ShowString(0,0,"game over");OLED_ShowString(30,3,"press left to restart");Game_Restart();}}
}void Food_Init() //食物初始化函数;
{u8 i;while(!foodflag){food.x=count%16;food.y=count%8;for(i=0;i<head;i++){if((food.x==Pos[i].x&&food.y==Pos[i].y)||food.x>=15||food.y>=7||food.x<=1||food.y<=1)//一旦满足这个条件,这个食物//数据就应该被舍弃,因为在蛇身或者在地图上;giveup=1;}if(giveup==1) {foodflag=0;giveup=0;}else{foodflag=1;OLED_SnakeBody(food.x,food.y);giveup=0;break;}}
}void Snake_Init() //蛇初始化函数;
{
u8 i;Pos[0].x=6;Pos[0].y=3;Pos[1].x=7;Pos[1].y=3;Pos[2].x=8;Pos[2].y=3;for(i=0;i<head;i++){OLED_SnakeBody(Pos[i].x,Pos[i].y);}
}void leftkey(){if(left==0&&mode!=1&&mode!=2)delay(25);if(left==0&&mode!=1&&mode!=2)mode=1;while(!left);}void rightkey(){if(right==0&&mode!=1&&mode!=2)delay(25);if(right==0&&mode!=1&&mode!=2)mode=2;while(!right);}void upkey(){if(up==0&&mode!=3&&mode!=4)delay(25);if(up==0&&mode!=3&&mode!=4)mode=3;while(!up);}void downkey(){if(down==0&&mode!=3&&mode!=4)delay(25);if(down==0&&mode!=3&&mode!=4)mode=4;while(!down) ;}void keypros()
{leftkey();rightkey();upkey();downkey();
}void modeleft()
{u8 i,j;if(mode==1){//首先判断是否撞墙;if(Pos[head-1].x==0)page=2;//再判断是否咬到自己的身体;if(page==1)for(i=0;i<head-1;i++) //这里要注意,千万不要把头部坐标也挤上去,省一点时间;{if((Pos[head-1].x-1)==Pos[i].x&&Pos[head-1].y==Pos[i].y)page=2;}//如果既没有撞墙也没有咬到自己 ,那就考虑吃到食物和没有吃到的情况;if(page==1){//吃到食物的情况;if((Pos[head-1].x-1)==food.x&&Pos[head-1].y==food.y){head++;//长度加一;foodflag=0;//刷新下一次食物;Pos[head-1].x=Pos[head-2].x-1; //改变头部之后,把之前头部的位置运算之后进行赋值;Pos[head-1].y=Pos[head-2].y;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新的狗头,就完事了;}else //没吃到食物的情况‘{OLED_CLR_Body(Pos[tail].x,Pos[tail].y);//砍断旧尾巴;for(j=0;j<head-1;j++){Pos[j].x=Pos[j+1].x;Pos[j].y=Pos[j+1].y;}Pos[head-1].x--;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新狗头;不能先点亮狗头,不然会造成数据丢失;}}}
}
void moderight()
{u8 i,j;if(mode==2){//首先判断是否撞墙;if(Pos[head-1].x==15)page=2;//再判断是否咬到自己的身体;if(page==1)for(i=0;i<head-1;i++) //这里要注意,千万不要把头部坐标也挤上去,省一点时间;{if((Pos[head-1].x+1)==Pos[i].x&&Pos[head-1].y==Pos[i].y)page=2;}//如果既没有撞墙也没有咬到自己 ,那就考虑吃到食物和没有吃到的情况;if(page==1){//吃到食物的情况;if((Pos[head-1].x+1)==food.x&&Pos[head-1].y==food.y){head++;//长度加一;foodflag=0;//刷新下一次食物;Pos[head-1].x=Pos[head-2].x+1; //改变头部之后,把之前头部的位置运算之后进行赋值;Pos[head-1].y=Pos[head-2].y;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新的狗头,就完事了;}else //没吃到食物的情况‘{OLED_CLR_Body(Pos[tail].x,Pos[tail].y);//砍断旧尾巴;for(j=0;j<head-1;j++){Pos[j].x=Pos[j+1].x;Pos[j].y=Pos[j+1].y;}Pos[head-1].x++;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新狗头;不能先点亮狗头,不然会造成数据丢失;}}}
}
void modeup()
{u8 i,j;if(mode==3){//首先判断是否撞墙;if(Pos[head-1].y==0)page=2;//再判断是否咬到自己的身体;if(page==1)for(i=0;i<head-1;i++) //这里要注意,千万不要把头部坐标也挤上去,省一点时间;{if((Pos[head-1].x)==Pos[i].x&&Pos[head-1].y-1==Pos[i].y)page=2;}//如果既没有撞墙也没有咬到自己 ,那就考虑吃到食物和没有吃到的情况;if(page==1){//吃到食物的情况;if(Pos[head-1].x==food.x&&(Pos[head-1].y-1)==food.y){head++;//长度加一;foodflag=0;//刷新下一次食物;Pos[head-1].x=Pos[head-2].x; //改变头部之后,把之前头部的位置运算之后赋值给新头部;Pos[head-1].y=Pos[head-2].y-1;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新的狗头,就完事了;}else //没吃到食物的情况‘{OLED_CLR_Body(Pos[tail].x,Pos[tail].y);//砍断旧尾巴;for(j=0;j<head-1;j++){Pos[j].x=Pos[j+1].x;Pos[j].y=Pos[j+1].y;}Pos[head-1].y--;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新狗头;不能先点亮狗头,不然会造成数据丢失;}}}
}
void modedown()
{u8 i,j;if(mode==4){//首先判断是否撞墙;if(Pos[head-1].y==7)page=2;//再判断是否咬到自己的身体;if(page==1)for(i=0;i<head-1;i++) //这里要注意,千万不要把头部坐标也挤上去,省一点时间;{if((Pos[head-1].x)==Pos[i].x&&Pos[head-1].y+1==Pos[i].y)page=2;}//如果既没有撞墙也没有咬到自己 ,那就考虑吃到食物和没有吃到的情况;if(page==1){//吃到食物的情况;if(Pos[head-1].x==food.x&&(Pos[head-1].y+1)==food.y){head++;//长度加一;foodflag=0;//刷新下一次食物;Pos[head-1].x=Pos[head-2].x; //改变头部之后,把之前头部的位置运算之后赋值给新头部;Pos[head-1].y=Pos[head-2].y+1;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新的狗头,就完事了;}else //没吃到食物的情况‘{OLED_CLR_Body(Pos[tail].x,Pos[tail].y);//砍断旧尾巴;for(j=0;j<head-1;j++){Pos[j].x=Pos[j+1].x;Pos[j].y=Pos[j+1].y;}Pos[head-1].y++;OLED_SnakeBody(Pos[head-1].x,Pos[head-1].y);//点亮新狗头;不能先点亮狗头,不然会造成数据丢失;}}}
}
void choice()
{if(left==0){delay(20);if(left==0){point++;if(point>3)point=1;} while(!left);}if(right==0){delay(20);if(right==0){if(point==1){time=90;page=1;start=1;foodflag=0;}if(point==2){time=150;page=1;//OLED_ShowString(0,0,"150");start=1;foodflag=0;}if(point==3)fun=1;}while(!right);}
} void modepros()
{modeup();modeleft();modedown();moderight();
}
void page0()
{OLED_ShowString(10,0,"greedy snake");OLED_ShowString(20,2,"difficult");OLED_ShowString(20,4,"normal");OLED_ShowString(20,6,"easy") ;
if(point==1)
{OLED_ShowChar(10,2,'>');OLED_ShowChar(10,4,' ');OLED_ShowChar(10,6,' ');
}
if(point==2)
{OLED_ShowChar(10,2,' ');OLED_ShowChar(10,4,'>');OLED_ShowChar(10,6,' ');
}
if(point==3)
{OLED_ShowChar(10,3,' ');OLED_ShowChar(10,4,' ');OLED_ShowChar(10,6,'>');
}
if(fun==1)
{OLED_Clear();OLED_ShowString(0,4,"you really have face");delay(100000);OLED_Clear();fun=0;
}
choice();
} void main()
{
TimerInit();
OLED_Init();
OLED_Clear();
ShowTen();
while(yanshi)
{delay(30000);yanshi--;
}
OLED_Clear();while(1){while(page==0){page0();} while(page==1){if(start==1){OLED_Clear();TR0=1;head=3;Snake_Init();start=0;} Food_Init();keypros();}while(page==2){TR0=0;Snake_Over();}}
}void Timer0() interrupt 1
{TH0=0xec;TL0=0x77; //4mscount++;//OLED_ShowNum(0,0,count,3,16);if(count==time){count=0;modepros();}
}
由于后期添加了其他功能。部分函数可以直接忽略,只参考有价值的代码段即可。
博主水平有限,尚在学习充能中,如存在部分错误,纰漏,表意不全等缺点,烦请原谅和指教。
谢谢大家支持!!!
基于51单片机和OLED屏幕的贪吃蛇游戏相关推荐
- 51单片机 IIC OLED屏幕驱动+Proteus仿真+实物验证示例程序
51单片机 IIC OLED屏幕驱动+Proteus仿真+实物验证示例程序 Proteus仿真效果 注意点击运行仿真后,图像刷新出来比较慢. 示例主程序 #include "REG51.h& ...
- 基于51单片机的OLED驱动方式(iic通讯方式)
基于51单片机的OLED驱动方式(iic通讯方式) 前言: 本人从事硬件开发,自学软件,因为发现在学习过程中,有很多问题对于没有项目实战经验的新手来讲太难解决了,可以说基本上是无从下手.现将自己学习过 ...
- 用STM32F103使用OLED屏实现贪吃蛇游戏
偶然一天突发奇想想在MCU上实现一个小游戏,综合现有硬件开发资源和开发能力,想来想去贪吃蛇最为合适. 有之前朋友留给我的一块四轴飞行器遥控器的电路板,上面有摇杆按键和OLED瓶.STM32F103R8 ...
- 基于WIN32 API界面编程实现的贪吃蛇游戏
1 设计目的和任务 本次期末大作业采用课程设计的形式进行,作为<Windows编程>课程的期末考核.要求综合运用Windows编程的相关知识,完成大作业的相关内容,并撰写设计报告.其目的和 ...
- 基于tkinter的界面应用小程序-贪吃蛇游戏
说明:利用tkinter结合面向对象思想进行一个贪吃蛇小游戏开发. # coding = gbk # 学习方法:有不懂的函数可以按住CTRL后单击它,进入相应的源文件,一般都有注释 # 移动方法:不断 ...
- Arduino + 74HC595实现24x24点阵贪吃蛇游戏
Arduino + 74HC595实现24x24点阵贪吃蛇游戏 简介 硬件 开发环境 接线图 软件部分 方向控制 蛇身轨迹 碰壁检测 蛇身显示 刷新食物 仿真演示 源码和原理图下载 简介 本文是基于A ...
- 基于51单片机的点阵贪吃蛇
这是基于51单片机的贪吃蛇小游戏,用四个独立按键控制上下左右,用8*8点阵作为显示 程序如下: #include <reg52.h> #include <intrins.h> ...
- 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy
** 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy ** 一直很想写一个贪吃蛇的小程序,这两天终于抽空完成了,这里把我的思路分享给大家,仅供参考! 代码如下: 先放段主函数压压惊 v ...
- 基于51单片机的贪吃蛇游戏设计
1绪 论 1.1本课题研究的背景及意义 随着当今社会的发展,人们的生活节奏变得越来越快,人们开始逐渐的融入全球化的世界.人们已经不再局限于一小块天地,加班,出差已经占据了现代人生活的绝大部分.这个时候 ...
最新文章
- 使用PCB Editor 制作元件封装Footprint
- Nginx服务器的安装配置
- javascript--拖动图片时取消浏览器默认提示
- svn教程----TortoiseSVN常用操作
- RichTextBox中表格不能折行的问题
- python中str和input_python中input()与raw_input()的区别分析
- MySQL5.6忘记root用户名和密码
- Linux C 存储映射IO
- 不想横屏看视频?谷歌开源框架AutoFlip一键截出最精彩竖版视频
- 百科知识 STEP文件如何打开
- OSI七层协议完美解读
- DSP eQEP正交编码
- ios9提取安装包ipa_支付宝9.9苹果版-ios支付宝9.9内测版下载ipa提取版-《百度网盘下载》西西软件下载...
- 一年级上册计算机教学计划,一年级信息技术上册教学计划一年级信息技术教学计划...
- 计算机word中的行间距在哪里设置,word怎么把所有行间距设置成22磅
- 用 Python 20秒画完小猪佩奇“社会人”
- mysql-5.7.11-winx64_mysql 5.7.11 winx64安装配置教程
- OC10 -- block / 多态
- 《Programming in Lua 3》读书笔记(一)
- 2019 年社保抵扣所得税说明
热门文章
- 【微淘百课】多群直播-微信群直播
- 计算机ram数据原理,每日一科普:了解RAM是什么?有何用?
- 我所经历的一次Dubbo服务雪崩,这是一个漫长的故事
- 一位20多年SE“骨灰级老炮”,告诉你售前工程师能力分级的真相
- 计算机控制g s 求d s,自动控制原理-中国大学mooc-题库零氪
- 管理2.0 (3):从粗放到精细化的企业管理升级路
- 女工程师独家揭秘:双11秘密武器阿里云数据库团队故事
- 带外数据:TCP紧急模式分析
- Android Socket IQ聊天软件 之 聊天气泡编码步骤讲解
- 87个C#帮助类,各种功能性代码(转载自微信公众号:dotNET全栈开发)