当前位置: 首页 > backend >正文

基于51单片机和8X8点阵屏、矩阵按键的匹对消除类小游戏

目录

  • 系列文章目录
  • 前言
  • 一、效果展示
  • 二、原理分析
  • 三、各模块代码
    • 1、8X8点阵屏
    • 2、矩阵按键
    • 3、定时器0
    • 4、定时器1
  • 四、主函数
  • 总结

系列文章目录


前言

用的是普中A2开发板,用到板上的8X8LED点阵屏和矩阵按键。

【单片机】STC89C52RC
【频率】12T@11.0592MHz

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

一、效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、原理分析

1、方块类型

总共有四种方块类型,分别是1个点、2个点、3个点、4个点。

开始游戏后,6个方块都是显示4个点,按确认键后,会显示是1个点、2个点还是3个点,对两个方块按了确认键“翻开”后,检查两个方块是否是相同的类型,如果是,则消除,不再显示,如果不是,则这两个方块恢复显示4个点。

2、方块的选择

通过闪烁来显示玩家选择的方块。

用一个 unsigned char 变量 Select 来保存闪烁的位置,0~2:第一行,3~5:第二行。

用6个字节组成的数组来记录这6个方块是否显示(消除后就不再显示了),当全部消除后游戏结束。

3、游戏用时

用一个 unsigned char 或 unsigned int 变量来记录游戏所用的时间,通过定时器来进行计时。

4、字母和数字的滚动显示

提前将字母和数字的字模存储到数组中(数据保存到单片机的Flash中),将数组的首地址和偏移量传给函数,可以轻松实现滚动显示。

5、矩阵按键

利用定时器每隔20ms检测一次按键,跟上一次检测的结果对比,可判断是按下瞬间、长按还是松手瞬间。本代码用的是松手的键码,因为如果硬件没问题的话,是一定能检测出松手的键码的,不管完成一次while(1)循环需要多长时间。

6、方块类型的随机排列

总共有三对方块类型,用到两个数组。

TypeForPick[]={0,1,2,0,1,2};

Type[6];

举例:

先利用rand()函数产生一个0~5的随机数,假设是2,则将TypeForPick[2]赋值给Type[0],然后将数组TypeForPick中TypeForPick[3]之后的数据往前移动一个索引,即TypeForPick[3]赋值给TypeForPick[2],TypeForPick[4]赋值给TypeForPick[3],TypeForPick[5]赋值给TypeForPick[4],亦即是变成TypeForPick[]={0,1,0,1,2,2},第6个数据TypeForPick[5]没有用了,之后用到前5个数据:0,1,0,1,2,。

第二次,利用rand()函数产生一个0~4的随机数,假设是1,则将TypeForPick[1]赋值给Type[1],然后将数组TypeForPick中TypeForPick[1]之后的数据往前移动一个索引,即变成TypeForPick[]={0,0,1,2,2,2},第5、6个数据没有用了,之后用到前4个数据:0,0,1,2,。

以此类推。

这些过程可以通过嵌套for循环来实现。

7、真随机的实现

如果没有设置随机数的种子,每次上电后产生的是一样的排列,这个是伪随机,本代码老夫在每次获取非0键码后都用定时器0的低八位做种子,这样用rand()函数产生的随机数就跟按下按键的时刻有关,产生的是真随机数。

三、各模块代码

1、8X8点阵屏

h文件

#ifndef __MATRIXLED__
#define __MATRIXLED__extern unsigned char DisplayBuffer[];
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);
void MatrixLED_Tick(void);
void MatrixLED_DrawPoint(unsigned char X,unsigned char Y);
void MatrixLED_ClearPoint(unsigned char X,unsigned char Y);
unsigned char MatrixLED_GetPoint(unsigned char X,unsigned char Y);
void MatrixLED_DrawBlock(unsigned char X,unsigned char Y,unsigned char Type);
void MatrixLED_ClearBlock(unsigned char X,unsigned char Y);#endif

c文件

