基于普中51单片机的贪吃蛇游戏

基于普中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单片机不久,有不周到之处或者错误的地方欢迎网友们指正。

  • 11
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值