基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》

系列文章目录


前言

B站视频简介区下载链接中增加了改进版。游戏原理和逻辑还是一样的,增删了一些函数,修改了一些注释,修改了一些变量名,等等。将按键检测改成了全功能检测(能检测按住、按下、松开、单击、双击、长按、重复),只要硬件没问题,按键检测就准确、灵敏。

《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。

以《贪吃蛇》为载体,熟悉各种屏幕的使用。

所用单片机:STC89C52RC

有两个版本:普中开发板矩阵按键版本和最小系统板自制独立按键版本。

本文代码对应的是最小系统板自制独立按键版本。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

(1)普中开发板矩阵按键版本
在这里插入图片描述

(2)最小系统板自制独立按键版本
在这里插入图片描述

二、原理分析

游戏原理可以参考以下这篇文章:
基于51单片机和8X8LED点阵屏(板载74HC595驱动)的普中开发板矩阵按键控制的小游戏《贪吃蛇》

这里主要说一下显示问题。

此16X16点阵屏模块采用2个74HC138和2个74HC595来驱动显示。74HC138选通某一行,送入2个字节数据到2个74HC595来控制这一行的显示。

芯片MAX7219内含自动扫描电路,驱动的点阵屏不会占用单片机的资源。

此点阵屏模块需要利用单片机的定时器来进行扫描显示,定时的时间太短,会频繁进中断函数,影响主函数的代码的执行,定时的时间太长,点阵屏又会出现闪烁的现象(总共有16行,每个LED灯接通的占空比为1/16)。所以需要找到一个平衡,既不会使点阵屏出现明显的闪烁,又不会影响主函数代码的执行。经过摸索,这个定时的时间大约是1ms多,效果不好的话可以继续调一下。

三、各模块代码

1、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

#include <REGX52.H>

/**
  * @brief	定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)
{
	TMOD&=0xF0;	//设置定时器模式(高四位不变,低四位清零)
	TMOD|=0x01;	//设置定时器模式(通过低四位设为“定时器0工作方式1”的模式)
	TL0=0x18;	//设置定时初值,定时1ms
	TH0=0xFC;	//设置定时初值,定时1ms
	TF0=0;	//清除TF0标志
	TR0=1;	//定时器0开始计时
	ET0=1;	//打开定时器0中断允许
	EA=1;	//打开总中断
	PT0=0;	//当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned int T0Count;	//定义静态变量
	TL0=0x18;	//设置定时初值,定时1ms
	TH0=0xFC;	//设置定时初值,定时1ms
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

2、自制八位独立按键

h文件

#ifndef __KEYSCAN_8_H__
#define __KEYSCAN_8_H__

unsigned char Key(void);
void Key_Tick(void);

#endif

c文件

#include <REGX52.H>

sbit Key1=P1^0;
sbit Key2=P1^1;
sbit Key3=P1^2;
sbit Key4=P1^3;
sbit Key5=P1^4;
sbit Key6=P1^5;
sbit Key7=P1^6;
sbit Key8=P1^7;

unsigned char KeyNumber;

/**
  * @brief  获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~24,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char KeyTemp=0;
	KeyTemp=KeyNumber;
	KeyNumber=0;	//主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0
	return KeyTemp;
}

/**
  * @brief  获取当前按下按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按键值,范围:0~8,无按键按下时返回值为0
  */
unsigned char Key_GetState()
{
	unsigned char KeyValue=0;
	
	if(Key1==0){KeyValue=1;}
	if(Key2==0){KeyValue=2;}
	if(Key3==0){KeyValue=3;}
	if(Key4==0){KeyValue=4;}
	if(Key5==0){KeyValue=5;}
	if(Key6==0){KeyValue=6;}
	if(Key7==0){KeyValue=7;}
	if(Key8==0){KeyValue=8;}
	
	return KeyValue;
}


/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Tick(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;	//按键状态更新
	NowState=Key_GetState();	//获取当前按键状态
	
	//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间
	if(LastState==0)
	{
		switch(NowState)
		{
			case 1:KeyNumber=1;break;
			case 2:KeyNumber=2;break;
			case 3:KeyNumber=3;break;
			case 4:KeyNumber=4;break;
			case 5:KeyNumber=5;break;
			case 6:KeyNumber=6;break;
			case 7:KeyNumber=7;break;
			case 8:KeyNumber=8;break;
			default:break;
		}
	}
	
	//如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键
	if(LastState && NowState)
	{
		if(LastState==1 && NowState==1){KeyNumber=9;}
		if(LastState==2 && NowState==2){KeyNumber=10;}
		if(LastState==3 && NowState==3){KeyNumber=11;}
		if(LastState==4 && NowState==4){KeyNumber=12;}
		if(LastState==5 && NowState==5){KeyNumber=13;}
		if(LastState==6 && NowState==6){KeyNumber=14;}
		if(LastState==7 && NowState==7){KeyNumber=15;}
		if(LastState==8 && NowState==8){KeyNumber=16;}
	}

	
	//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间
	if(NowState==0)
	{
		switch(LastState)
		{
			case 1:KeyNumber=17;break;
			case 2:KeyNumber=18;break;
			case 3:KeyNumber=19;break;
			case 4:KeyNumber=20;break;
			case 5:KeyNumber=21;break;
			case 6:KeyNumber=22;break;
			case 7:KeyNumber=23;break;
			case 8:KeyNumber=24;break;
			default:break;
		}
	}
}

3、点阵屏模块

h文件

#ifndef __MATRIXLED_16X16_74HC138_74HC595_H__
#define __MATRIXLED_16X16_74HC138_74HC595_H__

void MatrixLED_Init(void);
void _74HC595_WriteByte(unsigned char Byte);
void _74HC595_StoreData(void);
void _74HC138_PickLine(unsigned char Line);
void MatrixLED_Tick(unsigned char Line,unsigned char State);
void MatrixLED_MoveLeft(unsigned char *Array,unsigned char Offset);
void MatrixLED_MoveUp(unsigned char *Array,unsigned char Offset);