#include <REGX52.H>/*引脚定义*/sbit _74HC595_DS=P3^4;		//串行数据输入
sbit _74HC595_STCP=P3^5;	//储存寄存器时钟输入,上升沿有效
sbit _74HC595_SHCP=P3^6;	//移位寄存器时钟输入,上升沿有效/*
每一个B对应一个灯。缓存数组DisplayBuffer的8个字节分别对应这8列,高位在下
B0	B0	B0	B0	B0	B0	B0	B0
B1	B1  B1	B1	B1	B1	B1	B1
B2	B2  B2	B2	B2	B2	B2	B2
B3	B3  B3	B3	B3	B3	B3	B3
B4	B4  B4	B4	B4	B4	B4	B4
B5	B5  B5	B5	B5	B5	B5	B5
B6	B6  B6	B6	B6	B6	B6	B6
B7	B7  B7	B7	B7	B7	B7	B7
*///想要改变显示内容,改变数组DisplayBuffer的数据就行了,由定时器自动扫描
unsigned char DisplayBuffer[8];/*函数定义*//*** 函    数:LED点阵屏清空显示* 参    数:无* 返 回 值:无* 说    明:直接更改缓存数组的数据就行了,由定时器自动扫描显示*/
void MatrixLED_Clear(void)
{unsigned char i;for(i=0;i<8;i++){DisplayBuffer[i]=0;}		
}/*** 函    数:MatrixLED初始化(即74HC595初始化)* 参    数:无* 返 回 值:无*/
void MatrixLED_Init(void)
{_74HC595_SHCP=0;	//移位寄存器时钟信号初始化_74HC595_STCP=0;	//储存寄存器时钟信号初始化MatrixLED_Clear();	//点阵屏清屏
}/*** 函    数:74HC595写入字节* 参    数:Byte 要写入的字节* 返 回 值:无*/
void _74HC595_WriteByte(unsigned char Byte)
{unsigned char i;for(i=0;i<8;i++)	//循环8次{_74HC595_DS=Byte&(0x01<<i);	//低位先发_74HC595_SHCP=1;	//SHCP上升沿时,DS的数据写入移位寄存器_74HC595_SHCP=0;}_74HC595_STCP=1;	//STCP上升沿时,数据从移位寄存器转存到储存寄存器_74HC595_STCP=0;
}/*** 函    数:8X8LED点阵屏显示数组内容* 参    数:Array 传递过来的数组的首地址(即指针),数组名就是数组的首地址* 返 回 值:Offset 偏移量,向左偏移Offset个像素* 说    明:要求逐列式取模,高位在下*/
void MatrixLED_MoveLeft(unsigned char *Array,unsigned int Offset)
{unsigned char i;Array+=Offset;for(i=0;i<8;i++){DisplayBuffer[i]=*Array;Array++;	//地址自增}
}/*** 函    数:8X8LED点阵屏显示数组内容* 参    数:Array 传递过来的数组的首地址(即指针),数组名就是数组的首地址* 返 回 值:Offset 显示数组数据的偏移量,向上偏移Offset个像素* 说    明:要求逐列式取模,高位在下*/
void MatrixLED_MoveUp(unsigned char *Array,unsigned int Offset)
{unsigned char i,m,n;m=Offset/8;n=Offset%8;Array+=m*8;for(i=0;i<8;i++){DisplayBuffer[i]=(*Array>>n)|(*(Array+8)<<(8-n));Array++;}	
}/*** 函    数:LED点阵屏驱动函数,中断中调用* 参    数:无* 返 回 值:无*/
void MatrixLED_Tick(void)
{static unsigned char i=0;	//定义静态变量P0=0xFF;	//消影_74HC595_WriteByte(DisplayBuffer[i]);	//将数据写入到74HC595中锁存P0=~(0x80>>i);	//位选,低电平选中i++;	//下次进中断后扫描下一列i%=8;	//显示完第八列后,又从第一列开始显示
}/*** 函    数:MatrixLED在指定位置画一个点* 参    数:X 指定点的横坐标,范围:0~7* 参    数:Y 指定点的纵坐标,范围:0~7* 返 回 值:无* 说    明:左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向*/
void MatrixLED_DrawPoint(unsigned char X,unsigned char Y)
{if(X>=0 && X<=7 && Y>=0 && Y<=7){ DisplayBuffer[X] |= 0x01<<Y; }
}/*** 函    数:MatrixLED在指定位置清除一个点* 参    数:X 指定点的横坐标,范围:0~7* 参    数:Y 指定点的纵坐标,范围:0~7* 返 回 值:无* 说    明:左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向*/
void MatrixLED_ClearPoint(unsigned char X,unsigned char Y)
{if(X>=0 && X<=7 && Y>=0 && Y<=7){ DisplayBuffer[X] &= ~(0x01<<Y); }
}/*** 函    数:MatrixLED获取其中一个LED的状态* 参    数:X 指定点的横坐标,范围:0~7* 参    数:Y 指定点的纵坐标,范围:0~7* 返 回 值:LED的亮灭状态,1:亮,0:灭,2:说明超出了屏幕范围* 说    明:左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向*/
unsigned char MatrixLED_GetPoint(unsigned char X,unsigned char Y)
{if(X>=0 && X<=7 && Y>=0 && Y<=7){if( DisplayBuffer[X] & (0x01<<Y) ) {return 1;}else {return 0;}}else {return 2;}
}/*** 函    数:MatrixLED在指定位置显示一个指定类型的方块,方块大小:2X2* 参    数:X 方块位置,范围:0~2(0:1、2列, 1:4、5列, 2:7、8列)* 参    数:Y 方块位置,范围:0~6(0:1、2行, 1:2、3行, ..., 6:7、8行)* 参    数:Type 要显示方块的类型,0:全部不亮,1:方块的左下角的LED点亮,2:方块的左边两个LED点亮,3:左边两个LED和右下角的LED点亮,4:全部4个LED点亮* 返 回 值:无*/
void MatrixLED_DrawBlock(unsigned char X,unsigned char Y,unsigned char Type)
{if(X==0){switch(Type){case 0: DisplayBuffer[0] |= 0x02<<Y; break;case 1: DisplayBuffer[0] |= 0x03<<Y; break;case 2: DisplayBuffer[0] |= 0x03<<Y; DisplayBuffer[1] |= 0x02<<Y; break;case 3: DisplayBuffer[0] |= 0x03<<Y; DisplayBuffer[1] |= 0x03<<Y; break;default:break;}}if(X==1){switch(Type){case 0: DisplayBuffer[3] |= 0x02<<Y; break;case 1: DisplayBuffer[3] |= 0x03<<Y; break;case 2: DisplayBuffer[3] |= 0x03<<Y; DisplayBuffer[4] |= 0x02<<Y; break;case 3: DisplayBuffer[3] |= 0x03<<Y; DisplayBuffer[4] |= 0x03<<Y; break;default:break;}}if(X==2){switch(Type){case 0: DisplayBuffer[6] |= 0x02<<Y; break;case 1: DisplayBuffer[6] |= 0x03<<Y; break;case 2: DisplayBuffer[6] |= 0x03<<Y; DisplayBuffer[7] |= 0x02<<Y; break;case 3: DisplayBuffer[6] |= 0x03<<Y; DisplayBuffer[7] |= 0x03<<Y; break;default:break;}}
}/*** 函    数:MatrixLED在指定位置清除一个方块,方块大小:2X2* 参    数:X 方块位置,范围:0~2(0:1、2列, 1:4、5列, 2:7、8列)* 参    数:Y 方块位置,范围:0~6(0:1、2行, 1:2、3行, ..., 6:7、8行)* 返 回 值:无*/
void MatrixLED_ClearBlock(unsigned char X,unsigned char Y)
{if(X==0){DisplayBuffer[0] &= ~(0x03<<Y);DisplayBuffer[1] &= ~(0x03<<Y);}if(X==1){DisplayBuffer[3] &= ~(0x03<<Y);DisplayBuffer[4] &= ~(0x03<<Y);}if(X==2){DisplayBuffer[6] &= ~(0x03<<Y);DisplayBuffer[7] &= ~(0x03<<Y);}
}

2、矩阵按键

h文件

#ifndef __MATRIXKEYSCAN_H__
#define __MATRIXKEYSCAN_H__unsigned char MatrixKey(void);
void MatrixKey_Tick(void);#endif

c文件

#include <REGX52.H>#define Matrix_Port P1	//矩阵按键接口unsigned char KeyNumber;/*** 函    数:获取矩阵按键键码* 参    数:无* 返 回 值:按下按键的键码,范围:0~48,0表示无按键按下* 说    明:在下一次检测按键之前,第二次获取键码一定会返回0*/
unsigned char MatrixKey(void)
{unsigned char KeyTemp=0;KeyTemp=KeyNumber;KeyNumber=0;return KeyTemp;
}/*** 函    数:检测当前按下的按键,无消抖及松手检测* 参    数:无* 返 回 值:按下按键的键值,范围:0~16,无按键按下时返回值为0*/
unsigned char Key_GetState()
{unsigned char KeyValue=0;unsigned char i=0;Matrix_Port=0x0F;	//给所有行赋值0,列全为1i=2;while(i--);	//适当延时,延时5usif(Matrix_Port!=0x0F){Matrix_Port=0x0F;	//测试列i=2;while(i--);switch(Matrix_Port)	//所有行拉低,检测哪一列按下{case 0x07:KeyValue=1;break;case 0x0B:KeyValue=2;break;case 0x0D:KeyValue=3;break;case 0x0E:KeyValue=4;break;default:break;}Matrix_Port=0xF0;	//测试行i=2;while(i--);switch(Matrix_Port)	//所有列拉低,检测哪一行按下{case 0x70:KeyValue=KeyValue;break;case 0xB0:KeyValue=KeyValue+4;break;case 0xD0:KeyValue=KeyValue+8;break;case 0xE0:KeyValue=KeyValue+12;break;default:break;}}else{KeyValue=0;}return KeyValue;
}/*** 函    数:矩阵按键驱动函数,在中断中调用* 参    数:无* 返 回 值:无*/
void MatrixKey_Tick(void)
{static unsigned char NowState,LastState;static unsigned int KeyCount;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;case 9:KeyNumber=9;break;case 10:KeyNumber=10;break;case 11:KeyNumber=11;break;case 12:KeyNumber=12;break;case 13:KeyNumber=13;break;case 14:KeyNumber=14;break;case 15:KeyNumber=15;break;case 16:KeyNumber=16;break;default:break;}}//如果上个时间点按键按下,这个时间点按键还是按下,则是长按if(LastState && NowState){KeyCount++;if(KeyCount%50==0)	//定时器中断函数中每隔20ms检测一次按键{	//长按后每隔1s返回一次长按的键码if     (LastState== 1 && NowState== 1){KeyNumber=17;}else if(LastState== 2 && NowState== 2){KeyNumber=18;}else if(LastState== 3 && NowState== 3){KeyNumber=19;}else if(LastState== 4 && NowState== 4){KeyNumber=20;}else if(LastState== 5 && NowState== 5){KeyNumber=21;}else if(LastState== 6 && NowState== 6){KeyNumber=22;}else if(LastState== 7 && NowState== 7){KeyNumber=23;}else if(LastState== 8 && NowState== 8){KeyNumber=24;}else if(LastState== 9 && NowState== 9){KeyNumber=25;}else if(LastState==10 && NowState==10){KeyNumber=26;}else if(LastState==11 && NowState==11){KeyNumber=27;}else if(LastState==12 && NowState==12){KeyNumber=28;}else if(LastState==13 && NowState==13){KeyNumber=29;}else if(LastState==14 && NowState==14){KeyNumber=30;}else if(LastState==15 && NowState==15){KeyNumber=31;}else if(LastState==16 && NowState==16){KeyNumber=32;}}}else{KeyCount=0;}//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间if(NowState==0){switch(LastState){case 1:KeyNumber=33;break;case 2:KeyNumber=34;break;case 3:KeyNumber=35;break;case 4:KeyNumber=36;break;case 5:KeyNumber=37;break;case 6:KeyNumber=38;break;case 7:KeyNumber=39;break;case 8:KeyNumber=40;break;case 9:KeyNumber=41;break;case 10:KeyNumber=42;break;case 11:KeyNumber=43;break;case 12:KeyNumber=44;break;case 13:KeyNumber=45;break;case 14:KeyNumber=46;break;case 15:KeyNumber=47;break;case 16:KeyNumber=48;break;default:break;}}}

