C51单片机实现 贪吃蛇

一. 实验所需模块

4×4矩阵键盘,8×8点阵,定时器0

二. 模块简介

  1. 4×4矩阵键盘

    1. 矩阵键盘采用行和列扫描的方式进行判断那个按键被按下了
    2. 先进行扫描或先进行列扫描都可以
    3. 行扫描的时候,先将行所在的引脚置一,列所在的引脚置0,然后判断行所在引脚的电平的值,随后进行列扫描
  2. 8×8点阵

    1. 8×8点阵共由64个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上。
    2. 当某一行置1,某一列置0时,对应的二极管就会点亮。
    3. 与动态数码类似,利用视觉残留效果,即可实现动态依次点亮的效果
  3. 定时器0

    1. 16位定时器由高8位和低8位的两个寄存器组成TH0和TL0。
    2. 每来一个脉冲,定时器的值就会加一,当达到最大的时候,就会溢出清零,并产生中断信号。
    3. 预先设置TH0和TL0的值,可以得到我们所需的生产中断信号的间隔。

三. 点亮一个灯

这个很简单,就是把某一行线置1,并且把某一列线置0,其他的行线全部为0,列线全部为1.

void  Led(u8 dat,u8 dat2)  //dat为行线值,dat2为列线值
{
	u16 i;
	RCLKS = 1;
	SRCLK = 1;
	
	
	for(i=0;i<8;i++)
	{
	//行线的输入是由74HC595模块输入的,
	//通过时钟的上升沿把每一位的数据输入即可,高位优先
		SET = dat>>7;
		dat = dat <<1;
		SRCLK = 0;
		SRCLK =1;
	}
	
	RCLKS = 0;
	
	RCLKS = 1;
	P0 = dat2;   //列线由P0端口输入
}

这里令dat = 0x20,dat2=fb的时候,一个发光二极管就被点亮啦!

四. 一个灯的移动

前面我们已经知道了,行线可以控制那一行亮那一行不亮,列线也同样如此。
假设当我们的列线不变,行线左移一位或者右移一位,是不是我们的灯向上或者向下移动了一个!行线不变时,列线的移动同样如此,但是要注意,列线是为0点亮,每一次左移或右移,低位或高位就会多出来一个0,这时,我们还需要加上0x01或者0x80以消掉这个多出来的0

1.向上移动

列线不变,行线左移一位即可
if(Rows[0] != 0x80)  //防止移过头了
		Rows[0] <<=1;

2.向下移动

列线不变,行线右移一位即可
if(Rows[0] != 0x01)
		Rows[0]>>=1;

3.向左移动

行线不变,列线左移一位加0x01即可
if(Cols[0] != 0x7F)
	{
		Cols[0] <<=1;
		Cols[0] += 1;
	}
  1. 向右移动

    行线不变,列线右移一位加0x80即可

if(Cols[0] != 0xFE)
	{
		Cols[0] >>=1;
		Cols[0] += 0x80;
	}

自此,一个灯的上下左右的移动我们已经完成了,接下来蛇的移动了

五. 贪吃蛇

认真看玩过贪吃蛇的朋友应该都清楚,蛇身是随着蛇头的移动而移动的。其中不难发现。当前蛇的某一节的位置是它前面一节蛇的上一个状态的位置,知道了这个规律之后,我们就可以很方便地更新蛇的状态了。
由于8×8点阵显示有限,我就只设置了蛇的最大长度为5,防止闪烁。

1.蛇的存储

这里我采取的是数组的方式,存储每一节的行线值和列线值
u8 Rows[5];  //行线值
u8 Cols[5];  //列线值

2.蛇的更新

前面说过,蛇的当前蛇的某一节的位置是它前面一节蛇的上一个状态的位置,也就是说,Rows[n-1] = Rows[n],Cols[n-1] = Cols[n]
当然这里的Rows[n],和Cols[n]是上一个状态的值了
void Up(u8 len)   //当前蛇的长度
{
	u8 i;
	u8 tmp = Rows[0];
	u8 rr,cc_1,cc_2;
	if(Rows[0] != 0x80)
		Rows[0] <<=1;
	cc_1 = Cols[0];
        //后一节依次等于前一节的上一个状态的值
	for(i=1;i<len;i++)
	{
		
		rr = Rows[i];  // 保存上一个状态
		Rows[i] =tmp;  //Rows[i]为当前状态
		tmp = rr;
		
		cc_2 = Cols[i];   // 保存上一个状态
		Cols[i] = cc_1;   
		cc_1 = cc_2;
	}
}

这里就只拿了向上移动的代码作为参考,其他方向的移动类型,基本上一样,这里蛇的移动也完成了。

3.食物

食物的行线与列线同样用两个变量存储
食物的位置是随机的,这里我们用rand产生随机数并对8求余数,
这样就得到了0-7的随机数,然后对0x01进行相应的左移,就得到了
选定的行,取反就得到了选定的列。
Food_col = ~(0x01 << rand()%8);
Food_row = 0x01 << rand()%8;    