#endif

c文件

#include <REGX52.H>

//引脚定义
sbit _74HC138_D=P2^7;	//高位
sbit _74HC138_C=P2^6;
sbit _74HC138_B=P2^5;
sbit _74HC138_A=P2^4;	//低位
sbit _74HC138_G=P2^3;	//使能端,低电平有效
sbit _74HC595_DI=P2^2;	//串行数据输入
sbit _74HC595_CLK=P2^1;	//移位寄存器时钟输入,上升沿有效
sbit _74HC595_LAT=P2^0;	//储存寄存器时钟输入,上升沿有效

//取模设置要求:阳码(亮点为0),行列式取模,高位在右
unsigned char MatrixLEDBuffer[32]={	//MatrixLED缓存
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,	//默认无显示
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};

/**
  * @brief	MatrixLED初始化
  * @param	无
  * @retval	无
  */
void MatrixLED_Init(void)
{
	_74HC595_CLK=0;	//移位寄存器时钟信号初始化
	_74HC595_LAT=0;	//储存寄存器时钟信号初始化
}

/**
  * @brief	74HC595写入字节
  * @param	Byte 需要写入的字节
  * @retval	无
  */
void _74HC595_WriteByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)	//循环8次(8位移位寄存器)
	{
		_74HC595_DI=Byte&(0x80>>i);	//先高位后低位,先低位再高位也行,不过取模设置要更改,改成高位在右
		_74HC595_CLK=1;	//CLK上升沿时,DI口的数据(电平)写入移位寄存器
		_74HC595_CLK=0;
	}
}

/**
  * @brief	74HC595存储数据
  * @param	无
  * @retval	无
  */
void _74HC595_StoreData(void)
{
	_74HC595_LAT=1;	//LAT上升沿时,数据从移位寄存器转存储存寄存器
	_74HC595_LAT=0;
}

/**
  * @brief	74HC138扫描显示MatrixLED
  * @param	Line 要显示的行,范围:0~15(对应1~16行)
  * @retval	无
  */
void _74HC138_PickLine(unsigned char Line)
{
	switch(Line)
	{
		case 0:_74HC138_D=0;_74HC138_C=0;_74HC138_B=0;_74HC138_A=0;break;
		case 1:_74HC138_D=0;_74HC138_C=0;_74HC138_B=0;_74HC138_A=1;break;
		case 2:_74HC138_D=0;_74HC138_C=0;_74HC138_B=1;_74HC138_A=0;break;
		case 3:_74HC138_D=0;_74HC138_C=0;_74HC138_B=1;_74HC138_A=1;break;
		case 4:_74HC138_D=0;_74HC138_C=1;_74HC138_B=0;_74HC138_A=0;break;
		case 5:_74HC138_D=0;_74HC138_C=1;_74HC138_B=0;_74HC138_A=1;break;
		case 6:_74HC138_D=0;_74HC138_C=1;_74HC138_B=1;_74HC138_A=0;break;
		case 7:_74HC138_D=0;_74HC138_C=1;_74HC138_B=1;_74HC138_A=1;break;
		case 8:_74HC138_D=1;_74HC138_C=0;_74HC138_B=0;_74HC138_A=0;break;
		case 9:_74HC138_D=1;_74HC138_C=0;_74HC138_B=0;_74HC138_A=1;break;
		case 10:_74HC138_D=1;_74HC138_C=0;_74HC138_B=1;_74HC138_A=0;break;
		case 11:_74HC138_D=1;_74HC138_C=0;_74HC138_B=1;_74HC138_A=1;break;
		case 12:_74HC138_D=1;_74HC138_C=1;_74HC138_B=0;_74HC138_A=0;break;
		case 13:_74HC138_D=1;_74HC138_C=1;_74HC138_B=0;_74HC138_A=1;break;
		case 14:_74HC138_D=1;_74HC138_C=1;_74HC138_B=1;_74HC138_A=0;break;
		case 15:_74HC138_D=1;_74HC138_C=1;_74HC138_B=1;_74HC138_A=1;break;
		default:break;
	}
}

/**
  * @brief  点阵屏驱动函数,定时器中断函数中使用
  * @param  Line 要显示的行,范围:0~15(对应1~16行)
  * @param  State 控制屏幕显不显示,范围:0~1,0:屏幕不显示,1:屏幕显示
  * @retval 无
  */
void MatrixLED_Tick(unsigned char Line,unsigned char State)
{
	_74HC138_G=1;
	if(State==1)
	{
		_74HC595_WriteByte(MatrixLEDBuffer[16+Line]);
		_74HC595_WriteByte(MatrixLEDBuffer[Line]);
	}
	else
	{
		_74HC595_WriteByte(0xFF);
		_74HC595_WriteByte(0xFF);
	}
	_74HC595_StoreData();
	_74HC138_PickLine(Line);
	_74HC138_G=0;
}

/**
  * @brief  更新点阵屏的显示
  * @param  Array 传递过来的数组的指针(地址),16*16汉字或16*16数字取模要求:阳码(亮点为0),行列式取模,高位在右
  * @param  Offset 显示数组数据的偏移量,向左偏移Offset个像素
  * @retval 无
  */
void MatrixLED_MoveLeft(unsigned char *Array,unsigned char Offset)
{
    unsigned char i,m,n;
	m=Offset/8;
	n=Offset%8;
	Array+=m*16;
	for(i=0;i<32;i++)
	{
		MatrixLEDBuffer[i]=(*Array>>n)|(*(Array+16)<<(8-n));	//将偏移后的数据保存到点阵屏缓存中(高位在右)
		Array++;
	}
}

/**
  * @brief  更新点阵屏的显示
  * @param  Array 传递过来的数组的指针(地址),16*16汉字或16*16数字取模要求:阳码(亮点为0),行列式取模,高位在右
  * @param  Offset 显示数组数据的偏移量,向上偏移Offset个像素
  * @retval 无
  */
