基于51单片机和8X8LED点阵屏(MAX7219驱动)的自制独立按键控制的小游戏《贪吃蛇》
51单片机贪吃蛇小游戏8X8LED点阵屏MAX7219驱动8位独立按键
目录
系列文章目录
前言
B站视频简介区下载链接中增加了改进版。游戏原理和逻辑还是一样的,增删了一些函数,修改了一些注释,修改了一些变量名,等等。将按键检测改成了全功能检测(能检测按住、按下、松开、单击、双击、长按、重复),只要硬件没问题,按键检测就准确、灵敏。
《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。
用到51单片机最小开发板,用8位自制的独立按键控制,单片机芯片为STC89C52RC,晶振@12.0000MHz。8X8LED点阵屏模块通过MAX7219驱动,MAX7219内含自动扫描电路,只需要将要显示的数据送进芯片的寄存器就行了,很好用,建议使用。
矩阵按键控制的可以看下面这个版本:
基于51单片机和8X8LED点阵屏(板载74HC595驱动)的普中开发板矩阵按键控制的小游戏《贪吃蛇》
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有下载链接。
一、效果展示


二、原理分析
1、矩阵键盘按键的检测
不能用延时来消抖,那样会阻塞程序运行。
用定时器定时,每隔20ms在中断函数中调用检测函数(以此跳过消抖),检测哪个按键按下,并判断短按长按还是松手,并将对应的键码值保存到一个变量中,再在主函数中获取键码值来控制游戏的操作。游戏中短按和长按方向键都让蛇头改变方向,这样控制才会更加准确。
2、蛇身数据的保存
用一个字节保存蛇身一个点所在的位置,十位数(1~8)表示所在的列,从左到右分别是1~8列,个位数(1~8)表示所在的行,从上到下分别是1~8行。8X8点阵屏共64个点,所以总共需要64个无符号字符变量来保存,可以定义一个一维的数组SnakeBody来保存蛇身的数据。
用一个变量SnakeHead来保存蛇头在数组SnakeBody中对应数据的索引(范围:0~63)。
再用一个变量SnakeLength保存蛇的长度。
3、蛇的移动及显示
点阵屏有64个点,一个字节可以控制8个点,总共需要8个字节来存储要显示的数据。定义一个显示缓存数组DisplayBuffer来保存需要显示的数据,蛇移动后,或者蛇吃掉食物,产生了新的食物,需要将数据更新到显示缓存数组DisplayBuffer中,再把数据写入MAX7219芯片的寄存器就行了【代码中还定义了一个中间缓存数组MiddleBuffer(将数据先更新到MiddleBuffer中,再将MiddleBuffer中的64个0或1的数据合成为8个字节),其实可以不用定义这个数组,可以直接通过与、或、移位等操作将数据直接更新到显示缓存数组DisplayBuffer中】。同时,保存蛇身数据的数组SnakeBody中的数据也要同步更新。
unsigned char idata SnakeBody[]={
22,32,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
unsigned char idata MiddleBuffer[]={
0,0,0,0,0,0,0,0,
0,1,1,0,0,0,0,0, //蛇的初始位置在第二列第二行(蛇尾)和第三列第二行(蛇头)
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
举个例子,如上面代码所示,蛇初始长度为SnakeLength=2,初始位置是第二列第二行(蛇尾),和第三列第二行(蛇头),蛇头SnakeHead=1(对应数组中的第2个数据,索引为1)。如果没吃到食物且向右移动一格,则SnakeLength不变,SnakeHead自增变成2,蛇头对应数组SnakeBody的第三个数据,并且数组SnakeBody的第三个数据赋值为42,同时蛇尾的数据要清空,即第一个数据赋值为0(数组SnakeBody中蛇尾的数据可以不处理)。中间缓存数组MiddleBuffer的数据也要修改,并更新到显示缓存数组DisplayBuffer中,最后将数据写进MAX7219芯片的寄存器。蛇移动一次后这两个数组的数据应修改成下面代码所示的样子。
unsigned char idata SnakeBody[]={
0,32,42,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
unsigned char idata MiddleBuffer[]={
0,0,0,0,0,0,0,0,
0,0,1,1,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
如果没吃到食物,蛇的移动其实可以看成蛇尾对应的点移到了原来蛇头的前一个位置并且成为新的蛇头。
如果吃到食物,则整个蛇身不变,原来蛇头的前一个位置成为蛇的一部分,并且成为新的蛇头。
所以更新缓存数组的数据的时候,不用全部更新,只需要操作蛇头和蛇尾对应那部分就行了。每次移动时,蛇头的前一个LED点亮。如果没吃到食物,则蛇尾的那个LED熄灭,如果吃到了食物,则蛇尾的那个LED不熄灭,保持亮的状态。
移动的时候,其实数组SnakeBody中蛇尾的数据不用清空,将显示缓存数组DisplayBuffer中蛇尾的那个数据置0,再更新到MAX7219芯片的寄存器就可以了。
我们是循环使用数组SnakeBody中的数据,即蛇头变量SnakeHead=63(对应数组的最后一个数据,第64个数据)后再自增,让其变成0(此时蛇头对应数组的第1个数据)。
4、蛇头撞墙和撞到身体的检测
撞墙检测:如果移动后数组SnakeBody中的新蛇头的数据个位小于1、十位小于1、个位大于8、十位大于8,则说明撞墙了。游戏结束。
撞到身体检测:如果新蛇头的位置对应缓存数组中的那一位的数据为1,则说明撞到了身体。游戏结束。
5、真随机食物的创造
每次按下按键都以定时器0的低八位(TL0)做种,通过随机函数rand产生两个随机数来确定点阵屏的一个点,判断此位置是不是蛇身,如果不是,则此处为创造出来的食物的位置,如果是,则在此位置周围寻找,直到找到空位置为止。
6、蛇头方向的控制
if((KeyNum==13 || KeyNum==29) && Direction!=1) //长按和短按都检测,防止只检测短按时,短按的检测不灵敏,导致改变不了方向
{ //如果短按或长按“左”键(S13),且蛇头原来的移动方向不是向右
Direction=3; //则方向蛇头方向改为向左
}
如上代码所示,按下按键之后,如果按键对应的方向不是蛇头移动的反方向,则改变蛇头方向变量Direction的值。但是这样会有一个问题,在下一次移动前,如果多次按下按键,有可能会导致游戏结束。例如,在下次移动前,蛇“右转”后,继续“右转”,就会碰到蛇身(蛇的第二个点,即蛇头的下一个点)。
if((KeyNum==13 || KeyNum==29) && LastDirection!=1) //长按和短按都检测,防止只检测短按时,短按的检测不灵敏,导致改变不了方向
{ //如果短按或长按“左”键(S13),且蛇头原来的移动方向不是向右
Direction=3; //则方向蛇头方向改为向左
}
解决办法是再定义一个变量LastDirection,记录上次移动的方向,注意要在蛇移动后再将Direction的值赋给LastDirection进行更新,可以解决上面所讲的问题。
7、上下左右滚动显示
滚动显示函数的输入参数为:数组首地址(即数组名)和偏移量Offset,利用移位、与等操作处理数据后写入显示缓存数组。不熟练的建议学习了解指针的应用。
三、各模块代码
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、8位独立按键
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、8X8LED点阵屏(MAX7219驱动)
h文件
#ifndef __MATRIXLED_MAX7219_H__
#define __MATRIXLED_MAX7219_H__
void MAX7219_WriteByte(unsigned char Byte);
void MAX7219_SendData(unsigned char Address,Data);
void MatrixLED_Clear(void);
void MatrixLED_Init(void);
void MatrixLED_MoveLeft(unsigned char *Array,unsigned int Offset);
void MatrixLED_MoveUp(unsigned char *Array,unsigned int Offset);
#endif
c文件
#include <REGX52.H>
sbit MAX7219_DIN=P2^0; //串行数据
sbit MAX7219_CS=P2^1; //片选,上升沿有效
sbit MAX7219_CLK=P2^2; //串行时钟,上升沿有效
unsigned char MAX7219_DisplayBuffer[8]; //显示缓存
/**
* @brief 向MAX7219写入字节
* @param Byte 要写入的字节
* @retval 无
*/
void MAX7219_WriteByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
MAX7219_CLK=0; //清零时钟总线
MAX7219_DIN=Byte&(0x80>>i); //从高到低依次读取字节的位
MAX7219_CLK=1; //时钟上升沿,发送数据
}
}
/**
* @brief 向MAX7219发送数据
* @param Address 要写入的地址,如果是数码管的编号,则范围是:1~8
* @param Data 要写入的数据,只能驱动共阴数码管
* @retval 无
*/
void MAX7219_SendData(unsigned char Address,Data)
{
MAX7219_CS=0; //拉低片选线,选中器件
MAX7219_WriteByte(Address); //写入地址或数码管编号
MAX7219_WriteByte(Data); //写入数据
MAX7219_CS=1; //上升沿锁存数据
}
/**
* @brief MatrixLED清屏
* @param 无
* @retval 无
*/
void MatrixLED_Clear(void)
{
unsigned char i;
for(i=0;i<8;i++)
{
MAX7219_SendData(i+1,0x00);
}
}
/**
* @brief MatrixLED初始化,设置MAX7219内部的控制寄存器
* @param 无
* @retval 无
*/
void MatrixLED_Init(void)
{
MAX7219_SendData(0x09,0x00); //可按位选用译码器,0x00表示每位都不用译码器,0xFF表示8位都为该模式;
//译码方式:BCD码,例如二进制的0001 0011表示十进制的13
MAX7219_SendData(0x0A,0x03); //亮度,范围:0x00~0x0F
MAX7219_SendData(0x0B,0x07); //扫描界限,范围:0x00~0x07(1~8位数码管)
MAX7219_SendData(0x0C,0x01); //0:停机状态,1:正常工作状态
MAX7219_SendData(0x0F,0x00); //0:按设定模式正常工作;1:测试状态,全部LED将按最大亮度显示
MatrixLED_Clear(); //清屏
}
/**
* @brief LED点阵屏显示数组数据
* @param Array 传递过来的数组的首地址(数组名对应的就是数组的首地址)
* @param Offset 显示数组数据的偏移量,向左偏移Offset个像素
* @retval 无
*/
void MatrixLED_MoveLeft(unsigned char *Array,unsigned int Offset)
{
unsigned char i;
Array+=Offset;
for(i=0;i<8;i++)
{
MAX7219_SendData(8-i,*Array);
Array++;
}
}
/**
* @brief LED点阵屏显示数组数据
* @param Array 传递过来的数组的首地址(数组名对应的就是数组的首地址)
* @param Offset 显示数组数据的偏移量,向上偏移Offset个像素
* @retval 无
*/
void MatrixLED_MoveUp(unsigned char *Array,unsigned int Offset)
{
unsigned char i,m,n;
m=Offset/8;
n=Offset%8;
for(i=0;i<8;i++)
{
MAX7219_DisplayBuffer[i]=(*(Array+m*8)<<n)|(*(Array+m*8+8)>>(8-n)); //将偏移后的数据保存到显示缓存中(高位在上)
Array++;
}
for(i=0;i<8;i++)
{
MAX7219_SendData(8-i,MAX7219_DisplayBuffer[i]);
}
}
四、主函数
main.c
/*
by甘腾胜@20241202
效果查看/操作演示:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:12T@12.0000MHz
外设:自制的8位独立按键、8X8LED点阵屏(MAX7219驱动)
原理分析:https://blog.csdn.net/gantengsheng/article/details/143581157
操作说明:
(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_MAX7219.h"
unsigned char KeyNum; //存储获得的键码值
unsigned char Mode; //游戏模式,0:显示游戏英文名称“<<SNAKE>>”,1:显示难度的英文“DIFFICULTY”,
//2:难度选择界面(范围是1~5),3:游戏进行中,4:游戏结束全屏闪烁,
//5:显示得分英文“SCORE”,6:循环滚动显示得分
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; //保存整条蛇的数据的数组(共64个数据)中,蛇头对应的数据的索引(0~63),蛇的初始长度为2,初始只用了两个数据
//(数组的第1个数据和第2个数据),蛇头对应的是第2个数据,索引为1,Head的范围:0~63
unsigned char GameOverFlag; //游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char FlashFlag; //闪烁的标志,1:不显示,0:显示
unsigned char Food; //保存创造出来的食物的位置
unsigned char Offset1; //偏移量,用来控制英文字母向左滚动显示(切换模式后清零)
unsigned char Offset2; //偏移量,用来控制数字上下滚动显示(切换模式后不清零)
unsigned char RollFlag1; //滚动显示的标志,速度较快,1:滚动,0:不滚动
unsigned char RollFlag2; //滚动显示的标志,速度适中,1:滚动,0:不滚动
unsigned char RollFlag3; //滚动显示的标志,速度较慢,1:滚动,0:不滚动
unsigned char RollUpFlag; //难度选择界面,数字向上滚动显示的标志,1:滚动,0:不滚动
unsigned char RollDownFlag; //难度选择界面,数字向下滚动显示的标志,1:滚动,0:不滚动
unsigned char RollCount; //上下滚动的计次
unsigned char ExecuteOnceFlag; //各模式中只执行一次的标志,1:执行,0:不执行
unsigned char SnakeMoveSpeed=100; //蛇移动的速度,值越小,速度越快,上电后默认为1s移动一次(定时器定时10ms)
unsigned char T0Count0,T0Count1,T0Count2,T0Count3; //定时器计数的变量
unsigned char PauseFlag; //暂停的标志,1:暂停,0:不暂停
//用一个一位数组保存点阵屏哪些点该点亮的数据,每个数据均为0或1,0为不点亮,1为点亮
//需要更新显示时,将这64个数据合成为8个字节,更新到显示缓存DisplayBuffer数组中,再写入MAX7219寄存器
//不定义这个中间的缓存数组也行,可以利用与、或、移位等运算直接更新显示缓存DisplayBuffer数组中的数据
//也可以定义成一个二维数组
//用idata修饰数组变量,是因为STC89C52RC的片内RAM直接寻址的低128位不够用了,需要用到间接寻址的高128位
unsigned char idata MiddleBuffer[]={
0,0,0,0,0,0,0,0,
0,1,1,0,0,0,0,0, //蛇的初始位置在第二列第二行(蛇尾)和第三列第二行(蛇头)
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
//蛇身的数据,十位(1~8)表示蛇身所在的列,个位(1~8)表示蛇身所在的行
//初始长度为2,头部对应的是“32”(第三列第二行),即数组的第1个数据,所以蛇头Head初始值为1
//尾部对应的是“22”(第二列第二行),即数组的第0个数据
unsigned char idata SnakeBody[]={
22,32,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
};
//屏幕显示的缓存
//要求:纵向取模,高位在上
unsigned char DisplayBuffer[]={
0x00,0x40,0x40,0x00,0x00,0x00,0x00,0x00,
};
unsigned char idata ScoreShow[]={ //用来滚动显示得分
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
0x00,0x00,0x00,0x00,0x00,0x00, //游戏结束后将蛇身长度Length的十位的数字字模写入到这一行
0x00,0x00,0x00,0x00,0x00,0x00, //游戏结束后将蛇身长度Length的个位的数字字模写入到这一行
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
};
//阴码(亮点为1),纵向取模,高位在上,字模大小为6*8
//用code修饰,表示保存到ROM中
unsigned char code Text_SNAKE[]={ //游戏名称“<<SNAKE>>”
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
0x10,0x28,0x44,0x92,0x28,0x44,0x82,0x00, // <<
0x00,0x62,0x92,0x92,0x92,0x8C, // S
0x00,0xFE,0x20,0x10,0x08,0xFE, // N
0x00,0x3E,0x48,0x88,0x48,0x3E, // A
0x00,0xFE,0x10,0x28,0x44,0x82, // K
0x00,0xFE,0x92,0x92,0x92,0x82, // E
0x00,0x82,0x44,0x28,0x92,0x44,0x28,0x10, // >>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
};
unsigned char code Text_DIFFICULTY[]={ //难度的英文名“<<DIFFICULTY>>”
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
0x00,0xFE,0x82,0x82,0x44,0x38, // D
0x00,0x00,0x82,0xFE,0x82,0x00, // I
0x00,0xFE,0x90,0x90,0x90,0x80, // F
0x00,0xFE,0x90,0x90,0x90,0x80, // F
0x00,0x00,0x82,0xFE,0x82,0x00, // I
0x00,0x7C,0x82,0x82,0x82,0x44, // C
0x00,0xFC,0x02,0x02,0x02,0xFC, // U
0x00,0xFE,0x02,0x02,0x02,0x02, // L
0x00,0x80,0x80,0xFE,0x80,0x80, // T
0x00,0xE0,0x10,0x0E,0x10,0xE0, // Y
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
0x00,0x00,0x00,0x42,0xFE,0x02,0x00,0x00, // 1 如果不按按键跳过,则在显示“1”后自动切换到下一个模式
};
unsigned char code DifficultySelect[]={ //难度对应的数字,“1~5”
0x00,0x00,0x00,0x42,0xFE,0x02,0x00,0x00, // 1
0x00,0x00,0x42,0x86,0x8A,0x92,0x62,0x00, // 2
0x00,0x00,0x84,0x82,0xA2,0xD2,0x8C,0x00, // 3
0x00,0x00,0x18,0x28,0x48,0xFE,0x08,0x00, // 4
0x00,0x00,0xE4,0xA2,0xA2,0xA2,0x9C,0x00, // 5
0x00,0x00,0x00,0x42,0xFE,0x02,0x00,0x00, // 1 与第一行相同是因为要做成循环滚动显示
};
unsigned char code Text_SCORE[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
0x00,0x62,0x92,0x92,0x92,0x8C, // S
0x00,0x7C,0x82,0x82,0x82,0x44, // C
0x00,0x7C,0x82,0x82,0x82,0x7C, // O
0x00,0xFE,0x90,0x98,0x94,0x62, // R
0x00,0xFE,0x92,0x92,0x92,0x82, // E
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示
};
unsigned char code SCORE_Num[]={ //0~9的字模,根据得分,将对应的字模数据写入到数组ScoreShow中用于显示
0x00,0x7C,0x8A,0x92,0xA2,0x7C, // 0
0x00,0x00,0x42,0xFE,0x02,0x00, // 1
0x00,0x42,0x86,0x8A,0x92,0x62, // 2
0x00,0x84,0x82,0xA2,0xD2,0x8C, // 3
0x00,0x18,0x28,0x48,0xFE,0x08, // 4
0x00,0xE4,0xA2,0xA2,0xA2,0x9C, // 5
0x00,0x3C,0x52,0x92,0x92,0x0C, // 6
0x00,0x80,0x8E,0x90,0xA0,0xC0, // 7
0x00,0x6C,0x92,0x92,0x92,0x6C, // 8
0x00,0x60,0x92,0x92,0x94,0x78, // 9
};
/**
* @brief 创造出随机位置的食物
* @param 无
* @retval 创造出的食物位置的数据,数据的十位(1~8)代表食物所在的列,数据的个位(1~8)代表食物所在的行
*/
unsigned char CreateFood(void)
{
unsigned char FoodTemp;
unsigned char i,j,m,n;
m=rand()%8; //产生一个0~7的随机数
n=rand()%8; //产生一个0~7的随机数
for(j=0;j<8;j++) //产生一个随机位置,判断该位置是否是蛇身,如果不是,就返回该位置所对应的数据
{ //如果是蛇身的位置,则从该点向周围寻找不是蛇身的空位置
for(i=0;i<8;i++)
{
if(MiddleBuffer[((n+j)%8)*8+(m+i)%8]==0)
{
FoodTemp=((m+i)%8+1)*10+(n+j)%8+1;
break; //找到了空位置就退出循环
}
}
}
return FoodTemp;
}
/**
* @brief 转换数据,将数组MiddleBuffer的数据,转换成用于显示(DisplayBuffer)的数据
* @param 无
* @retval 无
*/
void ConvertData(void)
{
unsigned char i,j;
for(i=0;i<8;i++)
{
DisplayBuffer[i]=0;
for(j=0;j<8;j++)
{
if(MiddleBuffer[8*j+i]){DisplayBuffer[i]|=(0x80>>j);}
}
}
}
/**
* @brief 控制蛇的移动
* @param 无
* @retval 无
*/
void MoveSnake(void)
{
if(NowDirection==1) //如果向右移动
{ //SnakeBody数组中蛇头的下一个数据等于上一个数据加10,即蛇头移动到了右边这一列
SnakeBody[(Head+1)%64]=SnakeBody[Head%64]+10;
}
if(NowDirection==2) //如果向上移动
{ //SnakeBody数组中蛇头的下一个数据等于上一个数据减1,即蛇头移动到了上边这一行
SnakeBody[(Head+1)%64]=SnakeBody[Head%64]-1;
}
if(NowDirection==3) //如果向左移动
{ //SnakeBody数组中蛇头的下一个数据等于上一个数据减10,即蛇头移动到了左边这一列
SnakeBody[(Head+1)%64]=SnakeBody[Head%64]-10;
}
if(NowDirection==4) //如果向下移动
{ //SnakeBody数组中蛇头的下一个数据等于上一个数据加1,即蛇头移动到了下边这一行
SnakeBody[(Head+1)%64]=SnakeBody[Head%64]+1;
}
if(SnakeBody[(Head+1)%64]==Food) //判断蛇头移动后的位置是否是食物所在的位置
{ //如果是
Length++; //蛇身长度加1
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //中间缓存数组MiddleBuffer中蛇头的新位置写1
//防止食物闪烁刚好不显示的时候进入此函数,导致蛇身一个点不亮
Food=CreateFood(); //重新创造一个食物
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //中间缓存数组MiddleBuffer中新的食物对应位置写1
MatrixLED_MoveLeft(DisplayBuffer,0); //更新显示
FlashFlag=0; //创造出新的食物时,食物暂不闪烁
T0Count2=0; //定时器T0Count2重新计数
}
else if(SnakeBody[(Head+1)%64]/10<1 || SnakeBody[(Head+1)%64]/10>8 || SnakeBody[(Head+1)%64]%10<1 ||
SnakeBody[(Head+1)%64]%10>8|| MiddleBuffer[(SnakeBody[(Head+1)%64]%10-1)*8+SnakeBody[(Head+1)%64]/10-1]==1)
{ //如果蛇头移动后的位置,所在的行小于1或者大于8,或所在的列小于1或者大于8,或撞在蛇身上,则游戏结束
GameOverFlag=1; //游戏结束的标志置1
}
else //如果蛇头移动后的位置不是食物,也不是撞墙,也不是撞到蛇身的话
{
//中间缓存数组MiddleBuffer中蛇头前进后的新位置写1
MiddleBuffer[(SnakeBody[(Head+1)%64]%10-1)*8+SnakeBody[(Head+1)%64]/10-1]=1;
//中间缓存数组MiddleBuffer中蛇尾的位置对应的位清0(蛇身移动,如果没有吃到食物,相当于蛇尾对应的点跑到了
//蛇头的前一个点,变成了蛇头,原来的蛇头变成蛇身)整条蛇中间的数据不用操作
MiddleBuffer[(SnakeBody[(Head+64-Length+1)%64]%10-1)*8+SnakeBody[(Head+64-Length+1)%64]/10-1]=0;
//保存蛇身数据的数组SnakeBody中,蛇尾的数据清零,更新数据
//Head+64:+64是因为Head为63后再加1,就会变成了0,防止Head+64-Length+1为负数
//即我们是循环使用SnakeBody数组中的64个数据,蛇头对应数组SnakeBody的第64个数据(索引为63)后,再移动一次,蛇头就来到了数组的第1个数据(索引为0)
SnakeBody[(Head+64-Length+1)%64]=0;
}
Head++; //SnakeBody数组中,蛇头对应的索引加1
Head%=64; //蛇头在SnakeBody数组中范围:0~63,蛇头到达数组最后一个数据(第64个数据,索引为63)后,又回到数组的第1个数据(索引为0)
}
void main()
{
unsigned char i;
Timer0_Init(); //定时器初始化
MatrixLED_Init(); //MAX7219初始化
while(1)
{
KeyNum=Key(); //获取键码值
if(KeyNum) //如果有按键按下
{
srand(TL0); //以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据
if(Mode==6 && KeyNum==18) //如果滚动显示得分界面按下K2(松手瞬间)
{
Mode=2; //返回难度选择界面
ExecuteOnceFlag=1; //各模式只执行一次代码的标志置1
}
if(Mode==5 && KeyNum==17) //如果滚动显示英文“SCORE”时按下K1(松手瞬间)
{
Mode=6; //跳过英文显示,切换到滚动显示得分界面
ExecuteOnceFlag=1;
Offset1=0; //滚动显示的偏移量清0
}
if(Mode==4 && KeyNum==17) //如果游戏结束闪烁时按下K1(松手瞬间)
{
Mode=5; //切换到滚动显示英文“SCORE”模式
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) //如果按了开始键(K1)(松手瞬间),则开始游戏
{
Mode=3; //切换到游戏模式
ExecuteOnceFlag=1;
}
}
if(KeyNum>=17 && KeyNum<=24) //如果按下任意按键(松手瞬间)
{
//两个if的顺序不能调换,如果调换了,就从模式0直接跳到模式2了
if(Mode==1){Mode=2;Offset1=0;ExecuteOnceFlag=1;} //跳过英文“DIFFICULTY”的显示,切换到难度选择界面
if(Mode==0){Mode=1;Offset1=0;ExecuteOnceFlag=1;} //跳过游戏名英文“<<SNAKE>>”的显示,切换到英文“DIFFICULTY”的显示界面
}
}
if(Mode==0) //如果是显示游戏英文名称的模式
{
if(RollFlag3) //如果滚动的标志RollFlag3为1(定时器中每隔150ms将此标志置1)
{
RollFlag3=0; //滚动的标志RollFlag3清零
MatrixLED_MoveLeft(Text_SNAKE,Offset1); //向左滚动
Offset1++; //每次向左移动一个像素
Offset1%=54; //越界清零,循环滚动
}
}
if(Mode==1) //如果是显示难度的英文“DIFFICULTY”的模式
{
if(RollFlag2 && Offset1<=76) //只向左滚动显示一次,不循环滚动显示
{
RollFlag2=0;
MatrixLED_MoveLeft(Text_DIFFICULTY,Offset1);
Offset1++;
}
else if(Offset1>76) //显示数字“1”之后,自动切换到难度选择模式
{
Mode=2;
Offset1=0;
}
}
if(Mode==2) //如果是难度选择模式
{
if(ExecuteOnceFlag) //切换到该模式后,此if中的内容只执行1次
{ //第一次进入游戏,或游戏结束显示得分返回难度选择模式时,所有数据重置
ExecuteOnceFlag=0;
MatrixLED_MoveUp(DifficultySelect,Offset2); //显示难度对应的数字,范围:1~5
GameOverFlag=0; //游戏结束标志清零
PauseFlag=0; //游戏暂停标志清零
NowDirection=1; //蛇头默认向右移动
LastDirection=1; //上一次蛇头默认向右移动
Length=2; //蛇的初始长度为2
Head=1; //蛇头对应数组中的第1个数据(注意不是第0个)
for(i=0;i<64;i++) //蛇身数据全部清零
{
SnakeBody[i]=0;
}
SnakeBody[0]=22; //蛇身数据全部清零后,写入蛇身初始的两个数据
SnakeBody[1]=32;
for(i=0;i<64;i++) //中间缓存数组MiddleBuffer中对应的两个位置写1
{
if(i==9 || i==10){MiddleBuffer[i]=1;}
else{MiddleBuffer[i]=0;}
}
Food=CreateFood(); //进入游戏前,先创造出一个食物
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //将食物位置对应的数据置1
}
if(RollFlag1 && RollUpFlag) //如果滚动标志为1,且向上滚动的标志也为1
{
RollFlag1=0; //定时器中每个50ms将RollFlag1标志置1
Offset2++; //向上移动一个像素
Offset2%=40; //越界清零,总共5个数字,每个数字的高度是8,所以是5*8=40
MatrixLED_MoveUp(DifficultySelect,Offset2); //更新显示
RollCount++;
if(RollCount==8) //移动了8个像素后停止移动
{
RollCount=0;
RollUpFlag=0;
Offset2=(Offset2/8)*8; //防止移动到一半的时候按下“上”或“下”按键导致数字没有在点阵屏中间
//Offset2的值必须是8的整数倍
switch(Offset2/8)
{
case 0:SnakeMoveSpeed=100;break; //难度1,1s移动1次
case 1:SnakeMoveSpeed=75;break; //难度2,0.75s移动1次
case 2:SnakeMoveSpeed=50;break; //难度3,0.5s移动1次
case 3:SnakeMoveSpeed=25;break; //难度4,0.25s移动1次
case 4:SnakeMoveSpeed=12;break; //难度5,0.12s移动1次
default:break;
}
}
}
if(RollFlag1 && RollDownFlag) //如果滚动标志为1,且向下滚动的标志也为1
{
RollFlag1=0;
if(Offset2==0){Offset2=40;}
Offset2--;
MatrixLED_MoveUp(DifficultySelect,Offset2);
RollCount++;
if(RollCount==8)
{
RollCount=0;
RollDownFlag=0;
Offset2=(Offset2/8)*8;
switch(Offset2/8)
{
case 0:SnakeMoveSpeed=100;break;
case 1:SnakeMoveSpeed=75;break;
case 2:SnakeMoveSpeed=50;break;
case 3:SnakeMoveSpeed=25;break;
case 4:SnakeMoveSpeed=12;break;
default:break;
}
}
}
}
if(Mode==3) //如果是游戏进行模式
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
ConvertData(); //刚进入此模式时,转换中间缓存数组MiddleBuffer的数据到显示缓存数组DisplayBuffer中
MatrixLED_MoveLeft(DisplayBuffer,0); //显示初始的蛇身及食物
MoveSnakeFlag=0; //蛇移动的标志清零
T0Count1=0; //定时器计数变量T0Count1清零,重新计数
}
if(PauseFlag) //如果暂停了
{
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //食物不闪烁,一直显示
ConvertData(); //转换数据
MatrixLED_MoveLeft(DisplayBuffer,0); //更新显示
}
else if(FlashFlag) //如果不暂停,且闪烁标志为1
{
MiddleBuffer[(Food%10-1)*8+Food/10-1]=0; //不显示食物
ConvertData();
MatrixLED_MoveLeft(DisplayBuffer,0);
}
else //如果不暂停,且闪烁标志为0
{
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //显示食物
ConvertData();
MatrixLED_MoveLeft(DisplayBuffer,0);
}
if(MoveSnakeFlag && GameOverFlag==0 && PauseFlag==0)
{ //如果移动的标志为1,且不暂停,且游戏也没结束
LastDirection=NowDirection; //保存上一次移动的方向,用于按键的判断(蛇不能往后移动)
MoveSnakeFlag=0; //移动标志清零
MoveSnake(); //移动一次
ConvertData(); //转换数据
MatrixLED_MoveLeft(DisplayBuffer,0); //更新显示
}
if(GameOverFlag==1) //如果游戏结束
{
MiddleBuffer[(Food%10-1)*8+Food/10-1]=1; //显示食物
ConvertData();
MatrixLED_MoveLeft(DisplayBuffer,0);
Mode=4; //切换到全屏闪烁模式
ExecuteOnceFlag=1;
}
}
if(Mode==4)
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
for(i=0;i<6;i++) //提前将得分(即蛇身的长度)的十位和个位的字模写入数组ScoreShow中
{
ScoreShow[8+i]=SCORE_Num[(Length/10)*6+i];
}
for(i=0;i<6;i++)
{
ScoreShow[14+i]=SCORE_Num[(Length%10)*6+i];
}
PauseFlag=0;
}
//全屏闪烁
if(FlashFlag){MAX7219_SendData(0x0C,0x00);} //向MAX7219写入指令,让屏幕不显示
else{MAX7219_SendData(0x0C,0x01);} //向MAX7219写入指令,让屏幕显示
}
if(Mode==5)
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
//向MAX7219写入指令,屏幕显示,防止在上一个模式中,刚好在写入屏幕不显示的指令后切换到此模式
MAX7219_SendData(0x0C,0x01);
}
if(RollFlag2 && Offset1<=38) //只滚动显示一次英文“SCORE”
{
RollFlag2=0;
MatrixLED_MoveLeft(Text_SCORE,Offset1);
Offset1++;
}
else if(Offset1>38) //滚动结束后,自动切换到循环显示得分的模式
{
Mode=6;
Offset1=0; //偏移量清零
}
}
if(Mode==6) //如果是滚动显示得分模式
{
if(RollFlag2) //如果滚动的标志为1
{
RollFlag2=0;
MatrixLED_MoveLeft(ScoreShow,Offset1);
Offset1++;
Offset1%=20; //循环滚动
}
}
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned char i;
TL0=0xF0; //设置定时初值,定时10ms,晶振@12.0000MHz
TH0=0xD8; //设置定时初值,定时10ms,晶振@12.0000MHz
T0Count0++;
if(PauseFlag==0) //不暂停时,T0Count1和T0Count2才计数
{
T0Count1++;
T0Count2++;
}
T0Count3++;
if(T0Count0>=2) //20ms扫描一次按键
{
T0Count0=0;
Key_Tick();
}
if(T0Count1>=SnakeMoveSpeed) //用来控制蛇移动的速度
{
T0Count1=0;
MoveSnakeFlag=1;
}
if(T0Count2>=25) //0.25s取反闪烁标志FlashFlag的值(0或1)
{
T0Count2=0;
FlashFlag=!FlashFlag;
}
if(T0Count3>=5) //控制滚动的速度
{
T0Count3=0;
RollFlag1=1; //50ms
i++;
i%=240;
if(i%2==0){RollFlag2=1;} //100ms
if(i%3==0){RollFlag3=1;} //150ms
}
}
附录A:ASCII字符6*8像素字模
(来源:江科大的共享代码)
/*宽6像素,高8像素*/
//纵向取模,高位在下
0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0x2F,0x00,0x00,// ! 1
0x00,0x00,0x07,0x00,0x07,0x00,// " 2
0x00,0x14,0x7F,0x14,0x7F,0x14,// # 3
0x00,0x24,0x2A,0x7F,0x2A,0x12,// $ 4
0x00,0x23,0x13,0x08,0x64,0x62,// % 5
0x00,0x36,0x49,0x55,0x22,0x50,// & 6
0x00,0x00,0x00,0x07,0x00,0x00,// ' 7
0x00,0x00,0x1C,0x22,0x41,0x00,// ( 8
0x00,0x00,0x41,0x22,0x1C,0x00,// ) 9
0x00,0x14,0x08,0x3E,0x08,0x14,// * 10
0x00,0x08,0x08,0x3E,0x08,0x08,// + 11
0x00,0x00,0x00,0xA0,0x60,0x00,// , 12
0x00,0x08,0x08,0x08,0x08,0x08,// - 13
0x00,0x00,0x60,0x60,0x00,0x00,// . 14
0x00,0x20,0x10,0x08,0x04,0x02,// / 15
0x00,0x3E,0x51,0x49,0x45,0x3E,// 0 16
0x00,0x00,0x42,0x7F,0x40,0x00,// 1 17
0x00,0x42,0x61,0x51,0x49,0x46,// 2 18
0x00,0x21,0x41,0x45,0x4B,0x31,// 3 19
0x00,0x18,0x14,0x12,0x7F,0x10,// 4 20
0x00,0x27,0x45,0x45,0x45,0x39,// 5 21
0x00,0x3C,0x4A,0x49,0x49,0x30,// 6 22
0x00,0x01,0x71,0x09,0x05,0x03,// 7 23
0x00,0x36,0x49,0x49,0x49,0x36,// 8 24
0x00,0x06,0x49,0x49,0x29,0x1E,// 9 25
0x00,0x00,0x36,0x36,0x00,0x00,// : 26
0x00,0x00,0x56,0x36,0x00,0x00,// ; 27
0x00,0x08,0x14,0x22,0x41,0x00,// < 28
0x00,0x14,0x14,0x14,0x14,0x14,// = 29
0x00,0x00,0x41,0x22,0x14,0x08,// > 30
0x00,0x02,0x01,0x51,0x09,0x06,// ? 31
0x00,0x3E,0x49,0x55,0x59,0x2E,// @ 32
0x00,0x7C,0x12,0x11,0x12,0x7C,// A 33
0x00,0x7F,0x49,0x49,0x49,0x36,// B 34
0x00,0x3E,0x41,0x41,0x41,0x22,// C 35
0x00,0x7F,0x41,0x41,0x22,0x1C,// D 36
0x00,0x7F,0x49,0x49,0x49,0x41,// E 37
0x00,0x7F,0x09,0x09,0x09,0x01,// F 38
0x00,0x3E,0x41,0x49,0x49,0x7A,// G 39
0x00,0x7F,0x08,0x08,0x08,0x7F,// H 40
0x00,0x00,0x41,0x7F,0x41,0x00,// I 41
0x00,0x20,0x40,0x41,0x3F,0x01,// J 42
0x00,0x7F,0x08,0x14,0x22,0x41,// K 43
0x00,0x7F,0x40,0x40,0x40,0x40,// L 44
0x00,0x7F,0x02,0x0C,0x02,0x7F,// M 45
0x00,0x7F,0x04,0x08,0x10,0x7F,// N 46
0x00,0x3E,0x41,0x41,0x41,0x3E,// O 47
0x00,0x7F,0x09,0x09,0x09,0x06,// P 48
0x00,0x3E,0x41,0x51,0x21,0x5E,// Q 49
0x00,0x7F,0x09,0x19,0x29,0x46,// R 50
0x00,0x46,0x49,0x49,0x49,0x31,// S 51
0x00,0x01,0x01,0x7F,0x01,0x01,// T 52
0x00,0x3F,0x40,0x40,0x40,0x3F,// U 53
0x00,0x1F,0x20,0x40,0x20,0x1F,// V 54
0x00,0x3F,0x40,0x38,0x40,0x3F,// W 55
0x00,0x63,0x14,0x08,0x14,0x63,// X 56
0x00,0x07,0x08,0x70,0x08,0x07,// Y 57
0x00,0x61,0x51,0x49,0x45,0x43,// Z 58
0x00,0x00,0x7F,0x41,0x41,0x00,// [ 59
0x00,0x02,0x04,0x08,0x10,0x20,// \ 60
0x00,0x00,0x41,0x41,0x7F,0x00,// ] 61
0x00,0x04,0x02,0x01,0x02,0x04,// ^ 62
0x00,0x40,0x40,0x40,0x40,0x40,// _ 63
0x00,0x00,0x01,0x02,0x04,0x00,// ` 64
0x00,0x20,0x54,0x54,0x54,0x78,// a 65
0x00,0x7F,0x48,0x44,0x44,0x38,// b 66
0x00,0x38,0x44,0x44,0x44,0x20,// c 67
0x00,0x38,0x44,0x44,0x48,0x7F,// d 68
0x00,0x38,0x54,0x54,0x54,0x18,// e 69
0x00,0x08,0x7E,0x09,0x01,0x02,// f 70
0x00,0x18,0xA4,0xA4,0xA4,0x7C,// g 71
0x00,0x7F,0x08,0x04,0x04,0x78,// h 72
0x00,0x00,0x44,0x7D,0x40,0x00,// i 73
0x00,0x40,0x80,0x84,0x7D,0x00,// j 74
0x00,0x7F,0x10,0x28,0x44,0x00,// k 75
0x00,0x00,0x41,0x7F,0x40,0x00,// l 76
0x00,0x7C,0x04,0x18,0x04,0x78,// m 77
0x00,0x7C,0x08,0x04,0x04,0x78,// n 78
0x00,0x38,0x44,0x44,0x44,0x38,// o 79
0x00,0xFC,0x24,0x24,0x24,0x18,// p 80
0x00,0x18,0x24,0x24,0x18,0xFC,// q 81
0x00,0x7C,0x08,0x04,0x04,0x08,// r 82
0x00,0x48,0x54,0x54,0x54,0x20,// s 83
0x00,0x04,0x3F,0x44,0x40,0x20,// t 84
0x00,0x3C,0x40,0x40,0x20,0x7C,// u 85
0x00,0x1C,0x20,0x40,0x20,0x1C,// v 86
0x00,0x3C,0x40,0x30,0x40,0x3C,// w 87
0x00,0x44,0x28,0x10,0x28,0x44,// x 88
0x00,0x1C,0xA0,0xA0,0xA0,0x7C,// y 89
0x00,0x44,0x64,0x54,0x4C,0x44,// z 90
0x00,0x00,0x08,0x7F,0x41,0x00,// { 91
0x00,0x00,0x00,0x7F,0x00,0x00,// | 92
0x00,0x00,0x41,0x7F,0x08,0x00,// } 93
0x00,0x08,0x04,0x08,0x10,0x08,// ~ 94
纵向取模,高位在上的数据还没弄好
总结
这个小游戏的关键在于真随机数的产生,蛇身数据的保存,蛇身的移动,碰撞检测、按键的检测、LED点阵屏的扫描显示、字符的滚动显示、各种标志的使用。
更多推荐

所有评论(0)