基于51单片机的贪吃蛇游戏Protues仿真设计

目录

[1 系统设计目的](#1 系统设计目的)

[2 系统实现功能](#2 系统实现功能)

[3 系统硬件设计](#3 系统硬件设计)

3.1系统设计框图

[3.2 液晶显示模块LCD12864](#3.2 液晶显示模块LCD12864)

[3.3 按键输入模块](#3.3 按键输入模块)

[3.4 时钟电路和复位电路](#3.4 时钟电路和复位电路)

[4 系统软件设计](#4 系统软件设计)

4.1系统软件流程

[4.2 游戏引擎模块程序设计](#4.2 游戏引擎模块程序设计)

[4.3 显示模块程序设计](#4.3 显示模块程序设计)

[4.4 输入处理模块程序设计](#4.4 输入处理模块程序设计)

[5 系统仿真结果](#5 系统仿真结果)

[5.1 游戏引擎模块的实现](#5.1 游戏引擎模块的实现)

[5.2 显示模块功能的实现](#5.2 显示模块功能的实现)

[5.3 输入处理模块的实现](#5.3 输入处理模块的实现)


1 系统 设计目的

设计一个基于51单片机的贪吃蛇游戏,可以帮助学习者掌握硬件控制相关知识、学习游戏开发和代码实现,提高编程能力和解决问题的能力,同时培养团队合作精神和创新意识。

(1)掌握基于51单片机的硬件控制相关知识,包括输入输出口的操作、中断处理、定时器/计数器等模块的使用。

(2)学习贪吃蛇游戏的实现方法,掌握游戏逻辑的设计和代码实现,加深对面向对象编程的理解。

(3)提高编程能力和解决问题的能力,通过设计和编写程序,发现和解决软件和电路方面的问题。

(4)培养团队合作精神和创新意识,多思考和尝试不同的解决方案,共同完成项目。

(5)加深对嵌入式系统和游戏开发的了解,拓展相关领域的技能。

2 系统实现功能

本项目旨在设计和实现一个基于51单片机的贪吃蛇游戏,使用LCD12864显示屏作为游戏界面,通过矩阵按键控制蛇的移动,并增加暂停/启动游戏、显示已吃的食物数量等功能。以下是该项目的详细内容和功能要求:

(1)游戏界面设计:

使用LCD12864显示屏作为游戏界面,具有较高的分辨率,能够显示游戏界面、蛇、食物等元素。游戏界面应具备良好的界面布局,清晰明了,易于辨认。

(2)蛇的控制:

运用矩阵按键的4个按键分别控制蛇的上、下、左、右移动。当蛇吃到食物时,长度增加并重新生成食物,结束时显示已吃的食物数量。蛇的移动速度可以逐渐加快,增加游戏难度。

(3)暂停/启动游戏功能:

设计一个特定的按键,用于暂停或启动游戏。当按下暂停按键时,游戏暂停,蛇停止移动,游戏界面保持静止。再次按下该按键时,游戏继续进行,恢复蛇的移动。

(3)食物显示功能:

在游戏界面中增加显示当前已吃的食物数量的功能。每次蛇吃到食物时,更新并显示已吃的食物数量。

3 系统硬件设计

3.1 系统 设计 框图

本设计由控制器、液晶显示模块、按键输入模块、时钟电路和复位电路等5部分组成。控制器是系统的核心,通过IO口控制液晶显示模块,实现游戏画面的显示和游戏状态的更新,同时也可以接收按键输入模块的信号,根据使用者的操作控制蛇的移动和游戏速度等参数;液晶显示模块LCD12864用于显示游戏界面和相关信息;按键输入模块用于接收用户的操作输入,通过按下不同的按钮,用户可以控制蛇的移动方向和游戏速度等。时钟电路是为主控芯片提供稳定的时钟信号,通过确定的振荡频率,主控芯片可以按照时序进行正常的工作和指令执行;复位电路可以使用复位按钮或者上电复位电路,通过将主控芯片的复位引脚拉低一段时间来实现复位操作。系统设计框图如图3.1所示。

3.2 液晶显示模块LCD12864

液晶显示模块用于显示游戏界面和相关信息,一般采用16x2或128x64字符液晶显示屏。通过控制芯片的指令和数据,可以在屏幕上绘制游戏区域、分数以及游戏状态等内容,为用户提供良好的视觉体验。电路原理图如图3.2所示。

3.3 按键输入模块

按键输入模块用于接收用户的操作输入,通过使用矩阵式按键阵列,按下不同的按钮,用户可以控制蛇的移动方向和游戏速度等。在硬件设计中,需要考虑防抖处理,以避免因按键抖动产生误操作。电路原理图如图3.3所示。

3.4 时钟电路和复位电路

时钟电路是为主控芯片提供稳定的时钟信号,常使用晶体振荡器。通过确定的振荡频率,主控芯片可以按照时序进行正常的工作和指令执行。根据系统需要,可以选择合适的晶体频率,通常使用10MHz或者12MHz晶体。

复位电路用于在系统上电或者复位时将主控芯片初始化为一个已知的状态,以确保系统在启动时正常工作。复位电路可以使用复位按钮或者上电复位电路,通过将主控芯片的复位引脚拉低一段时间来实现复位操作。电路原理图如图3.4所示。

4 系统软件设计

4.1系统软件流程

基于51单片机的贪吃蛇游戏系统的软件部分包括四个主要模块,分别是游戏引擎模块、显示模块、输入处理模块和计时模块。游戏引擎模块是贪吃蛇游戏的核心,它负责控制游戏的状态和逻辑;显示模块主要负责将游戏的状态和画面显示在液晶屏上;输入处理模块负责读取用户按键操作的输入,并将其传递给游戏引擎模块进行处理;计时模块主要负责控制游戏的速度和帧率,为游戏加入时间的概念。基于51单片机的贪吃蛇游戏系统的软件部分通过游戏引擎、显示、输入处理和计时等模块的协同工作,为用户提供了一个有趣、稳定和流畅的游戏体验。总体软件流程图如图4.1所示。

4.2 游戏引擎模块程序设计

游戏引擎模块是贪吃蛇游戏的核心,它负责控制游戏的状态和逻辑。在每一个游戏循环中,它根据当前蛇的状态和用户的操作来更新蛇的位置、食物的位置,检测蛇是否撞墙或者咬到自己的身体,计算得分等等。游戏引擎模块也负责处理游戏的初始化和结束逻辑,使得游戏在启动和结束时能够正常的运行和退出。游戏引擎模块程序设计流程图如图4.2所示:

游戏引擎模块部分程序如下所示:

cpp 复制代码
void Game_Play()					//游戏的具体过程,也是贪吃蛇算法的关键部分
{
	uchar n;
	InitRandom(TL0);
	food.yes=1;			//1表示需要出现新事物,0表示已经存在食物尚未吃掉
	snake.life=0;					//表示蛇还活着
	snake.direction=DOWN;
	snake.x[0]=6;snake.y[0]=6;
	snake.x[1]=3;snake.y[1]=6;
	snake.node=2;
	Lcd_Show_Score();				//显示分数
	Lcd_Show_Title();			   	//显示标题
	while(1)
	{
		if(food.yes==1)
		{
			while(1)
			{
				food.x=Random()*85+3;
				food.y=Random()*55+3;		//获得随机数
	
				while(food.x%3!=0)
					food.x++;
				while(food.y%3!=0)
					food.y++;
			    for(n=0;n<snake.node;n++)	//判断产生的食物坐标是否和蛇身重合
				{
					if((food.x==snake.x[n])&&(food.y==snake.y[n]))
						break;
				}
				if(n==snake.node)
				{
					food.yes=0;
					break;			//产生有效的食物坐标
				}
			}
		}
		if(food.yes==0)
		{
			Lcd_Rectangle(food.x,food.y,food.x+2,food.y+2,1);
		}	
		if(Run==0)
		{
			for(n=snake.node-1;n>0;n--)
			{
				snake.x[n]=snake.x[n-1];
				snake.y[n]=snake.y[n-1];
			}	
			switch(snake.direction)
			{
				case DOWN:snake.x[0]+=3;break;
				case UP:snake.x[0]-=3;break;
				case RIGHT:snake.y[0]-=3;break;
				case LEFT:snake.y[0]+=3;break;
				default:break;
			}
			for(n=3;n<snake.node;n++)	//从第三节开始判断蛇头是否咬到自己
			{
				if(snake.x[n]==snake.x[0]&&snake.y[n]==snake.y[0])
				{
					Game_Over();
					snake.life=1;
					break;
				}
			}
		}
		if(snake.x[0]<3||snake.x[0]>=90||snake.y[0]<3||snake.y[0]>=60)//判蛇头是否撞到墙壁
		{
			Game_Over();
			snake.life=1;
		}
		if(snake.life==1)
			break;					//蛇死,则跳出while(1)循环
		if(snake.x[0]==food.x&&snake.y[0]==food.y)	//判蛇是否吃到食物
		{
			Lcd_Rectangle(food.x,food.y,food.x+2,food.y+2,0);	//消隐食物
			snake.x[snake.node]=200;
			snake.y[snake.node]=200;//产生蛇新的节坐标先放在看不见的位置
			snake.node++;			//蛇节数加1
			food.yes=1;				//食物标志置1
			if(++Score>=PASSSCORE)
			{
				Lcd_Show_Score();
				Game_Over();
				break;
			}
			Lcd_Show_Score();
		}
		for(n=0;n<snake.node;n++)
		{
			Lcd_Rectangle(snake.x[n],snake.y[n],snake.x[n]+2,snake.y[n]+2,1);
		}							//根据蛇的节数画出蛇
		Delay(Speed*1000);
		Lcd_Rectangle(snake.x[snake.node-1],snake.y[snake.node-1],snake.x[snake.node-1]+2,snake.y[snake.node-1]+2,0);
		switch(KeyBuffer)
		{
			case FUNC:
					KeyBuffer=0;
					if(++Speed>=10)
						Speed=1;
				   	Lcd_Show_Title();
					break;
			case DOWN:				//下
					KeyBuffer=0;
					if(snake.direction!=UP)
						snake.direction=DOWN;
					break;
			case UP:	  			//上
					KeyBuffer=0;
					if(snake.direction!=DOWN)
						snake.direction=UP;
					break;
			case RIGHT:		  		//右
					KeyBuffer=0;
					if(snake.direction!=LEFT)
						snake.direction=RIGHT;
					break;
		   	case LEFT:				//左
					KeyBuffer=0;
					if(snake.direction!=RIGHT)
						snake.direction=LEFT;
					break;
			default:
					break;
		}			
	}
}

4.3 显示模块 程序设计

显示模块主要负责将游戏的状态和画面显示在液晶屏上。它通过调用液晶屏的相关API,绘制出游戏区域、蛇和食物等图形元素,根据游戏引擎模块返回的数据更新分数和其他游戏状态信息。液晶屏的分辨率和色彩深度等参数会影响显示效果,所以在开发时需要根据硬件选型和资源需求进行平衡。显示模块程序设计流程图如图4.3所示:

显示模块部分程序如下所示:

cpp 复制代码
void Lcd_Show_Board()				//LCD显示墙壁函数
{
	uchar n;
	for(n=0;n<31;n++)
	{
		Lcd_Rectangle(3*n,0,3*n+2,2,1);
	}
	for(n=0;n<21;n++)
	{
		Lcd_Rectangle(0,3*n,2,3*n+2,1);
		Lcd_Rectangle(90,3*n,92,3*n+2,1);		
	}
}
void Lcd_Show_Title()				//液晶显示贪吃蛇汉字
{
	Lcd_Show_String(7,0,"贪"); 		//显示汉字
	Lcd_Show_String(7,1,"吃");
	Lcd_Show_String(7,2,"蛇");
}

4.4 输入处理模块 程序设计

输入处理模块负责读取用户按键操作的输入,并将其传递给游戏引擎模块进行处理。根据设计,它可以通过矩阵按键扫描,实现对四个方向的控制和其他额外的功能键,比如开始、暂停、调速等命令。输入处理模块需要防止按键的抖动和误操作,因此一般采用扫描或者中断的方式实现。显示模块程序设计流程图如图4.4所示,输入处理模块部分程序如下所示。

cpp 复制代码
uchar KeyBoard()				/* 反转法键盘扫描 */
{
	uchar temp1,temp2,temp,a=0;
	P1=0xf0;						/* 输入行值(或列值) */
	Delay_MS(1) 	;				/* 延时 */
	temp1=P1;						/* 读列值(或行值) */
	P1=0xff;
	Delay_MS(1);				/* 延时 */
	P1=0x0f;						/* 输入列值(或行值) */
	Delay_MS(1) ;				/* 延时 */	
	temp2=P1;						/* 读行值(或列值) */
	P1=0xff;
	temp=(temp1&0xf0)|(temp2&0xf);	/* 将两次读入数据组合 */
	switch(temp)					/* 通过读入数据组合判断按键位置 */
	{		
		case 0xdd :a=20;break; // 	按键上
		case 0x7d :a=40;break;// 按键下
		case 0xbe :a=30;break;	//  按键左
		case 0xbb :a=10;break;	//  按键右
		default :a=0;
	}
	return a;						/* 返回按键值 */
}

5 系统 仿真结果

5.1 游戏引擎模块 的实现

游戏引擎模块是贪吃蛇游戏的核心,它负责控制游戏的状态和逻辑。系统开始时,进入游戏运行界面。效果如图5.1所示:

5.2 显示模块 功能的实现

液晶显示模块用于显示游戏界面和相关信息,可以在屏幕上绘制游戏区域、分数以及游戏状态等内容,实现效果如图5.2所示。

5.3 输入处理模块 的实现

输入处理模块负责读取用户按键操作的输入,可以通过矩阵按键扫描,实现对四个方向的控制和其他额外的功能键,比如开始、暂停等命令实现效果如图5.3所示。

演示视频:基于51单片机的贪吃蛇游戏设计演示视频-CSDN直播

完整代码:

cpp 复制代码
#include<reg51.h>					//加载头文件			
#include<intrins.h>
#define uchar unsigned char		 	//宏定义
#define uint unsigned int
#define ulong unsigned long
#define A 48271L					//参数宏定义  					
#define M 2147483647L
#define Q (M/A)
#define R (M%A)
#define N 25													
#define FUNC 1
#define UP 2
#define DOWN 3
#define LEFT 4
#define RIGHT 5
#define PASSSCORE 20				//预定义过关成绩
#define SHORT_ON_DITHERING_COUNTER 3//定义短按按下去抖时间
#define SHORT_OFF_DITHERING_COUNTER 3//定义短按松开去抖时间,一般与短按按下去抖时间相同
#define LCD_DATA P0					//液晶数据口定义
sbit LCD_RS=P2^0; 					//液晶并行的指令/数据选择信号, H数据, L命令
sbit LCD_RW=P2^1; 					//液晶并行读写选择信号, H读, L写
sbit LCD_EN=P2^2; 					//液晶并行使能端, H有效, L无效
sbit KEY_UP=P3^4;					//按键上
sbit KEY_RIGHT=P3^5;				//按键右
sbit KEY_LEFT=P3^6;	 				//按键左
sbit KEY_DOWN=P3^7;					//按键下
static ulong Seed=1;				//变量声明
bit Run=0;
uchar Flag=0;
uchar Score=0;
uchar Speed=5;
uchar KeyBuffer=0;
code uint MaskTab[]={				//为加速逻辑运算而设置的掩码表,这是以牺牲空间而换取时间的办法
0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,
0x0100,0x0200,0x0400,0x0800,0x1000,0x2000,0x4000,0x8000};
struct Food
{
	uchar x;
	uchar y;
	uchar yes;
}food;								//食物结构体
struct Snake
{
	uchar x[N];
	uchar y[N];
	uchar node;
	uchar direction;
	uchar life;
}snake;								//蛇结构体
double Random()						//伪随机数发生器
{
	long TmpSeed;
	TmpSeed=A*(Seed%Q)-R*(Seed/Q);
	if(TmpSeed>=0)
		Seed=TmpSeed;
	else
		Seed=TmpSeed+M;
	return (double)Seed/M;
}
void InitRandom(ulong InitVal)		//为伪随机数发生器播种
{
	Seed=InitVal;
}
void Delay(uint t)					//延时子程序
{  
	uint i,j;
	for(i=0;i<t;i++)
		for(j=0;j<10;j++);    
}
void Delay_MS(uint z) 				//z*1MS延时函数
{
	uint x,y;
	for(x=z;x>0;x--)
		for(y=110;y>0;y--);
}
void Timer_Init() 					//定时器初始化
{
	TMOD=0x00;						//定时器工作方式
	TH0=0;	 						//定时器赋初值
	TL0=0;
	ET0=1;	   						//允许定时器0中断
	EA=1;						   	//开总中断
	TR0=1;							//定时器0启动
}
void Lcd_W_Com(uchar com)			//液晶写指令函数
{  
   	LCD_RS=0;
   	LCD_RW=0;
   	LCD_EN=0;
   	_nop_();  
	_nop_();
   	LCD_DATA=com;
   	_nop_(); 
	_nop_();
   	LCD_EN=1;
   	_nop_();  
	_nop_();
   	LCD_EN=0;
}
void Lcd_W_Dat(uchar dat)			//液晶写数据函数
{  
	LCD_RS=1;
	LCD_RW=0;
	LCD_EN=0;
	_nop_();  
	_nop_(); 
	LCD_DATA=dat;
	LCD_EN=1;
	_nop_();
	_nop_();
	LCD_EN=0;
}
uchar Lcd_R_Dat()					//液晶读数据函数
{
	uchar Temp;
 	LCD_DATA=0xff;
 	LCD_RS=1;
	LCD_RW=1;
	LCD_EN=1;
	_nop_();
   	Temp=LCD_DATA;
   	LCD_EN=0;
   	return Temp;
}
void Lcd_Show_String(uchar x,uchar y,uchar *Str)	//液晶在某个位置显示字符串函数
{
	if((y>3)||(x>7))
		return;						//如果指定位置不在显示区域内,则不做任何写入直接返回
	EA=0;	 						//关总中断
	switch(y)
	{
		case 0:				 		//第一行
			Lcd_W_Com(0x80+x);
			break;
		case 1:		 				//第二行
			Lcd_W_Com(0x90+x);
			break;				
		case 2:		  				//第三行
			Lcd_W_Com(0x88+x);
			break;
		case 3:				 		//第四行
			Lcd_W_Com(0x98+x);
			break;
	}
	while(*Str>0)
	{  
		Lcd_W_Dat(*Str);			//写入字符串
  		Str++;     
	}
	EA=1;							//开总中断
}
void Lcd_PutPixel(uchar x,uchar y,uchar Color)	//向LCD指定坐标写入一个象素,象素颜色有两种,0代表白(无显示),1代表黑(有显示)
{
	uchar z,w;
	uint Temp;
	if(x>=128||y>=64)
		return;
	Color=Color%2;
	w=15-x%16;						//确定对这个字的第多少位进行操作
	x=x/16;							//确定为一行上的第几字
	if(y<32) 						//如果为上页
		z=0x80;
	else     						//否则如果为下页
		z=0x88;
	y=y%32;
	EA=0;
	Lcd_W_Com(0x36);
	Lcd_W_Com(y+0x80);        		//行地址
	Lcd_W_Com(x+z);     			//列地址 
	Temp=Lcd_R_Dat();				//先空读一次
	Temp=(uint)Lcd_R_Dat()<<8;		//再读出高8位
	Temp|=(uint)Lcd_R_Dat();		//再读出低8位
	EA=1;
	if(Color==1) 					//如果写入颜色为1
		Temp|=MaskTab[w];			//在此处查表实现加速
	else         					//如果写入颜色为0
		Temp&=~MaskTab[w];			//在此处查表实现加速
	EA=0;
	Lcd_W_Com(y+0x80);        		//行地址
	Lcd_W_Com(x+z);     			//列地址
    Lcd_W_Dat(Temp>>8);				//先写入高8位,再写入低8位
    Lcd_W_Dat(Temp&0x00ff);
	Lcd_W_Com(0x30);
	EA=1;
}
void Lcd_HoriLine(uchar x,uchar y,uchar Length,uchar Color)	//向LCD指定位置画一条长度为Length的指定颜色的水平线
{
	uchar i;
	if(Length==0)
		return;
	for(i=0;i<Length;i++)
		Lcd_PutPixel(x+i,y,Color);
}
void Lcd_VertLine(uchar x,uchar y,uchar Length,uchar Color)	//向LCD指定位置画一条长度为Length的指定颜色的垂直线
{
	uchar i;
	if(Length==0)
		return;
	for(i=0;i<Length;i++)
		Lcd_PutPixel(x,y+i,Color);
}
void Lcd_Rectangle(uchar x0,uchar y0,uchar x1,uchar y1,uchar Color)	//向LCD指定左上角坐标和右下角坐标画一个指定颜色的矩形
{
	uchar Temp;
	if(x0>x1)
	{
		Temp=x0;
		x0=x1;
		x1=Temp;
	}	
	if(y0>y1)
	{
		Temp=y0;
		y0=y1;
		y1=Temp;
	}
	Lcd_VertLine(x0,y0,y1-y0+1,Color);
	Lcd_VertLine(x1,y0,y1-y0+1,Color);
	Lcd_HoriLine(x0,y0,x1-x0+1,Color);
	Lcd_HoriLine(x0,y1,x1-x0+1,Color);	
}
void Lcd_Clear(uchar Mode)			//清除Lcd全屏,如果清除模式Mode为0,则为全屏清除为颜色0(无任何显示)
{  									//否则为全屏清除为颜色1(全屏填充显示)
	uchar x,y,ii;
	uchar Temp;
	if(Mode%2==0)
		Temp=0x00;
	else
		Temp=0xff;
	Lcd_W_Com(0x36);				//扩充指令 绘图显示
	for(ii=0;ii<9;ii+=8)   
		for(y=0;y<0x20;y++)     
			for(x=0;x<8;x++)
			{ 	
				EA=0;	   			//关总中断
				Lcd_W_Com(y+0x80);  //行地址
				Lcd_W_Com(x+0x80+ii); 	//列地址     
				Lcd_W_Dat(Temp); 	//写数据 D15-D8 
				Lcd_W_Dat(Temp); 	//写数据 D7-D0 
				EA=1;		   		//开总中断
			}
	Lcd_W_Com(0x30);
}
void Lcd_Init()						//LCD液晶初始化
{  
	Lcd_W_Com(0x30);       			//选择基本指令集
	Lcd_W_Com(0x0c);       			//开显示(无游标、不反白)
	Lcd_W_Com(0x01);       			//清除显示,并且设定地址指针为00H
	Lcd_W_Com(0x06);       			//指定在资料的读取及写入时,设定游标的移动方向及指定显示的移位
}

//************************************************************************/
// 描述: 反转法键盘扫描 
//************************************************************************/
uchar KeyBoard()				/* 反转法键盘扫描 */
{
	uchar temp1,temp2,temp,a=0;
	P1=0xf0;						/* 输入行值(或列值) */
	Delay_MS(1) 	;				/* 延时 */
	temp1=P1;						/* 读列值(或行值) */
	P1=0xff;
	Delay_MS(1);				/* 延时 */
	P1=0x0f;						/* 输入列值(或行值) */
	Delay_MS(1) ;				/* 延时 */	
	temp2=P1;						/* 读行值(或列值) */
	P1=0xff;
	temp=(temp1&0xf0)|(temp2&0xf);	/* 将两次读入数据组合 */
	switch(temp)					/* 通过读入数据组合判断按键位置 */
	{		
		case 0xdd :a=20;break; // 	按键上
		case 0x7d :a=40;break;// 按键下
		case 0xbe :a=30;break;	//  按键左
		case 0xbb :a=10;break;	//  按键右
		default :a=0;
	}
	return a;						/* 返回按键值 */
}

void Exint_Init()
{
	IT0 = 1;
	EX0 = 1;
	EA = 1;
}

/*********外部中断0服务函数***********/
void Exint0_Service() interrupt 0
{
  Run=~Run;
}

uchar Key_Read()					//读取按键动作函数
{									//没有按键动作,则返回0,
	static uchar KeyEventCnt=0;		//1号按键动作,返回1-4
	static uchar KeySampleCnt=0;	//2号按键动作,返回5-8,如此类推
	static uchar KeyBuffer=0;		//返回1、5、9、13:确认短按按下
	uchar KeyTemp;					//返回2、6、10、14:确认长按按下
	KeyTemp=KeyBoard();				//返回3、7、11、15:确认短按松开
	switch(KeyEventCnt)				//返回4、8、12、16:确认长按松开
	{
		case 0:
			if(KeyTemp!=0)
			{
				KeySampleCnt=0;
				KeyBuffer=KeyTemp;
				KeyEventCnt=1;     
			}
			return 0;				//没有按下,return 0
			break;
		case 1:
			if(KeyTemp!=KeyBuffer)
			{
				KeyEventCnt=0;
				return 0;			//抖动,return 0
			}
			else
			{
				if(++KeySampleCnt>=SHORT_ON_DITHERING_COUNTER)
				{
					KeySampleCnt=0;
					KeyEventCnt=2;
					return KeyBuffer;
				}
				else
					return 0;		//不确定按下,return 0                 
			}
			break;
				
		case 2:
			if(KeyTemp!=KeyBuffer)
			{
				if(++KeySampleCnt>=SHORT_OFF_DITHERING_COUNTER)
				{
					KeyEventCnt=0;
					return KeyBuffer+3;
				}
				else
					return 0;
			}
			else
			{
				KeySampleCnt=0;
				return 0;
			}
			break;
		default:break;
	}
	return 0;
}
void Lcd_Show_Board()				//LCD显示墙壁函数
{
	uchar n;
	for(n=0;n<31;n++)
	{
		Lcd_Rectangle(3*n,0,3*n+2,2,1);
		Lcd_Rectangle(3*n,60,3*n+2,62,1);
	}
	for(n=0;n<21;n++)
	{
		Lcd_Rectangle(0,3*n,2,3*n+2,1);
		Lcd_Rectangle(90,3*n,92,3*n+2,1);		
	}
}
void Lcd_Show_Score()				//液晶显示分数函数
{
	uchar Str[3];
	Lcd_Show_String(6,1,"分");		//显示汉字
	Lcd_Show_String(6,2,"数");
	Str[0]=(Score/10)|0x30;			//十位
	Str[1]=(Score%10)|0x30;			//个位
	Str[2]=0;
	Lcd_Show_String(6,3,Str); 		//液晶显示分数
}
void Lcd_Show_Title()				//液晶显示贪吃蛇汉字
{
	Lcd_Show_String(7,0,"贪"); 		//显示汉字
	Lcd_Show_String(7,1,"吃");
	Lcd_Show_String(7,2,"蛇");
}
void Game_Over()					//游戏结束处理
{
	uchar n;
	Lcd_Rectangle(food.x,food.y,food.x+2,food.y+2,0);	//消隐出食物
	for(n=1;n<snake.node;n++)
		Lcd_Rectangle(snake.x[n],snake.y[n],snake.x[n]+2,snake.y[n]+2,0);//消隐食物,蛇头已到墙壁内,故不用消去		
	if(snake.life==0)				//如果蛇还活着
		Lcd_Show_String(2,1,"过关");
	else             				//如果蛇死了
		Lcd_Show_String(2,1,"输了");
	Lcd_Show_String(1,2,"游戏结束");
}
void Game_Play()					//游戏的具体过程,也是贪吃蛇算法的关键部分
{
	uchar n;
	InitRandom(TL0);
	food.yes=1;						//1表示需要出现新事物,0表示已经存在食物尚未吃掉
	snake.life=0;					//表示蛇还活着
	snake.direction=DOWN;
	snake.x[0]=6;snake.y[0]=6;
	snake.x[1]=3;snake.y[1]=6;
	snake.node=2;
	Lcd_Show_Score();				//显示分数
	Lcd_Show_Title();			   	//显示标题
	while(1)
	{
		if(food.yes==1)
		{
			while(1)
			{
				food.x=Random()*85+3;
				food.y=Random()*55+3;		//获得随机数
	
				while(food.x%3!=0)
					food.x++;
				while(food.y%3!=0)
					food.y++;
			    for(n=0;n<snake.node;n++)	//判断产生的食物坐标是否和蛇身重合
				{
					if((food.x==snake.x[n])&&(food.y==snake.y[n]))
						break;
				}
				if(n==snake.node)
				{
					food.yes=0;
					break;			//产生有效的食物坐标
				}
			}
		}
		if(food.yes==0)
		{
			Lcd_Rectangle(food.x,food.y,food.x+2,food.y+2,1);
		}	
		if(Run==0)
		{
			for(n=snake.node-1;n>0;n--)
			{
				snake.x[n]=snake.x[n-1];
				snake.y[n]=snake.y[n-1];
			}	
			switch(snake.direction)
			{
				case DOWN:snake.x[0]+=3;break;
				case UP:snake.x[0]-=3;break;
				case RIGHT:snake.y[0]-=3;break;
				case LEFT:snake.y[0]+=3;break;
				default:break;
			}
			for(n=3;n<snake.node;n++)	//从第三节开始判断蛇头是否咬到自己
			{
				if(snake.x[n]==snake.x[0]&&snake.y[n]==snake.y[0])
				{
					Game_Over();
					snake.life=1;
					break;
				}
			}
		}
		if(snake.x[0]<3||snake.x[0]>=90||snake.y[0]<3||snake.y[0]>=60)//判蛇头是否撞到墙壁
		{
			Game_Over();
			snake.life=1;
		}
		if(snake.life==1)
			break;					//蛇死,则跳出while(1)循环
		if(snake.x[0]==food.x&&snake.y[0]==food.y)	//判蛇是否吃到食物
		{
			Lcd_Rectangle(food.x,food.y,food.x+2,food.y+2,0);	//消隐食物
			snake.x[snake.node]=200;
			snake.y[snake.node]=200;//产生蛇新的节坐标先放在看不见的位置
			snake.node++;			//蛇节数加1
			food.yes=1;				//食物标志置1
			if(++Score>=PASSSCORE)
			{
				Lcd_Show_Score();
				Game_Over();
				break;
			}
			Lcd_Show_Score();
		}
		for(n=0;n<snake.node;n++)
		{
			Lcd_Rectangle(snake.x[n],snake.y[n],snake.x[n]+2,snake.y[n]+2,1);
		}							//根据蛇的节数画出蛇
		Delay(Speed*1000);
		Lcd_Rectangle(snake.x[snake.node-1],snake.y[snake.node-1],snake.x[snake.node-1]+2,snake.y[snake.node-1]+2,0);
		switch(KeyBuffer)
		{
			case FUNC:
					KeyBuffer=0;
					if(++Speed>=10)
						Speed=1;
				   	Lcd_Show_Title();
					break;
			case DOWN:				//下
					KeyBuffer=0;
					if(snake.direction!=UP)
						snake.direction=DOWN;
					break;
			case UP:	  			//上
					KeyBuffer=0;
					if(snake.direction!=DOWN)
						snake.direction=UP;
					break;
			case RIGHT:		  		//右
					KeyBuffer=0;
					if(snake.direction!=LEFT)
						snake.direction=RIGHT;
					break;
		   	case LEFT:				//左
					KeyBuffer=0;
					if(snake.direction!=RIGHT)
						snake.direction=LEFT;
					break;
			default:
					break;
		}			
	}
}
void Main()							//主函数
{  
start:
	Score=0;
	Timer_Init();					//定时器初始化
	Exint_Init();
	Lcd_Init(); 					//液晶初始化
	Lcd_Clear(0);					//液晶清屏
	Lcd_Show_Board();				//液晶显示墙壁
	Game_Play();					//玩游戏
	Game_Over();					//游戏结束	
	Delay_MS(4000);
	goto start;
}
void Timer0_ISR() interrupt 1		//定时器0中断处理函数
{
	switch(Key_Read())
	{
		case 90:
			KeyBuffer=FUNC;
			if(++Speed>=10)
				Speed=1;
			Flag|=1<<1;				//速度变化标志置1
			break;
		case 13:
			KeyBuffer=DOWN;
			break;
		case 33:
			KeyBuffer=UP;
			break;
		case 23:
			KeyBuffer=RIGHT;
			break;
	   	case 43:
			KeyBuffer=LEFT;
			break;
		default:
			break;
	}
}

仿真源文件、源程序及设计报告百度网盘链接:https://pan.baidu.com/s/1C4da0C0fJHio73F4eFVChg?pwd=q1ef 提取码: q1ef

相关推荐
jacklood1 小时前
基于STM32与中航ZH-E3L字符卡通信在LED屏显示数据
stm32·单片机·嵌入式硬件
秋说2 小时前
【PTA数据结构 | C语言版】二叉树层序序列化
c语言·数据结构·算法
wind_one12 小时前
STM32小实验三--让蜂鸣器响起来
stm32·单片机·嵌入式硬件
小憩-2 小时前
【stm32】新建工程
stm32·单片机·嵌入式硬件
erdongchen2 小时前
分支和循环语句 (1 / 2)
c语言
wind_one12 小时前
STM32小实验二--流水灯
stm32·单片机·嵌入式硬件
小严家2 小时前
《硬件产品经理》第七章:产品开发流程之验证
嵌入式硬件·产品经理·智能硬件
拼好饭玩家3 小时前
定时器更新中断与串口中断
单片机·嵌入式硬件
lzb_kkk5 小时前
【C++】多线程同步三剑客介绍
c语言·c++·条件变量·互斥锁·信号量
耳总是一颗苹果5 小时前
C语言---自定义类型(下)(枚举和联合类型)
c语言·开发语言