void MatrixLED_MoveUp(unsigned char *Array,unsigned char Offset)
{
    unsigned char i,m,n;
	m=Offset/16;
	n=Offset%16;
	Array+=m*32;
	Array+=n;
	for(i=0;i<16;i++)
	{
		if(i<16-n)
		{
			MatrixLEDBuffer[i]=*(Array);
			MatrixLEDBuffer[i+16]=*(Array+16);
		}
		else
		{
			MatrixLEDBuffer[i]=*(Array+16);
			MatrixLEDBuffer[i+16]=*(Array+32);
		}
		Array++;
	}
}

四、主函数

main.c

/*
by甘腾胜@20241219
效果查看/操作演示:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:12T@12.0000MHz
外设:自制独立按键、16X16LED点阵屏(74HC138和74HC595驱动)
原理分析:https://blog.youkuaiyun.com/gantengsheng/article/details/143581157
注意:经过测试,接P0口会导致显示不正常,原因可能是P0口的上拉电阻太大,导致上拉的速度太慢,所以接了P2口

操作说明:

(1)自制独立按键版本

		K7				K2				上:K7
                                        下:K6
	K8		K5		K4		K1          左:K8
                                        右:K5
		K6				K3              开始/暂停/继续:K1
                                        返回:K2

(2)普中开发板矩阵按键版本

	S1		S2		S3		S4			上:S10				
										下:S14		
	S5		S6		S7		S8      	左:S13
										右:S15
	S9		S10		S11		S12     	开始/暂停/继续:S16
										返回:S12
	S13		S14		S15		S16     

*/

#include <REGX52.H>	//包含寄存器的定义
#include <STDLIB.H>	//包含随机函数的声明
#include "Timer0.h"	//包含工程目录下的头文件,相当于把头文件内容插入此处
#include "KeyScan_8.h"
#include "MatrixLED_16X16_74HC138_74HC595.h"

unsigned char KeyNum;	//存储获得的键码值
unsigned char Mode;	//游戏模式,0:显示游戏名称“《贪吃蛇》”,1:显示汉字“难度选择”,
					//2:难度选择界面(数字范围是1~5),3:游戏进行模式,4:游戏结束全屏闪烁,
					//5:显示汉字“得分”,6:循环滚动显示得分,7:显示作者姓名和编程日期
unsigned char MoveSnakeFlag;	//移动蛇身的标志,1:移动,0:不移动
unsigned char NowDirection=1;	//蛇头移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动
unsigned char LastDirection=1;	//蛇头上一次移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动(此处可以不赋初值)
unsigned char Length=2;	//蛇的长度,初始值为2(此处可以不赋初值,因为进入游戏进行模式时还会赋值一次)
unsigned char Head=1;	//保存整条蛇的数据的数组(共256个数据,数据索引为:0~255)中,蛇头对应的数据的索引,蛇的初始长度为2,
							//开始时只用了两个数据(数组的第1个数据和第2个数据),蛇头对应的是第2个数据(索引为1),Head的范围:0~255
unsigned char GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char FlashFlag;	//闪烁的标志,1:不显示,0:显示
unsigned int Food;	//保存创造出来的食物的位置,高四位(范围:0~15)对应列(1~16列),低四位(范围:0~15)对应行(1~16行)
					//从左往右数,分别是1~16列,从上往下数,分别是1~16行
unsigned int Offset1;	//偏移量,用来控制汉字或数字向左滚动显示(切换模式后清零)
unsigned int Offset2;	//偏移量,用来控制难度对应的数字上下滚动显示(切换模式后不清零)
unsigned char RollFlag;	//滚动的标志,1:滚动,0:不滚动
unsigned char RollUpFlag;	//难度选择界面,数字向上滚动的标志,1:滚动,0:不滚动
unsigned char RollDownFlag;	//难度选择界面,数字向下滚动的标志,1:滚动,0:不滚动
unsigned char RollCount;	//上下滚动的计次
unsigned char ExecuteOnceFlag;	//各模式中只执行一次的标志,1:执行,0:不执行
unsigned int SnakeMoveSpeed=1000;	//蛇移动的速度,值越小,速度越快,上电默认1.00s移动一次,定时器定时1ms
unsigned int T0Count0,T0Count1,T0Count2,T0Count3,T0Count4;	//定时器计数的变量
unsigned char PauseFlag;	//暂停的标志,1:暂停,0:不暂停

unsigned char pdata SnakeBody[256];	//点阵屏是16X16=256像素,需要用256个数据记录蛇身的数据
									//用pdata修饰是因为片内RAM不够用,所以保存到片外RAM
unsigned char DisplayBuffer[32];	//显示缓存,一个字节对应8个点,总共256个点,所以需要32个字节
									//阳码(亮点为0),行列式取模,高位在右