3、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0_Init(void);#endif

c文件

#include <REGX52.H>/*** 函    数:定时器0初始化* 参    数:无* 返 回 值:无*/
void Timer0_Init(void)
{	TMOD&=0xF0;	//设置定时器模式(高四位不变,低四位清零)TMOD|=0x01;	//设置定时器模式(通过低四位设为16位不自动重装)TL0=0x66;	//设置定时初值,定时1ms,12T@11.0592MHzTH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzTF0=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=0x66;	//设置定时初值,定时1ms,12T@11.0592MHzTH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzT0Count++;if(T0Count>=1000){T0Count=0;}
}
*/

4、定时器1

h文件

#ifndef __TIMER1_H__
#define __TIMER1_H__void Timer1_Init(void);#endif

c文件

#include <REGX52.H>/*** 函    数:定时器1初始化* 参    数:无* 返 回 值:无*/
void Timer1_Init(void)
{TMOD&=0x0F;	//设置定时器模式(低四位不变,高四位清零)TMOD|=0x10;	//设置定时器模式(通过高四位设为16位不自动重装的模式)TL1=0x66;	//设置定时初值,定时1ms,12T@11.0592MHzTH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzTF1=0;	//清除TF1标志TR1=1;	//定时器1开始计时ET1=1;	//打开定时器1中断允许EA=1;	//打开总中断PT1=1;	//当PT1=0时,定时器1为低优先级,当PT1=1时,定时器1为高优先级
}/*定时器中断函数模板
void Timer1_Routine() interrupt 3	//定时器1中断函数
{static unsigned int T1Count;	//定义静态变量TL1=0x66;	//设置定时初值,定时1ms,12T@11.0592MHzTH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzT1Count++;if(T1Count>=1000){T1Count=0;}
}
*/