4.吃到食物

这也是很好办的事,只需要判断蛇头的Row和Col是否等于食物的Row和Col,
如果相等,则更新食物的位置,蛇身加一.
if((Rows[0] == Food_row) && (Cols[0] == Food_col)) // 判断是否吃到食物
		{
			if(len<5)  //判断蛇长是否达到最大值
			{
				Rows[len] = Rows[len-1];
				Cols[len] = Cols[len-1];
				len++;
			}
			//随机更新食物的位置
			Food_col = ~(0x01 << rand()%8);
			Food_row = 0x01 << rand()%8;
		}

自此,贪吃蛇可以说已经基本完成了,剩下的就是用键盘来控制蛇的移动方向了

六. 蛇方向的控制

方向的控制我把它放在中断里面,这样可以解决如下问题

  1. 如果不用延迟,蛇移动得太快,根本看不清楚蛇的移动。
  2. 如果用延迟来控制蛇移动的速度,那么键盘的读取就会变动不灵敏,
    要按下许久之后,才能读取,游戏效果不好。

因此我决定把它放在定时器中断中,通过设定初值,以极小的时间差,来读取键盘的内容,即可达到实时的效果,而且延时对这个完全没有干扰。

void Time_0() interrupt 1
{
	u8 key;
	key = KEY();
	if(key != -1)
	{
		if(key == 1)
		{
			//Up(len);
			direct = 0;
			delay(10000); // 不用也没有关系,不会造成影响
		}
		if(key == 9)
		{
			//Down(len);
			direct = 1;
			delay(10000);
		}
		if(key == 4)
		{
			//Left(len);
			direct = 2;
			delay(10000);
		}
		if(key == 6)
		{
			//Right(len);
			direct = 3;
			delay(10000);
		}
	}
	//防止p过大
	if(p>8*100)
		p = 0;
	p++;
	//每8次移动一下,这是合理的,由于每一次的时间差极小
	//8次加起来的时间差也是非常小的,
	//该时间差小于先后按下两个不同方向的时间差,因为这是合理的
	if(p%8 == 0)
	{
	switch(direct)
	{
		case 0: Up(len);break;
		case 1: Down(len);break;
		case 2: Left(len);break;
		case 3: Right(len);break;
	}
	}
	TH0 = 0x0c;
	TL0 = 0x0c;
}

七. 最后就是main函数啦

这部分也是非常简单的。

void main()
{
	u8 i;
	//u8 len = 1;  // 初始化长度,这个放到全局变量中了
	u8 Food_row = 0x20;
	u8 Food_col = 0xfb;
	//u8 direct = 0;// 初始化方向,这个放到全局变量中了
	Rows[0] = Row;  //初始化,蛇头的位置
	Cols[0] = Col;
	Init_time_0();   //配置定时器0,开启中断
	while(1)
	{
		//判断蛇头是否到达食物的位置
		if((Rows[0] == Food_row) && (Cols[0] == Food_col))
		{
			if(len<5)
			{
				Rows[len] = Rows[len-1];
				Cols[len] = Cols[len-1];
				len++;
			}
                        //随机更新食物的位置
                Food_col = ~(0x01 << rand()%8);
                Food_row = 0x01 << rand()%8;
		}
		
		Led(Food_row,Food_col);  //显示食物
		for(i=0;i<len;i++)
			Led(Rows[i],Cols[i]);  // 显示蛇
			
		//这里用延时来控制速度的话,容易产生闪烁。
	}
}

至此贪吃蛇就完成啦!!!!

完整代码如下

#include


typedef unsigned int u16;
typedef unsigned char u8;

sbit RCLKS = P3^5;
sbit SRCLK = P3^6;
sbit SET = P3^4;

//Row为1亮,Col为0亮
u8 Row = 0x08;
u8 Col = 0xF7;

//最大长度为5 ,防止过长出现闪烁
u8 Rows[5];
u8 Cols[5];

u8 len = 1;
u8 direct = 0;

u16 p=0;//每8次中断检测一次
void Init_time_0()
{
	TMOD = 0x01;
	TH0 = 0x0c;
	TL0 = 0x0c;
	ET0 = 1;
	EA = 1;
	TR0 = 1;
}


void delay(u16 i)
{
	while(i--);
}
void  Led(u8 dat,u8 dat2)
{
	u16 i;
	RCLKS = 1;
	SRCLK = 1;
	
	
	for(i=0;i<8;i++)
	{
		SET = dat>>7;
		dat = dat <<1;
		SRCLK = 0;
		SRCLK =1;
	}
	
	RCLKS = 0;
	
	RCLKS = 1;
	P0 = dat2;
}