//取模设置:阳码(亮点为0),行列式取模,高位在右(所有取模都必须按这个格式,否则需要修改函数)
unsigned char code Table1[]={	// “《贪吃蛇》”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
0xFF,0xFF,0xFF,0xFF,0x7F,0xBF,0xDF,0x6F,0xDF,0xBF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,
0xF7,0xDB,0xED,0xF6,0xFB,0xFD,0xFE,0xFF,0xFE,0xFD,0xFB,0xF6,0xED,0xDB,0xF7,0xFF,/*"《",0*/
0x7F,0xBF,0xDF,0x6F,0xF3,0x0C,0xFF,0xFF,0x07,0xF7,0x77,0x77,0x77,0xBF,0xCF,0xF1,
0xFF,0xFE,0xFD,0xFB,0xE6,0x98,0xFD,0xFE,0xF0,0xF7,0xF7,0xF7,0xF7,0xF9,0xE7,0xDF,/*"贪",1*/
0xFF,0xFF,0x61,0x6D,0xAD,0xCD,0x6D,0xED,0xED,0xED,0xE1,0x6D,0xBF,0xBF,0x7F,0xFF,
0xFE,0xFE,0xFF,0x80,0xFF,0xFF,0xE0,0xEF,0xF7,0xF9,0xFE,0xFF,0xBF,0xBF,0x80,0xFF,/*"吃",2*/
0xF7,0xF7,0xF7,0x41,0x55,0x95,0xD5,0xD5,0xC1,0xF5,0xF7,0xD7,0x87,0xB8,0xFD,0xFF,
0xFB,0xF7,0xF7,0x80,0xBF,0xDF,0xFE,0xEE,0xF6,0xFA,0xFC,0xBE,0xBE,0xBE,0x81,0xFF,/*"蛇",3*/
0xEF,0xDB,0xB7,0x6F,0xDF,0xBF,0x7F,0xFF,0x7F,0xBF,0xDF,0x6F,0xB7,0xDB,0xEF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFE,0xFD,0xFB,0xF6,0xFB,0xFD,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,/*"》",4*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
};

unsigned char code Table2[]={	// “ 难度选择 1”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
0xFF,0xFF,0xC0,0x5F,0x5F,0x2D,0x4B,0x6B,0x77,0x77,0x6B,0x5B,0x5D,0x7E,0x7F,0x7F,
0xFA,0xF6,0xFE,0x80,0xF7,0xF7,0xC0,0xF7,0xF7,0xC0,0xF7,0xF7,0xF7,0x80,0xFF,0xFF,/*"难",0*/
0x7F,0xFF,0x03,0xBB,0xBB,0x03,0xBB,0xBB,0x3B,0xFB,0x0B,0xDB,0xBD,0x7D,0x9E,0xE3,
0xFF,0xFE,0x80,0xFB,0xFB,0xC0,0xFB,0xFB,0xF8,0xFF,0xF0,0xF7,0xFB,0xFC,0xF3,0x8F,/*"度",1*/
0xFF,0xBB,0xB7,0x37,0xDF,0xFF,0x10,0x77,0x77,0x77,0xB7,0xB7,0xD7,0xEB,0x1D,0xFF,
0xFD,0xFD,0xFD,0xE0,0xFD,0xFD,0xC0,0xFB,0xFB,0xFB,0xDB,0xDB,0xC7,0xFF,0x80,0xFF,/*"选",2*/
0xFB,0x1B,0xBB,0x7B,0xF0,0xFB,0x7B,0x9B,0xF3,0x38,0xFB,0xFB,0x1B,0xFB,0xFA,0xFD,
0xFF,0xE0,0xEF,0xF7,0xFA,0xFD,0xF2,0x8D,0xFD,0xE0,0xFD,0xFD,0xC0,0xFD,0xFD,0xFD,/*"择",3*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
};

unsigned char code Table3[]={	//难度对应的数字:“1~5”,最后两行数据与前两行数据相同,是为了做成循环显示的效果

0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
0xFF,0xFF,0xFF,0x0F,0xF3,0xE3,0xF7,0xFF,0xFF,0x7F,0x9F,0xEF,0xF3,0x03,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF0,0xE7,0xCF,0xE7,0xE7,0xF9,0xFE,0xFF,0xDF,0xCF,0xE0,0xFF,0xFF,/*"2",1*/
0xFF,0xFF,0xFF,0x0F,0xF3,0xE3,0xFF,0xFF,0x7F,0xFF,0xFF,0xE3,0xF3,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF8,0xE7,0xE7,0xE7,0xF9,0xF0,0xE7,0xCF,0xCF,0xE7,0xF8,0xFF,0xFF,/*"3",2*/
0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xBF,0xCF,0xF7,0xFB,0x01,0xFF,0xFF,0x3F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF3,0xF0,0xF1,0xF1,0xF1,0xF1,0xF1,0x80,0xF1,0xF1,0x80,0xFF,0xFF,/*"4",3*/
0xFF,0xFF,0xFF,0x07,0xF7,0xF7,0xF7,0x17,0xE7,0xFF,0xFF,0xE3,0xF3,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xE0,0xFF,0xFF,0xFF,0xF0,0xE7,0xCF,0xCF,0xCF,0xE7,0xF8,0xFF,0xFF,/*"5",4*/
0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
};

unsigned char code Table5[]={	// “得分”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
0xEF,0x2F,0xB7,0x3B,0xAD,0x2F,0xF7,0x33,0xF5,0x16,0xF7,0xB7,0x77,0xF7,0xF7,0xF7,
0xFF,0xE0,0xEF,0xE0,0xEF,0xE0,0xFF,0xC0,0xF7,0x80,0xF7,0xF7,0xF7,0xF7,0xF5,0xFB,/*"得",0*/
0xFF,0xDF,0xDF,0xEF,0xF7,0xFB,0xFD,0x06,0xDF,0xDF,0xDF,0xEF,0xEF,0xF7,0x7B,0xFD,
0xFD,0xFD,0xFB,0xFB,0xF7,0xEF,0xDF,0xB8,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFD,0xFE,/*"分",1*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
};

unsigned char code Table6[]={	//分数字模:0~9,大小:宽8*高16
0xFF,0xFF,0xFF,0xE7,0xDB,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xDB,0xE7,0xFF,0xFF,/*"0",0*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",1*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",2*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBF,0xDF,0xE7,0xDF,0xBF,0xBD,0xBD,0xC3,0xFF,0xFF,/*"3",3*/
0xFF,0xFF,0xFF,0xDF,0xCF,0xCF,0xD7,0xDB,0xDB,0xDD,0x01,0xDF,0xDF,0x07,0xFF,0xFF,/*"4",4*/
0xFF,0xFF,0xFF,0x81,0xFD,0xFD,0xFD,0xE1,0xDD,0xBF,0xBF,0xBD,0xDD,0xE3,0xFF,0xFF,/*"5",5*/
0xFF,0xFF,0xFF,0xE7,0xDB,0xFD,0xFD,0xC5,0xB9,0xBD,0xBD,0xBD,0xBB,0xC7,0xFF,0xFF,/*"6",6*/
0xFF,0xFF,0xFF,0x81,0xBD,0xDF,0xDF,0xEF,0xEF,0xF7,0xF7,0xF7,0xF7,0xF7,0xFF,0xFF,/*"7",7*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xDB,0xE7,0xDB,0xBD,0xBD,0xBD,0xC3,0xFF,0xFF,/*"8",8*/
0xFF,0xFF,0xFF,0xE3,0xDD,0xBD,0xBD,0xBD,0x9D,0xA3,0xBF,0xBF,0xDB,0xE7,0xFF,0xFF,/*"9",9*/
};