四、主函数

main.c

/*by甘腾胜@20250503
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8LED点阵屏、矩阵按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】点阵屏旁边的跳线帽要接三个排针的左边两个
【操作说明】
(1)显示游戏英文名的界面按任意按键进入游戏
(2)S10、S14、S13、S15四个按键分别是上、下、左、右
(3)按下S16(确认键)后,方块会显示是什么类型(1~3个点)
(4)游戏结束全屏闪烁界面按S16进入显示游戏用时的英文的界面
(5)显示游戏用时的英文的界面可按S16跳过
(6)循环显示游戏用时的界面可按S12返回重新开始游戏
*/#include <REGX52.H>	//51单片机头文件
#include "MatrixLED.h"	//8X8点阵屏
#include "MatrixKeyScan.h"	//矩阵按键
#include "Timer0.h"		//定时器0
#include "Timer1.h"		//定时器1
#include <STDLIB.H>		//随机函数unsigned char KeyNum;	//存储获取的键码
unsigned char Mode;	//游戏模式,0:循环滚动显示游戏英文名,1:游戏中,2:游戏结束全屏闪烁,3:滚动显示游戏用时的英文,4:循环滚动显示游戏用时
bit OnceFlag;	//各模式中切换为其他模式前只执行一次的标志,用于模式的初始化,功能类似于while(1)前面那部分,1:执行,0:不执行
unsigned char Offset;	//偏移量,用来控制英文字母或数字向左滚动显示
bit RollFlag;	//字母滚动一个像素的标志,1:滚动,0:不滚动
bit GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
bit FlashFlag;	//闪烁的标志,1:不显示,0:显示
unsigned char Select;	//方块选择变量,范围:0~5
unsigned char T0Count;	//定时器0计数全局变量
unsigned char idata TypeForPick[]={0,1,2,0,1,2};	//六个方块(三对)的类型,用来将这三对方块随机打乱顺序保存到数组Type中
unsigned char idata Type[]={0,1,1,2,0,2};	//六个方块的类型(idata:变量保存在片内间接访问区)
unsigned char idata IsShow[]={1,1,1,1,1,1};	//六个方块是否显示的标志,1:显示,0:不显示
unsigned char ConfirmFlag;	//按下确认键的标志,0:未确认,1:第一次确认,2:第二次确认
unsigned char FirstConfirm=6;	//第一次确认的方块,范围:0~5,初始值为0~5以外的任意一个数字,并要求FirstConfirm和SecondConfirm的初始值不一样
unsigned char SecondConfirm=7;	//第二次确认的方块,范围:0~5,初始值为0~5以外的任意一个数字,并要求FirstConfirm和SecondConfirm的初始值不一样
unsigned int UsedTime;	//全部配对所用的时间,单位:100ms,范围:0~6553.5s
bit NoFlashFlag;	//翻开两个方块后暂不闪烁的标志,1:不闪烁,0:闪烁unsigned char idata UsedTimeShow[]={	//游戏用时,用于游戏结束后循环滚动显示
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x00,0x00,0x00,0x00,0x00,	// 用时的十位
0x00,0x00,0x00,0x00,0x00,0x00,	// 用时的个位
0x00,0x00,0x60,0x60,0x00,0x00,	// 用时的小数点
0x00,0x00,0x00,0x00,0x00,0x00,	// 用时的十分位
0x00,0x48,0x54,0x54,0x54,0x20,	// “秒”的英文缩写:小写的“s”
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};//取模要求:阴码(亮点为1),纵向取模,高位在下
//我分享的工程文件夹中有6X8像素的ASCII字符字模
unsigned char code Table1[]={	//游戏名称的英文:<<MAKE PAIRS>>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x08,0x14,0x22,0x49,0x14,0x22,0x41,	// <<	宽8高8(自定义书名号:两个小于号)
0x00,0x7F,0x02,0x0C,0x02,0x7F,	// M
0x00,0x7C,0x12,0x11,0x12,0x7C,	// A
0x00,0x7F,0x08,0x14,0x22,0x41,	// K
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x00,0x00,0x00,0x00,0x00,	//  
0x00,0x7F,0x09,0x09,0x09,0x06,	// P
0x00,0x7C,0x12,0x11,0x12,0x7C,	// A
0x00,0x00,0x41,0x7F,0x41,0x00,	// I
0x00,0x7F,0x09,0x19,0x29,0x46,	// R
0x00,0x46,0x49,0x49,0x49,0x31,	// S
0x00,0x41,0x22,0x14,0x49,0x22,0x14,0x08,	// >>(自定义书名号:两个大于号)
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};
unsigned char code Table2[]={	//“所用时间”的英文:“USED TIME”,宽6高8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	//无显示
0x00,0x3F,0x40,0x40,0x40,0x3F,	// U
0x00,0x46,0x49,0x49,0x49,0x31,	// S
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x00,0x00,0x00,0x00,0x00,	//  
0x00,0x01,0x01,0x7F,0x01,0x01,	// T
0x00,0x00,0x41,0x7F,0x41,0x00,	// I
0x00,0x7F,0x02,0x0C,0x02,0x7F,	// M
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	//无显示
};
unsigned char code Table3[]={	//游戏得分的字模数据,宽6高8
0x00,0x3E,0x51,0x49,0x45,0x3E,	// 0
0x00,0x00,0x42,0x7F,0x40,0x00,	// 1
0x00,0x42,0x61,0x51,0x49,0x46,	// 2
0x00,0x21,0x41,0x45,0x4B,0x31,	// 3
0x00,0x18,0x14,0x12,0x7F,0x10,	// 4
0x00,0x27,0x45,0x45,0x45,0x39,	// 5
0x00,0x3C,0x4A,0x49,0x49,0x30,	// 6
0x00,0x01,0x71,0x09,0x05,0x03,	// 7
0x00,0x36,0x49,0x49,0x49,0x36,	// 8
0x00,0x06,0x49,0x49,0x29,0x1E,	// 9
};/*** 函    数:主函数(有且仅有一个)* 参    数:无* 返 回 值:无* 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌*/
void main()
{unsigned char i,j,k;P2_5=0;	//防止开发板上的蜂鸣器发出声音Timer0_Init();  //定时器0初始化Timer1_Init();  //定时器1初始化MatrixLED_Init();	//点阵屏初始化while(1){KeyNum=MatrixKey();	//获取独立按键的键码/*键码处理*/if(KeyNum){srand(TL0);	//每次获取非0的键码值后用定时器0的低8位做种子,从而产生真随机数if(Mode==0)	//如果是循环滚动显示游戏英文名“<<MAKE PAIRS>>”的界面{if(KeyNum>=33 && KeyNum<=48)	//如果按下任意按键(松手瞬间){Mode=1;	//切换到模式1OnceFlag=1;	//切换模式前只执行一次的标志置1}}else if(Mode==1)	//如果是游戏界面{if(KeyNum==47)	//如果按了右键S15(松手瞬间){if(Select/3==0)	//如果是第一行{Select++;	//变量自增Select%=3;	//第一行对应的Select的范围是0~2,用于按左右键时循环选择while(IsShow[Select%3]==0)	//如果对应的方块已经消除(不显示了),则往右选择下一个{Select++;Select%=3;}}else if(Select/3==1)	//如果是第二行{Select++;	//变量自增Select=Select%3+3;	//第二行对应的Select的范围是3~5,用于按左右键时循环选择while(IsShow[Select%3+3]==0)	//如果对应的方块已经消除(不显示了),则往右选择下一个{Select++;Select=Select%3+3;}}}if(KeyNum==45)	//如果按了左键S13(松手瞬间){if(Select/3==0){Select=(Select+3-1)%3;while(IsShow[Select%3]==0){Select=(Select+3-1)%3;}}else if(Select/3==1){Select=(Select+3-1)%3+3;while(IsShow[Select%3+3]==0){Select=(Select+3-1)%3+3;}}}if(KeyNum==42 || KeyNum==46)	//如果按了下键S14(松手瞬间)或者按了上键S10(松手瞬间){if(IsShow[(Select+3)%6])	//如果另外一行正对的方块还未消除{Select=(Select+3)%6;	//则选择变量改成正对的这个方块}else	//如果另外一行正对的方块已经消除{if(Select/3==0)	//如果原来是第一行{if(IsShow[3]+IsShow[4]+IsShow[5])	//如果第二行还有方块未消除{if(Select%3==2)	//如果原来在第一行第三列{while(IsShow[Select%3+3]==0)	//则换成第二行后向左寻找第一个未消除的方块{Select=(Select+3-1)%3+3;}}else	//如果原来不是在第一行第三列{while(IsShow[Select%3+3]==0)	//则换成第二行后向右寻找第一个未消除的方块{Select++;Select=Select%3+3;}}}}else if(Select/3==1)	//如果原来是第二行{if(IsShow[0]+IsShow[1]+IsShow[2])	//如果第一行还有方块未消除{if(Select%3==2)	//如果原来在第二行第三列{while(IsShow[Select%3]==0)	//则换成第一行后向左寻找第一个未消除的方块{Select=(Select+3-1)%3;}}else	//如果原来不是在第二行第三列{while(IsShow[Select%3]==0)	//则换成第一行后向右寻找第一个未消除的方块{Select++;Select%=3;}}}}}}if(KeyNum==48)	//如果按了确认键S16(松手瞬间){if(ConfirmFlag!=1 || Select!=FirstConfirm)	//第一次确认的和第二次确认的必须是不同的方块{ConfirmFlag++;ConfirmFlag%=3;	//确认标志ConfirmFlag的范围是0~2}if(ConfirmFlag==0){if(NoFlashFlag)	//如果第二次确认后暂不闪烁的标志为真{ConfirmFlag=0;	//确认的标志置0NoFlashFlag=0;	//暂不闪烁的标志置0if(Type[FirstConfirm]==Type[SecondConfirm])	//如果两次确认的方块是相同的类型{	//则这两个方块不再显示IsShow[FirstConfirm]=0;IsShow[SecondConfirm]=0;}}FirstConfirm=6;		//FirstConfirm和SecondConfirm必须为0~5以外的两个不等的数SecondConfirm=7;	//FirstConfirm和SecondConfirm必须为0~5以外的两个不等的数}if(ConfirmFlag==1){FirstConfirm=Select;	//保存第一次确认对应的方块}if(ConfirmFlag==2){SecondConfirm=Select;	//保存第二次确认对应的方块T0Count=0;	//定时器0的全局计数变量清零NoFlashFlag=1;	//第二次确认后暂不闪烁的标志置1}}if(KeyNum==42 || KeyNum==46 || KeyNum==45 || KeyNum==47)	//如果按了上、下、左或右键{if(NoFlashFlag)	//如果第二次确认后暂不闪烁的标志为真{ConfirmFlag=0;	//确认的标志置0NoFlashFlag=0;	//暂不闪烁的标志置0if(Type[FirstConfirm]==Type[SecondConfirm])	//如果两次确认的方块是相同的类型{	//则这两个方块不再显示IsShow[FirstConfirm]=0;IsShow[SecondConfirm]=0;}FirstConfirm=6;		//FirstConfirm和SecondConfirm必须为0~5以外的两个不等的数SecondConfirm=7;	//FirstConfirm和SecondConfirm必须为0~5以外的两个不等的数}}MatrixLED_Clear();	//清屏}else if(Mode==2)	//如果是游戏结束全屏闪烁{if(KeyNum==48)	//如果按下S16(松手瞬间){Mode=3;OnceFlag=1;}}else if(Mode==3)	//如果是滚动显示游戏用时的英文“USED TIME”的界面{if(KeyNum==48)	//如果按下S16(松手瞬间){Mode=4;OnceFlag=1;}}else if(Mode==4)	//如果是循环滚动显示游戏用时的界面{if(KeyNum==44)	//如果按下S12(松手瞬间){Mode=1;	//重新开始游戏OnceFlag=1;}}}/*游戏处理*/if(Mode==0)	//循环滚动显示游戏英文名{if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次{OnceFlag=0;	//只执行一次的标志清零Offset=0;	//滚动显示的偏移量清零}if(RollFlag)	//如果滚动的标志RollFlag为真(非零即真){RollFlag=0;	//滚动的标志RollFlag清零MatrixLED_MoveLeft(Table1,Offset);	//向左滚动Offset++;	//每次向左移动一个像素Offset%=84;	//越界清零,循环滚动显示}}else if(Mode==1)	//游戏进行中{if(OnceFlag){OnceFlag=0;//游戏初始化MatrixLED_Clear();	//清屏ConfirmFlag=0;	//确认的标志置0Select=0;	//方块选择变量置0for(i=0;i<6;i++)	//全部方块都显示{IsShow[i]=1;}TypeForPick[0]=0;TypeForPick[1]=1;TypeForPick[2]=2;TypeForPick[3]=0;TypeForPick[4]=1;TypeForPick[5]=2;	//数组TypeForPick的值重置for(i=0;i<6;i++)	//将数组TypeForPick随机打乱放入数组Type中{j=rand()%(6-i);Type[i]=TypeForPick[j];for(k=j;k<6-i-1;k++){TypeForPick[k]=TypeForPick[k+1];}}UsedTime=0;	//游戏用时清零GameOverFlag=0;	//游戏结束的标志置0MatrixLED_DrawBlock(0,1,3);MatrixLED_DrawBlock(1,1,3);MatrixLED_DrawBlock(2,1,3);MatrixLED_DrawBlock(0,4,3);MatrixLED_DrawBlock(1,4,3);MatrixLED_DrawBlock(2,4,3);	//显示六个“未翻转”的方块}if(IsShow[0]+IsShow[1]+IsShow[2]+IsShow[3]+IsShow[4]+IsShow[5])	//如果还有方块未消除{if(IsShow[0]+IsShow[1]+IsShow[2]==0 && Select/3==0){Select=3;}	//如果第一行全部都消除了,则选择第二行的第一个if(IsShow[3]+IsShow[4]+IsShow[5]==0 && Select/3==1){Select=0;}	//如果第二行全部都消除了,则选择第一行的第一个if(Select/3==0)	//如果是第一行{while(IsShow[Select%3]==0)	//向右选择第一个未消除的方块{Select++;Select%=3;}}else if(Select/3==1)	//如果是第二行{while(IsShow[Select%3+3]==0)	//向右选择第一个未消除的方块{Select++;Select=Select%3+3;}}}else	//如果所有方块都消除了{GameOverFlag=1;	//则游戏结束}if(ConfirmFlag==0)	//如果确认标志为0{if( (Select==0 && FlashFlag) || IsShow[0]==0){MatrixLED_ClearBlock(0,1);}else{MatrixLED_DrawBlock(0,1,3);}if( (Select==1 && FlashFlag) || IsShow[1]==0){MatrixLED_ClearBlock(1,1);}else{MatrixLED_DrawBlock(1,1,3);}if( (Select==2 && FlashFlag) || IsShow[2]==0){MatrixLED_ClearBlock(2,1);}else{MatrixLED_DrawBlock(2,1,3);}if( (Select==3 && FlashFlag) || IsShow[3]==0){MatrixLED_ClearBlock(0,4);}else{MatrixLED_DrawBlock(0,4,3);}if( (Select==4 && FlashFlag) || IsShow[4]==0){MatrixLED_ClearBlock(1,4);}else{MatrixLED_DrawBlock(1,4,3);}if( (Select==5 && FlashFlag) || IsShow[5]==0){MatrixLED_ClearBlock(2,4);}else{MatrixLED_DrawBlock(2,4,3);}			}else if(ConfirmFlag==1)	//如果确认标志为1{	//则闪烁显示选择的方块,其他的不闪烁,且已经“翻转”的方块显示对应的类型if( (Select==0 && FlashFlag) || IsShow[0]==0){MatrixLED_ClearBlock(0,1);}else if(FirstConfirm==0){MatrixLED_DrawBlock(0,1,Type[0]);}else{MatrixLED_DrawBlock(0,1,3);}if( (Select==1 && FlashFlag) || IsShow[1]==0){MatrixLED_ClearBlock(1,1);}else if(FirstConfirm==1){MatrixLED_DrawBlock(1,1,Type[1]);}else{MatrixLED_DrawBlock(1,1,3);}if( (Select==2 && FlashFlag) || IsShow[2]==0){MatrixLED_ClearBlock(2,1);}else if(FirstConfirm==2){MatrixLED_DrawBlock(2,1,Type[2]);}else{MatrixLED_DrawBlock(2,1,3);}if( (Select==3 && FlashFlag) || IsShow[3]==0){MatrixLED_ClearBlock(0,4);}else if(FirstConfirm==3){MatrixLED_DrawBlock(0,4,Type[3]);}else{MatrixLED_DrawBlock(0,4,3);}if( (Select==4 && FlashFlag) || IsShow[4]==0){MatrixLED_ClearBlock(1,4);}else if(FirstConfirm==4){MatrixLED_DrawBlock(1,4,Type[4]);}else{MatrixLED_DrawBlock(1,4,3);}if( (Select==5 && FlashFlag) || IsShow[5]==0){MatrixLED_ClearBlock(2,4);}else if(FirstConfirm==5){MatrixLED_DrawBlock(2,4,Type[5]);}else{MatrixLED_DrawBlock(2,4,3);}			}else if(ConfirmFlag==2)	//如果确认标志为2{if(NoFlashFlag)	//如果暂不闪烁的标志为真{	//则6个方块均不闪烁if(IsShow[0]==0){MatrixLED_ClearBlock(0,1);}else if(FirstConfirm==0 || SecondConfirm==0){MatrixLED_DrawBlock(0,1,Type[0]);}else{MatrixLED_DrawBlock(0,1,3);}if(IsShow[1]==0){MatrixLED_ClearBlock(1,1);}else if(FirstConfirm==1 || SecondConfirm==1){MatrixLED_DrawBlock(1,1,Type[1]);}else{MatrixLED_DrawBlock(1,1,3);}if(IsShow[2]==0){MatrixLED_ClearBlock(2,1);}else if(FirstConfirm==2 || SecondConfirm==2){MatrixLED_DrawBlock(2,1,Type[2]);}else{MatrixLED_DrawBlock(2,1,3);}if(IsShow[3]==0){MatrixLED_ClearBlock(0,4);}else if(FirstConfirm==3 || SecondConfirm==3){MatrixLED_DrawBlock(0,4,Type[3]);}else{MatrixLED_DrawBlock(0,4,3);}if(IsShow[4]==0){MatrixLED_ClearBlock(1,4);}else if(FirstConfirm==4 || SecondConfirm==4){MatrixLED_DrawBlock(1,4,Type[4]);}else{MatrixLED_DrawBlock(1,4,3);}if(IsShow[5]==0){MatrixLED_ClearBlock(2,4);}else if(FirstConfirm==5 || SecondConfirm==5){MatrixLED_DrawBlock(2,4,Type[5]);}else{MatrixLED_DrawBlock(2,4,3);}			}else	//如果暂不闪烁的标志为0{ConfirmFlag=0;	//确认的标志置0if(Type[FirstConfirm]==Type[SecondConfirm])	//如果“翻开”的两个方块的类型相同{	//则这两个方块不再显示IsShow[FirstConfirm]=0;IsShow[SecondConfirm]=0;}}}if(GameOverFlag)	//如果游戏结束的标志为真{Mode=2;	//切换到全屏闪烁的模式OnceFlag=1;}}else if(Mode==2)	//游戏结束全屏闪烁{if(OnceFlag){OnceFlag=0;MatrixLED_DrawBlock(0,1,Type[0]);MatrixLED_DrawBlock(1,1,Type[1]);MatrixLED_DrawBlock(2,1,Type[2]);MatrixLED_DrawBlock(0,4,Type[3]);MatrixLED_DrawBlock(1,4,Type[4]);MatrixLED_DrawBlock(2,4,Type[5]);	//闪烁显示6个方块的类型}//在定时器1中实现全屏闪烁}else if(Mode==3)	//滚动显示英文“USED TIME”{if(OnceFlag){OnceFlag=0;Offset=0;}if(RollFlag && Offset<=62)	//只滚动显示一次英文“SCORE”{RollFlag=0;MatrixLED_MoveLeft(Table2,Offset);Offset++;}else if(Offset>62) //滚动结束后,自动切换到循环显示得分的模式{Mode=4;OnceFlag=1;}	}else if(Mode==4)	//循环滚动显示得分{if(OnceFlag){OnceFlag=0;Offset=0;//将游戏用时对应的字模写入数组UsedTimeShow中for(i=0;i<6;i++){UsedTimeShow[ 8+i]=Table3[(UsedTime/100%10)*6+i];	//十位}for(i=0;i<6;i++){UsedTimeShow[14+i]=Table3[(UsedTime/10%10)*6+i];	//个位}for(i=0;i<6;i++){UsedTimeShow[26+i]=Table3[(UsedTime%10)*6+i];	//十分位}}if(RollFlag){RollFlag=0;MatrixLED_MoveLeft(UsedTimeShow,Offset);Offset++;Offset%=38;	//循环滚动显示}}}
}/*** 函    数:定时器0中断函数* 参    数:无* 返 回 值:无*/
void Timer0_Routine() interrupt 1
{static unsigned char T0Count1,T0Count2,T0Count3,T0Count4;	//定时器计数变量TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHzTH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHzT0Count1++;T0Count2++;T0Count3++;T0Count++;if(T0Count1>=2)	//每隔20ms检测一次键码{T0Count1=0;MatrixKey_Tick();}if(T0Count2>=25)	//每隔250ms置反FlashFlag{T0Count2=0;FlashFlag=!FlashFlag;}if(T0Count3>=10)	//每隔100ms滚动显示一次字母或数字{T0Count3=0;RollFlag=1;}if(GameOverFlag==0)	//如果游戏没结束{T0Count4++;if(T0Count4>=10)	//每隔100ms将游戏用时变量UsedTime加1{T0Count4=0;UsedTime++;}}else	//如果游戏结束{T0Count4=0;}if(T0Count>=100)	//第二次确认后1s内不闪烁{T0Count=0;NoFlashFlag=0;}
}/*** 函    数:定时器1中断函数* 参    数:无* 返 回 值:无* 说    明:专门用定时器1来扫描显示LED点阵屏,定时器1的优先级要比定时器0的高,否则显示会有闪烁现象*/
void Timer1_Routine() interrupt 3
{TL1=0x66;	//设置定时初值,定时1ms,12T@11.0592MHzTH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzif(Mode==2 && FlashFlag){P0=0xFF;}	//控制游戏结束后的全屏闪烁else{MatrixLED_Tick();}
}