u16 KEY()	
{
	u16 key = -1;
	P1 = 0x0f;
	
	
	if(P1 != 0x0f)
	{
		delay(500);
		
		if(P1 != 0x0f)
		{
			P1 = 0x0f;
			switch(P1)
			{
				case (0x07): key = 0;break;
				case (0x0b): key = 1;break;
				case (0x0d): key = 2;break;
				case (0x0e): key = 3;break;
			}
			P1 = 0xf0;
			switch(P1)
			{
				case(0x70): key+=0;break;
				case(0xb0): key+=4;break;
				case(0xd0): key+=8;break;
				case(0xe0): key+=12;break;
			}
		}
		
		
	
	
	}
	
	
	return key;
	

}




void Up(u8 len)
{
	u8 i;
	u8 tmp = Rows[0];
	u8 rr,cc_1,cc_2;
	if(Rows[0] != 0x80)
		Rows[0] <<=1;
	cc_1 = Cols[0];
        //后一节依次等于前一节的值
	for(i=1;i<len;i++)
	{
		
		rr = Rows[i];
		Rows[i] =tmp;
		tmp = rr;
		
		cc_2 = Cols[i];
		Cols[i] = cc_1;
		cc_1 = cc_2;
	}

}


void Down(u8 len)
{
	u8 i;
	u8 tmp = Rows[0];
	u8 rr,cc_1,cc_2;
	if(Rows[0] != 0x01)
		Rows[0]>>=1;
	cc_1 = Cols[0];
	for(i=1;i<len;i++)
	{
		
		rr = Rows[i];
		Rows[i] =tmp;
		tmp = rr;
		
		cc_2 = Cols[i];
		Cols[i] = cc_1;
		cc_1 = cc_2;
	}
}

void Left(u8 len)
{
	u8 i;
	u8 tmp = Cols[0];
	u8 cc,rr_1,rr_2;
	if(Cols[0] != 0x7F)
	{
		Cols[0] <<=1;
		Cols[0] += 1;
	}
	rr_1 = Rows[0];
	for(i=1;i<len;i++)
	{
		cc = Cols[i];
		Cols[i] = tmp;
		tmp = cc;
		
		rr_2 = Rows[i];
		Rows[i] = rr_1;
		rr_1 = rr_2;
	
	}
}

void Right(u8 len)
{
	u8 i;
	u8 tmp = Cols[0];
	u8 cc,rr_1,rr_2;
	if(Cols[0] != 0xFE)
	{
		Cols[0] >>=1;
		Cols[0] += 0x80;
	}
	rr_1 = Rows[0];
	for(i=1;i<len;i++)
	{
		cc = Cols[i];
		Cols[i] = tmp;
		tmp = cc;
		
		rr_2 = Rows[i];
		Rows[i] = rr_1;
		rr_1 = rr_2;
	
	}
}

void main()
{
	u8 i;
	u16 key;
	//u8 len = 1;
	u8 Food_row = 0x20;
	u8 Food_col = 0xfb;
	//u8 direct = 0;
	Rows[0] = Row;
	Cols[0] = Col;
	Init_time_0();
	while(1)
	{
//		key = KEY();
//		if(key != -1)
//		{
//			if(key == 1)
//			{
//				Up(len);
//			//	direct = 0;
//				delay(20000);
//			}
//			if(key == 9)
//			{
//				Down(len);
//				//direct = 1;
//				delay(20000);
//			}
//			if(key == 4)
//			{
//				Left(len);
//				//direct = 2;
//				delay(20000);
//			}
//			if(key == 6)
//			{
//				Right(len);
//				//direct = 3;
//				delay(20000);
//			}
//		}
//		switch(direct)
//		{
//			case 0: Up(len);break;
//			case 1: Down(len);break;
//			case 2: Left(len);break;
//			case 3: Right(len);break;
//		}
		//判断蛇头是否到达食物的位置
		if((Rows[0] == Food_row) && (Cols[0] == Food_col))
		{
			if(len<5)
			{
				Rows[len] = Rows[len-1];
				Cols[len] = Cols[len-1];
				len++;

                                
                               
			}
                        //随机更新食物的位置
                        Food_col = ~(0x01 << rand()%8);
                        Food_row = 0x01 << rand()%8;
		}
		
		Led(Food_row,Food_col);
		for(i=0;i<len;i++)
			Led(Rows[i],Cols[i]);
		
	}


}

void Time_0() interrupt 1
{
	u8 key;
	key = KEY();
	if(key != -1)
	{
		if(key == 1)
		{
			//Up(len);
			direct = 0;
			delay(10000);
		}
		if(key == 9)
		{
			//Down(len);
			direct = 1;
			delay(10000);
		}
		if(key == 4)
		{
			//Left(len);
			direct = 2;
			delay(10000);
		}
		if(key == 6)
		{
			//Right(len);
			direct = 3;
			delay(10000);
		}
	}
	//防止p过大
	if(p>8*100)
		p = 0;
	p++;
	if(p%8 == 0)
	{
	switch(direct)
	{
		case 0: Up(len);break;
		case 1: Down(len);break;
		case 2: Left(len);break;
		case 3: Right(len);break;
	}
	}
	TH0 = 0x0c;
	TL0 = 0x0c;
}

Thank for your reading !!!

你可能感兴趣的:(C51,贪吃蛇,单片机)