unsigned char code Table7[]={	// “ by甘腾胜at20241202 ”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
0xFF,0xFF,0xFF,0xFF,0xFC,0xFD,0xFD,0xE5,0xD9,0xBD,0xBD,0xBD,0xD9,0xE5,0xFF,0xFF,/*"b",0*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x18,0xBD,0xDB,0xDB,0xE7,0xE7,0xF7,0xF7,0xF9,/*"y",1*/
0xEF,0xEF,0xEF,0xEF,0x01,0xEF,0xEF,0xEF,0xEF,0x0F,0xEF,0xEF,0xEF,0xEF,0x0F,0xEF,
0xF7,0xF7,0xF7,0xF7,0x80,0xF7,0xF7,0xF7,0xF7,0xF0,0xF7,0xF7,0xF7,0xF7,0xF0,0xF7,/*"甘",2*/
0xBF,0x61,0x6D,0x0D,0xED,0x01,0x6D,0xAD,0x4D,0xE1,0x6D,0x6D,0xED,0x2D,0xED,0xE6,
0xED,0xED,0xF5,0xC0,0xFD,0x80,0xF7,0xEF,0x90,0xF7,0xF7,0xC0,0xDF,0xD8,0xD7,0xEF,/*"腾",3*/
0xFF,0xE1,0x6D,0x6D,0x6D,0x61,0xAD,0xED,0xED,0x61,0xED,0xED,0xED,0xED,0x2D,0xE6,
0xFB,0xFB,0xFB,0xFB,0xC0,0xFB,0xFB,0xFB,0xFB,0xC0,0xFB,0xFB,0xFB,0xFB,0x80,0xFF,/*"胜",4*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0xDD,0xCF,0xD3,0xDD,0xCD,0x93,0xFF,0xFF,/*"a",5*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xF7,0xC1,0xF7,0xF7,0xF7,0xF7,0xB7,0xCF,0xFF,0xFF,/*"t",6*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",7*/
0xFF,0xFF,0xFF,0xE7,0xDB,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xDB,0xE7,0xFF,0xFF,/*"0",8*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",9*/
0xFF,0xFF,0xFF,0xDF,0xCF,0xCF,0xD7,0xDB,0xDB,0xDD,0x01,0xDF,0xDF,0x07,0xFF,0xFF,/*"4",10*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",11*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",12*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",13*/
0xFF,0xFF,0xFF,0xE3,0xDD,0xBD,0xBD,0xBD,0x9D,0xA3,0xBF,0xBF,0xDB,0xE7,0xFF,0xFF,/*"9",14*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*"  ", */
};

/**
  * @brief  创造出随机位置的食物,数据的高四位(范围:0~15)代表食物所在的列(1~16),数据的低四位(范围:0~15)代表食物所在的行(1~16)
  * @brief  从左往右数,分别是1~16列,从上往下数,分别是1~16行
  * @param  无
  * @retval 创造出的食物位置的数据
  */
unsigned char CreateFood(void)
{
	unsigned char FoodTemp;
	unsigned char i,j,m,n;
	m=rand()%16;	//产生一个0~15的随机数
	n=rand()%16;	//产生一个0~15的随机数
	for(j=0;j<16;j++)	//产生一个随机位置,判断该位置是否是蛇身,如果不是,就返回该位置所对应的数据
	{					//如果该位置是蛇身的位置,则从该点向周围寻找不是蛇身的空位置
		for(i=0;i<16;i++)
		{
			if( (DisplayBuffer[(n+j)%16+(m+i)%16/8*16] & (0x01<<(m+i)%8)) )
			{
				FoodTemp=(m+i)%16*16+(n+j)%16;
				break;	//找到了空位置就退出循环
			}
		}
	}
	return FoodTemp;
}

/**
  * @brief  更新显示缓存中的数据,例如:想要点亮第一行第十列的点,就需要让数组DisplayBuffer的
  * @brief  第17个数据(索引为16)的B1位(一个字节从最低位到最高位分别为B0~B7)变为0(阳码,亮点为0)
  * @brief  注意,更新了数组DisplayBuffer的数据后,还需要配合滚屏函数MatrixLED_MoveLeft更新屏幕的显示
  * @param  Position,要更新的位置,范围:0~255,高四位(0~15)对应1~16列(从左往右数,分别是1~16列),
  * @param                                       低四位(0~15)对应1~16行(从上往下数,分别是1~16行)
  * @param  State,要更新成的状态,范围:0~1,1:点亮,0:熄灭
  * @retval 无
  */
void ConvertData(unsigned char Position,unsigned char State)
{
	if(State==1)
	{
		DisplayBuffer[Position%16+Position/16/8*16] &= ~(0x01<<(Position/16%8));
	}
	else
	{
		DisplayBuffer[Position%16+Position/16/8*16] |= 0x01<<(Position/16%8);
	}
}


/**
  * @brief  控制蛇的移动
  * @param  无
  * @retval 无
  */
