基于普中51单片机的贪吃蛇游戏
一、8*8LED点阵模块以及74HC595模块
首先我们先来看LED点阵的原理图:
在进行LED点阵点亮之前我们先要了解74HC595写入规则。595是一个串行输入并行输出的芯片,SRCLK引脚上升沿有效时从SER端读取一个bit位,因此8个周期后可读取一个字节的数据,此时RCLK来一个上升沿数据便可以从8个并行输出口输出数据。以下是595的写入函数:
void write_595(char dat)
{
int i;
for(i=0;i<8;i++)
{
ser = dat >> 7;//取最高位
dat = dat << 1;//把每次要读取的bit位放到最高位
srclk = 0;//产生上升沿
srclk = 1;
}
rclk = 0;//产生上升沿
rclk = 1;
}
会往74HC595写入数据后,我们可以看到LED点阵的每行接到了74HC595的并行输出口,而每列直接接到了51单片机的P0口,这样我们如果想要点亮某一个LED等,只需配置74HC595的输入以及51单片机的P0口。例如点亮第0行第0列的LED灯:
只需给595的输入口SER写入10000000b,51单片机的P0口写入01111111b即可(让LED点阵的第0行为高电平,其余行为低电平;第0列为低电平,其余列为高电平,这样就只有第0行第0列的LED点亮)。这样我们可以事先把行使能与列使能提前存入两个数组中,方便后续使用。
char buff[8] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
char buff_P0[8] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
其中buff数组为行使能,buff_P0数组为列使能。如果想要依次点亮8*8点阵,可以进行以下操作:
void display()
{
int i,j;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
write_595(buff[i]);
led_col = buff_P0[j];
delay(50000);
}
}
}
有了以上的基础,我们来想一想如何实现贪吃蛇游戏。以下是我的想法:
创建一个88的数组matrix来模拟88的LED点阵。围墙的数值计为-1;食物的数值计为-2;蛇的身体为1到当前的长度len,1代表尾巴,len代表长度;其余位置计为0,这样我们的打印函数就很好实现了:
void display()
{
int i,j;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
//打印围墙,食物,蛇的身体
if(matrix[i][j] == -1 || matrix[i][j] > 0 || matrix[i][j] == -2)
{
write_595(buff[i]);
led_col = buff_P0[j];
}
}
}
}
二、按键检测模块
实现了打印函数后,接下来我们要让蛇动起来,即通过四个独立按键代表上下左右四个方向。那么就先要实现按键的检测。按键检测需要注意的点是按键消抖,否则按键检测就不灵敏。我们先来看独立按键的原理图:
可以看到四个独立按键接在了51单片机P3的四个口上,按下为低电平,独立按键的扫描代码如下:
char key_scan(char mode)
{
static int n = 1;
if(mode == 1)//mode为1时实现连续检测
n = 1;
if(n == 1 && (key1 == 0 || key2 == 0 || key3 == 0 || key4 == 0))
{
n=0;
delay(1000);//按键消抖
if(key1 == 0)
return 1;
else if(key2 == 0)
return 2;
else if(key3 == 0)
return 3;
else if(key4 == 0)
return 4;
}
else if(key1 == 1 && key2 == 1 && key3 == 1 && key4 == 1 )
{
n=1;
}
return 0;
}
注意:mode为1时实现连续检测,后续用的是mode=0,即检测一次。如果理解不了可以看看b站普中科技的按键检测实验。
三、方向移动模块
实现按键扫描后,那么每个按键就代表了不同的方向,按下后就向不同的方向移动。如何实现方向的移动呢?我的想法是:
遍历数组找到数值为len的点,如果向上移动就将这个点的上方点赋值为len+1,若上方点是食物(-2),则给当前长度加一,生成食物并结束;若这点是空白处(0),则再遍历一遍数组将值大于0的点都减一;若这点是墙或者自己的身体(-1或者大于0),则让蜂鸣器发出警报。其余方向的移动思路都类似,废话不多说,之间上代码:
void up()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i-1][j] == -2)
flag = 0;
else if(matrix[i-1][j] == -1 || matrix[i-1][j] > 0)
flag = 2;
matrix[i-1][j] = len+1;
}
//上方是空白处
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
//上方是食物
else if(flag == 0)
{
len++;
make_food();
}
//上方是墙或者蛇的身体
else if(flag == 2)
err();
m=0;
}
void down()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i+1][j] == -2)
flag = 0;
else if(matrix[i+1][j] == -1 || matrix[i+1][j] > 0)
flag = 2;
matrix[i+1][j] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void left()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i][j-1] == -2)
flag = 0;
else if(matrix[i][j-1] == -1 || matrix[i][j-1] > 0)
flag = 2;
matrix[i][j-1] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void right()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i][j+1] == -2)
flag = 0;
else if(matrix[i][j+1] == -1 || matrix[i][j+1] > 0)
flag = 2;
matrix[i][j+1] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
四、判断当前方向模块
按键值代表对应方向,代码十分简单:
void run_dir()
{
if(key == 1)//上
{
dir = 1;
m=0;
}
if(key == 2)//下
{
dir = 2;
m=0;
}
if(key == 3)//左
{
dir = 3;
m=0;
}
if(key == 4)//右
{
dir = 4;
m=0;
}
}
五、蜂鸣器模块
蜂鸣器为无源蜂鸣器,beep接在P2的第5口,让其发出声音只需给出一定频率的信号:
void err()
{
int i = 2000;//持续时间
while(i--)
{
beep = !beep;
delay(100);//半个周期
}
i = 0;
}
六、食物生成模块
生成两个0-7的随机数,代表x和y坐标,要注意不能生成到围墙和蛇的身体上:
void make_food()
{
int x,y;
while(1)
{
x=rand()%8;
y=rand()%8;
if(matrix[x][y] == 0)//生成到空白处
break;
}
matrix[x][y] = -2;
}
七、定时器中断模块
为什么要用中断呢?想象一下,在主函数里我们要循环打印每一个状态,但是蛇的移动速度很慢(秒级),如果放在主函数里就要延时相对长一点的时间,这样的话我们看到的画面就是一闪一闪的。因此我引入了中断模块。在主函数里进行循环打印和按键检测(毫秒级),然后中断处理函数里面判断当前的方向(毫秒级的时间发一次中断,通过m计数实现计数到秒级),每隔大约秒级的时间进行蛇的移动。
总体是主函数执行一段毫秒级时间后,在中断处理程序里判断当前的方向,但因为m的值未到达秒级时间,不进行移动,然后继续执行主函数,然后再发生中断执行中断处理函数,如此重复到秒级时间后,蛇移动。但是每隔毫秒级的时间执行主函数时都会打印一下画面,因此不会出现一闪一闪的情况。代码如下:
void time0_init()
{
TMOD |= 0x01;
TH0 = 0xed;
TL0 = 0xff;
ET0 = 1;
EA = 1;
TR0 = 1;
}
void time0() interrupt 1
{
TH0 = 0xed;
TL0 = 0xff;
run_dir();
m++;
if( m == 100 && dir == 1)
up();
else if( m == 100 && dir == 2)
down();
else if( m == 100 && dir == 3)
left();
else if( m == 100 && dir == 4)
right();
}
八、总体代码
#include "reg52.h"
#include"stdlib.h"
sbit key1 = P3^1;
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;
sbit ser = P3^4;
sbit rclk = P3^5;
sbit srclk = P3^6;
sbit beep = P2^5;
#define led_col P0
char buff[8] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
char buff_P0[8] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
char matrix[8][8] = {0};
int len;
int m = 0;
char dir = 0;
char key = 0;
void delay(int n)
{
while(n--)
{
}
}
void err()
{
int i = 2000;
while(i--)
{
beep = !beep;
delay(100);
}
i = 0;
}
char key_scan(char mode)
{
static int n = 1;
if(mode == 1)
n = 1;
if(n == 1 && (key1 == 0 || key2 == 0 || key3 == 0 || key4 == 0))
{
n=0;
delay(1000);
if(key1 == 0)
return 1;
else if(key2 == 0)
return 2;
else if(key3 == 0)
return 3;
else if(key4 == 0)
return 4;
}
else if(key1 == 1 && key2 == 1 && key3 == 1 && key4 == 1 )
{
n=1;
}
return 0;
}
void write_595(char dat)
{
int i;
for(i=0;i<8;i++)
{
ser = dat >> 7;
dat = dat << 1;
srclk = 0;
srclk = 1;
}
rclk = 0;
rclk = 1;
}
void init ()
{
int i,j;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(i == 0 || i == 7 || j == 0 || j == 7)
matrix[i][j] = -1;
matrix[3][2]=1;
matrix[3][3]=2;
matrix[3][4]=3;
len = 3;
}
void make_food()
{
int x,y;
while(1)
{
x=rand()%8;
y=rand()%8;
if(matrix[x][y] == 0)
break;
}
matrix[x][y] = -2;
}
void display()
{
int i,j;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(matrix[i][j] == -1 || matrix[i][j] > 0 || matrix[i][j] == -2)
{
write_595(buff[i]);
led_col = buff_P0[j];
}
}
}
}
void run_dir()
{
if(key == 1)
{
dir = 1;
m=0;
}
if(key == 2)
{
dir = 2;
m=0;
}
if(key == 3)
{
dir = 3;
m=0;
}
if(key == 4)
{
dir = 4;
m=0;
}
}
void up()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i-1][j] == -2)
flag = 0;
else if(matrix[i-1][j] == -1 || matrix[i-1][j] > 0)
flag = 2;
matrix[i-1][j] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void down()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i+1][j] == -2)
flag = 0;
else if(matrix[i+1][j] == -1 || matrix[i+1][j] > 0)
flag = 2;
matrix[i+1][j] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void left()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i][j-1] == -2)
flag = 0;
else if(matrix[i][j-1] == -1 || matrix[i][j-1] > 0)
flag = 2;
matrix[i][j-1] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void right()
{
int i,j,flag=1;
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] == len)
{
if(matrix[i][j+1] == -2)
flag = 0;
else if(matrix[i][j+1] == -1 || matrix[i][j+1] > 0)
flag = 2;
matrix[i][j+1] = len+1;
}
if(flag == 1)
{
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(matrix[i][j] > 0)
matrix[i][j] -= 1;
}
else if(flag == 0)
{
len++;
make_food();
}
else if(flag == 2)
err();
m=0;
}
void time0_init()
{
TMOD |= 0x01;
TH0 = 0xed;
TL0 = 0xff;
ET0 = 1;
EA = 1;
TR0 = 1;
}
void time0() interrupt 1
{
TH0 = 0xed;
TL0 = 0xff;
run_dir();
m++;
if( m == 100 && dir == 1)
up();
else if( m == 100 && dir == 2)
down();
else if( m == 100 && dir == 3)
left();
else if( m == 100 && dir == 4)
right();
}
void main()
{
init();
make_food();
time0_init();
while(1)
{
display();
key = key_scan(0);
delay(100);
}
}
本人刚学习51单片机不久,有不周到之处或者错误的地方欢迎网友们指正。