总结

此小游戏难在消除后,有些方块不显示了,需要对按键的选择做一些处理,跳过不显示的方块,也难在3对方块类型的随机排列。

http://www.xdnf.cn/news/6709.html

相关文章:

  • 服务器性能参数分析基础:磁盘-CPU-内存
  • 关于如何本地启动xxl-job,并且整合SpringBoot
  • 最新模型集合(仅用于个人收集)
  • 前端批量下载文件打包为zip
  • 【Unity】用事件广播的方式实现游戏暂停,简单且实用!
  • 5月16日day27打卡
  • LED接口设计
  • R语言学习--Day03--数据清洗技巧
  • day32-多线程juc
  • QML元素 - OpacityMask
  • [BJDCTF2020]The mystery of ip
  • Python 在自动驾驶数据标签中的应用:如何让 AI 读懂道路?
  • 2025年山东省省赛数模竞赛C题完整论文+代码分享
  • 【动态导通电阻】GaN HEMT动态导通电阻的精确测量
  • 罗杰斯高频板技术解析:低损耗基材如何定义 5G 通信未来
  • tauri2项目使用tauri-plugin-updater配置更新程序流程
  • 如何阅读、学习 Tcc (Tiny C Compiler) 源代码?如何解析 Tcc 源代码?
  • VsCode和AI的前端使用体验:分别使用了Copilot、通义灵码、iflyCode和Trae
  • iOS音视频解封装分析
  • Spring Batch学习,和Spring Cloud Stream区别
  • MySQL面试知识点详解
  • 计算机图形学基础--Games101笔记(一)数学基础与光栅化
  • 生产级编排AI工作流套件:Flyte全面使用指南 — Core concepts Launch plans
  • 非受控组件在 React 中如何进行状态更新?
  • 好用的拓客APP有哪些?
  • C#学习第23天:面向对象设计模式
  • 基于WISE30sec制作中国1km分辨率土壤属性栅格数据(20种属性/0-200cm深度分层)
  • Flask框架搭建
  • 信号灯和旋钮在接地电阻柜内的作用主要包括以下几个方面
  • 吴恩达 Deep Learning(1-36)ppt逐行理解