void MoveSnake(void)
{
	if(NowDirection==1)	//如果向右移动
	{
		//移动前判断一下移动后是否撞墙,如果是,则游戏结束,游戏结束的标志置1
		if((SnakeBody[Head]/16)==15){GameOverFlag=1;}
		
		//SnakeBody数组中蛇头的下一个数据等于上一个数据加16(即高四位加1),即蛇头移动到了右边这一列
		else{SnakeBody[(Head+1)%256]=SnakeBody[Head]+16;}
	}
	if(NowDirection==2)	//如果向上移动
	{
		if((SnakeBody[Head]%16)==0){GameOverFlag=1;}
		
		//SnakeBody数组中蛇头的下一个数据等于上一个数据减1(即低四位减1),即蛇头移动到了上边这一行
		else{SnakeBody[(Head+1)%256]=SnakeBody[Head]-1;}
	}
	if(NowDirection==3)	//如果向左移动
	{
		if((SnakeBody[Head]/16)==0){GameOverFlag=1;}
		
		//SnakeBody数组中蛇头的下一个数据等于上一个数据减16(即高四位减1),即蛇头移动到了左边这一列
		else{SnakeBody[(Head+1)%256]=SnakeBody[Head]-16;}
	}
	if(NowDirection==4)	//如果向下移动
	{
		if((SnakeBody[Head]%16)==15){GameOverFlag=1;}
		
		//SnakeBody数组中蛇头的下一个数据等于上一个数据加1(即低四位加1),即蛇头移动到了下边这一行
		else{SnakeBody[(Head+1)%256]=SnakeBody[Head]+1;}
	}
	
	if(GameOverFlag==0)	//如果没撞墙
	{
		if(SnakeBody[(Head+1)%256]==Food)	//判断蛇头移动后的位置是否是食物所在的位置
		{	//如果是
			Length++;	//蛇身长度加1
			ConvertData(Food,1);	//防止食物闪烁刚好不显示的时候进入此函数,导致蛇身一个点不显示
			Food=CreateFood();	//重新创造一个食物
			ConvertData(Food,1);	//先更新显示缓存,在主循环中再更新屏幕的显示
			FlashFlag=0;	//创造出新的食物时,食物暂不闪烁
			T0Count2=0;	//定时器T0Count2重新计数
		}
		else if( !( (DisplayBuffer[SnakeBody[(Head+1)%256]%16+SnakeBody[(Head+1)%256]/16/8*16]) 
			& (0x01<<SnakeBody[(Head+1)%256]/16%8) ) )
		{	//如果蛇头移动后的位置撞在蛇身上,则游戏结束
			GameOverFlag=1;	//游戏结束的标志置1
		}
		else	//如果蛇头移动后的位置不是食物,也不是撞墙,也不是撞到蛇身的话
		{
			//显示缓存数组DisplayBuffer中蛇头前进后蛇头的新位置对应的位更新数值
			ConvertData(SnakeBody[(Head+1)%256],1);
			
			//显示缓存数组DisplayBuffer中蛇尾的位置对应的位清0(蛇身移动,如果没有吃到食物,相当于蛇尾对应的点跑到了
			//蛇头的前一个点,变成了蛇头,原来的蛇头变成蛇身)整条蛇中间的数据不用操作
			ConvertData(SnakeBody[(Head+256-Length+1)%256],0);

			//数组SnakeBody中,蛇尾的数据清零,,
			//Head+256:+256是因为Head为255后再加1,就会变成了0,防止Head+256-Length+1为负数
			//循环使用SnakeBody数组中的256个数据,蛇头对应数组SnakeBody的第255个数据后,再移动一次,蛇头就来到了数组的第0个数据
//			SnakeBody[(Head+256-Length+1)%256]=0;	//其实可以不用清零
		}
	}
	
	Head++;	//SnakeBody数组中,蛇头对应的数据的索引加1
	Head%=256;	//蛇头变量Head是无符号字符型数据(范围是0~255,值为255后自增会自动变为0),所以这一行可以省略
					//如果Head是整型(int),则需要有这一行
}

void main()
{
	unsigned int i;
	unsigned char Count=0;	
	Timer0_Init();	//定时器初始化
	MatrixLED_Init();	//点阵屏初始化
	while(1)
	{
		KeyNum=Key();	//获取键码值

		if(KeyNum)	//如果有按键按下
		{
			srand(TL0);	//以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据
			
			if(Mode==7 && KeyNum==18)	//如果是显示作者姓名和编程日期的界面,且按下了返回键K2(松手瞬间)
			{
				Mode=2;	//返回难度选择界面
				ExecuteOnceFlag=1;	//各模式只执行一次代码的标志置1
			}

			if(Mode==6)	//如果是循环滚动显示得分的界面
			{
				if(KeyNum==18)	//如果按下了返回键K2(松手瞬间)
				{
					Mode=2;	//返回难度选择界面
					ExecuteOnceFlag=1;	//各模式只执行一次代码的标志置1
				}
				else if(KeyNum==19)	//如果按下了K3(松手瞬间)
				{
					Mode=7;	//切换到显示作者姓名和编程日期的界面
					Offset1=0;	//滚动显示的偏移量清0
				}
			}
			
			if(Mode==5 && KeyNum==17)	//如果是显示汉字“得分”的界面,且按下开始键K1(松手瞬间)
			{
				Mode=6;	//跳过显示汉字,切换到循环滚动显示得分的界面
				ExecuteOnceFlag=1;
				Offset1=0;	//滚动显示的偏移量清0
			}

			if(Mode==4 && KeyNum==17)	//如果游戏结束闪烁时按下开始键K1(松手瞬间)
			{
				Mode=5;	//切换到显示汉字“得分”的界面
				ExecuteOnceFlag=1;
				Offset1=0;
			}
			
			if(Mode==3)	//如果是游戏进行模式
			{
				if(KeyNum==17)	//按下K1暂停或继续(松手瞬间)
				{
					PauseFlag=!PauseFlag;
				}
				if(PauseFlag==0)	//如果不是暂停
				{	//按下瞬间、长按、松手瞬间都进行检测,这样控制方向更有效,防止按键没检测出来导致没能改变方向
					if((KeyNum==8 || KeyNum==16 || KeyNum==24) && LastDirection!=1)
					{	//如果按了“左”键,且蛇头原来的移动方向不是向右
						NowDirection=3;	//则方向蛇头方向改为向左
					}
					if((KeyNum==7 || KeyNum==15 || KeyNum==23) && LastDirection!=4)
					{	//如果按了“上”键,且蛇头原来的移动方向不是向下
						NowDirection=2;	//则方向蛇头方向改为向上
					}
					if((KeyNum==6 || KeyNum==14 || KeyNum==22) && LastDirection!=2)
					{	//如果按了“下”键,且蛇头原来的移动方向不是向上
						NowDirection=4;	//则方向蛇头方向改为向左
					}
					if((KeyNum==5 || KeyNum==13 || KeyNum==21) && LastDirection!=3)
					{	//如果按了“右”键,且蛇头原来的移动方向不是向左
						NowDirection=1;	//则方向蛇头方向改为向左
					}
				}
			}
			
			if(Mode==2)	//如果是难度选择界面
			{
				if(KeyNum==23)	//如果按了“上”键(松手瞬间)
				{
					RollUpFlag=1;	//数字向上滚动的标志置1
				}
				if(KeyNum==22)	//如果按了“下”键(松手瞬间)
				{
					RollDownFlag=1;	//数字向下滚动的标志置1
				}
				if(KeyNum==17)	//如果按了开始键(松手瞬间),则开始游戏
				{
					Mode=3;	//切换到游戏模式
					ExecuteOnceFlag=1;
				}
			}

			if(KeyNum<=24 && KeyNum>=17)	//如果按下任意按键(松手瞬间)
			{
				//两个if的顺序不能调换,如果调换了,就从模式0直接跳到模式2了
				if(Mode==1){Mode=2;Offset1=0;ExecuteOnceFlag=1;}	//跳过汉字“难度选择”的显示,切换到难度选择界面
				if(Mode==0){Mode=1;Offset1=0;ExecuteOnceFlag=1;}	//跳过游戏名“《贪吃蛇》”的显示,切换到汉字“难度选择”的显示界面
			}
		}

		if(Mode==0)	//如果是显示游戏名称“《贪吃蛇》”的模式
		{
			if(RollFlag)	//如果滚动的标志RollFlag为1(定时器中每隔一段时间将此标志置1)
			{
				RollFlag=0;	//滚动的标志RollFlag清零
				MatrixLED_MoveLeft(Table1,Offset1);	//向左滚动
				Offset1++;	//每次向左移动一个像素
				Offset1%=96;	//越界清零,循环滚动
			}
		}
		
		if(Mode==1)	//如果是显示汉字“难度选择”的模式
		{
			if(RollFlag && Offset1<=96)	//只向左滚动显示一次,不循环滚动显示
			{
				RollFlag=0;
				MatrixLED_MoveLeft(Table2,Offset1);
				Offset1++;
			}
			else if(Offset1>96)	//显示数字“1”之后,自动切换到难度选择模式
			{
				Mode=2;
				Offset1=0;
			}
		}
		
		if(Mode==2)	//如果是难度选择模式
		{
			if(ExecuteOnceFlag)	//切换到该模式后,此if中的内容只执行1次
			{
				ExecuteOnceFlag=0;
				MatrixLED_MoveUp(Table3,Offset2);	//显示难度对应的数字,范围:1~5
			}
			if(RollFlag && RollUpFlag)	//如果滚动标志为1,且向上滚动的标志也为1
			{
				RollFlag=0;	//定时器中每个50ms将RollFlag标志置1
				Offset2++;	//向上移动一个像素
				Offset2%=80;	//越界清零,总共5个数字,每个数字的高度是16,所以是5*16=80
				MatrixLED_MoveUp(Table3,Offset2);	//更新显示
				RollCount++;
				if(RollCount==16)	//移动了16个像素后停止移动
				{
					RollCount=0;
					RollUpFlag=0;
					Offset2=(Offset2/16)*16;	//防止移动到一半的时候按下“上”或“下”按键导致数字没有在点阵屏中间
												//Offset2的值必须是16的整数倍
					switch(Offset2/16)
					{
						case 0:SnakeMoveSpeed=1000;break;	//难度1,1.00s移动1次
						case 1:SnakeMoveSpeed=750;break;	//难度2,0.75s移动1次
						case 2:SnakeMoveSpeed=500;break;	//难度3,0.50s移动1次
						case 3:SnakeMoveSpeed=250;break;	//难度4,0.25s移动1次
						case 4:SnakeMoveSpeed=120;break;	//难度5,0.12s移动1次
						default:break;
					}
				}
			}
			if(RollFlag && RollDownFlag)	//如果滚动标志为1,且向下滚动的标志也为1
			{
				RollFlag=0;
				if(Offset2==0){Offset2=80;}
				Offset2--;
				MatrixLED_MoveUp(Table3,Offset2);
				RollCount++;
				if(RollCount==16)
				{
					RollCount=0;
					RollDownFlag=0;
					Offset2=(Offset2/16)*16;
					switch(Offset2/16)
					{
						case 0:SnakeMoveSpeed=1000;break;	//难度1,1.00s移动1次
						case 1:SnakeMoveSpeed=750;break;	//难度2,0.75s移动1次
						case 2:SnakeMoveSpeed=500;break;	//难度3,0.50s移动1次
						case 3:SnakeMoveSpeed=250;break;	//难度4,0.25s移动1次
						case 4:SnakeMoveSpeed=120;break;	//难度5,0.12s移动1次
						default:break;
					}
				}
			}
		}
		
		if(Mode==3)	//如果是游戏进行模式
		{
			if(ExecuteOnceFlag)	//切换到该模式后,此if中的内容只执行1次
			{	//开始游戏后,所有数据初始化
				ExecuteOnceFlag=0;
				GameOverFlag=0;	//游戏结束标志清零
				PauseFlag=0;	//游戏暂停标志清零
				NowDirection=1;	//蛇头默认向右移动
				LastDirection=1;	//上一次蛇头默认向右移动
				Length=2;	//蛇的初始长度为2
				Head=1;	//蛇头对应数组中的第2个数据(索引为1)
//				for(i=0;i<256;i++)	//蛇身数据全部清零(其实可以不用清零)
//				{
//					SnakeBody[i]=0;
//				}
				//写入蛇身初始的两个数据
				SnakeBody[0]=1*16+1;	//蛇尾位置:第二行第二列
				SnakeBody[1]=2*16+1;	//蛇头位置:第二行第三列
				for(i=0;i<32;i++)	//显示缓存数据全部清空(阳码:亮点为0,所以所有字节的每一位置1)
				{
					DisplayBuffer[i]=0xFF;
				}
				DisplayBuffer[1]=0xF9;	//显示缓存数据全部清空后,写入蛇身初始的两个数据(阳码:亮点为0)
				Food=CreateFood();	//进入游戏前,先创造出一个食物
				ConvertData(Food,1);	//更新显示缓存
				MatrixLED_MoveLeft(DisplayBuffer,0);	//屏幕显示初始的蛇身及食物
				MoveSnakeFlag=0;	//蛇移动的标志清零
				T0Count1=0;	//定时器计数变量T0Count1清零,重新计数
			}
			
			if(PauseFlag)	//如果暂停了
			{
				ConvertData(Food,1);	//食物不闪烁,一直显示
				MatrixLED_MoveLeft(DisplayBuffer,0);	//更新显示
			}			
			else if(FlashFlag)	//如果不暂停,且闪烁标志为1
			{
				ConvertData(Food,0);	//不显示食物
				MatrixLED_MoveLeft(DisplayBuffer,0);
			}
			else	//如果不暂停,且闪烁标志为0
			{
				ConvertData(Food,1);	//显示食物
				MatrixLED_MoveLeft(DisplayBuffer,0);
			}

			if(MoveSnakeFlag && GameOverFlag==0 && PauseFlag==0)
			{	//如果移动的标志为1,且不暂停,且游戏也没结束
				LastDirection=NowDirection;	//保存上一次移动的方向,用于按键的判断(蛇不能往后移动)
				MoveSnakeFlag=0;	//移动标志清零
				MoveSnake();	//移动一次
				MatrixLED_MoveLeft(DisplayBuffer,0);	//MoveSnake函数中更改显示缓存的数据,这里用移屏函数更新屏幕显示
			}
			if(GameOverFlag==1)	//如果游戏结束
			{
				ConvertData(Food,1);
				MatrixLED_MoveLeft(DisplayBuffer,0);
				Mode=4;	//切换到全屏闪烁模式
				ExecuteOnceFlag=1;
			}
		}
		
		if(Mode==4)	//如果是游戏结束全屏闪烁模式
		{
			//在中断中处理
		}
		
		if(Mode==5)	//显示汉字“得分”
		{
			if(RollFlag && Offset1<=48)	//只显示一次汉字“得分”
			{
				RollFlag=0;
				Count++;
				Count%=10;
				if(Count==0)
				{
					MatrixLED_MoveLeft(Table5,Offset1);
					Offset1+=16;
				}
			}
			else if(Offset1>48) //显示结束后,自动切换到循环显示得分的模式
			{
				Mode=6;
				Offset1=0;	//偏移量清零
				ExecuteOnceFlag=1;
			}	
		}
		
		if(Mode==6)	//如果是滚动显示得分模式
		{
			if(ExecuteOnceFlag)
			{
				ExecuteOnceFlag=0;
				for(i=0;i<112;i++)	//蛇身数据部分清零(SnakeBody前160个用来循环显示三位数得分)
				{
					SnakeBody[i]=0xFF;
				}
				
				//将得分(即蛇身的长度)的百位、十位、个位的字模写入数组SnakeBody中
				for(i=0;i<16;i++)
				{
					SnakeBody[32+i]=Table6[Length/100*16+i];
				}
				for(i=0;i<16;i++)
				{
					SnakeBody[48+i]=Table6[Length/10%10*16+i];
				}
				for(i=0;i<16;i++)
				{
					SnakeBody[64+i]=Table6[Length%10*16+i];
				}
				PauseFlag=0;
			}

			if(RollFlag)	//如果滚动的标志为1
			{
				RollFlag=0;
				MatrixLED_MoveLeft(SnakeBody,Offset1);
				Offset1++;
				Offset1%=40;	//循环滚动
			}
		}
		
		if(Mode==7)	//如果是显示作者姓名和编程时间的模式
		{
			if(RollFlag)	//如果滚动的标志为1
			{
				RollFlag=0;
				MatrixLED_MoveLeft(Table7,Offset1);
				Offset1++;
				Offset1%=160;	//循环滚动
			}
		}
	}
}

void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned char Line;
	TL0=0x18;	//设置定时初值,定时1ms,晶振@12.0000MHz
	TH0=0xFC;	//设置定时初值,定时1ms,晶振@12.0000MHz
	T0Count3++;
	T0Count4++;
	if(T0Count0>=20)	//20ms扫描一次按键
	{
		T0Count0=0;
		Key_Tick();
	}
	T0Count0++;
	if(PauseFlag==0)	//不暂停时,T0Count1和T0Count2才计数
	{
		T0Count1++;
		T0Count2++;
	}
	if(T0Count1>=SnakeMoveSpeed)	//用来控制蛇移动的速度
	{
		T0Count1=0;
		MoveSnakeFlag=1;
	}
	if(T0Count2>=250)	//0.25s取反闪烁标志FlashFlag的值
	{
		T0Count2=0;
		FlashFlag=!FlashFlag;
	}
	if(T0Count3>=50)	//控制滚动的速度,50ms滚动一次
	{
		T0Count3=0;
		RollFlag=1;
	}
	if(T0Count4>=1)
	{
		T0Count4=0;
		if(Mode==4 && FlashFlag){MatrixLED_Tick(Line,0);}	//游戏结束,全屏闪烁
		else{MatrixLED_Tick(Line,1);}		
		Line++;
		if(Line>15){Line=0;}	//总共要扫描16行,Line的范围是:0~15
	}
}

总结

这个点阵屏模块比较占用单片机的资源,如果用速度较快的单片机则影响不大,如果是用89系列的单片机会感觉影响比较大,建议用MAX7219驱动的点阵屏,很好用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值