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

基于STC89C52RC和8X8点阵屏、独立按键的匹配消除类小游戏

目录

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

系列文章目录


前言

用的是普中A2开发板,用到的外设有:8X8LED点阵屏、独立按键。

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

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

一、效果展示

1、通过按按键使两组方块一一匹配。
在这里插入图片描述

2、按确认键发射玩家方块,如果全部匹配,则消除方块,否则不消除。
在这里插入图片描述

3、需要匹配的方块到达底部游戏结束,循环滚动显示得分,消除几次就得几分。(每消除两次,下落的时间间隔会减少0.1s,即下落的速度会变快,可以测试自己的极限)
在这里插入图片描述

二、原理分析

1、方块的显示与清除显示

需要针对此游戏写两个专门的函数,来实现方块的显示与清除显示。

开发板上是通过74HC595和P0口控制LED的亮灭的,由定时器扫描显示,只需要修改显示缓存(8个字节)即可自动更新显示。

所以画面的改变其实就是对显示缓存的8个字节进行操作,通过或、与、移位等操作可以随意改变8个字节对应的64位的任意一位的值,来实现任意一个LED的亮灭控制。

2、方块的移动

如何实现方块的移动呢?

很简单,移动之前,先清除显示,方块的位置变量改变之后,再重新显示。

3、方块的匹配判断

总共两组方块,一组共有三个方块,共6个方块。玩家3个方块,正在下落的需要匹配的也有3个方块。用六个变量存储方块的类型,玩家的是Type1、Type2、Type3,需要匹配的是Type4、Type5、Type6,两组方块重合时,判断Type1和Type4、Type2和Type5、Type3和Type6是否分别相等,就可以知道是否一一匹配了。

三、各模块代码

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:方块的左下角的LED点亮,1:方块的左边两个LED点亮,2:左边两个LED和右下角的LED点亮,3:全部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 __KEYSCAN_H__
#define __KEYSCAN_H__extern unsigned char KeyNumber1;
unsigned char Key(void);
void Key_Tick(void);#endif

c文件

#include <REGX52.H>sbit Key1=P3^1;
sbit Key2=P3^0;
sbit Key3=P3^2;
sbit Key4=P3^3;unsigned char KeyNumber;/*** 函    数:获取独立按键键码* 参    数:无* 返 回 值:按下按键的键码,范围:0~12,0表示无按键按下* 说    明:在下一次检测按键之前,第二次获取键码一定会返回0*/
unsigned char Key(void)
{unsigned char KeyTemp=0;KeyTemp=KeyNumber;KeyNumber=0;return KeyTemp;
}/*** 函    数:按键驱动函数,在中断中调用* 参    数:无* 返 回 值:无*/
void Key_Tick(void)
{static unsigned char NowState,LastState;static unsigned int KeyCount;LastState=NowState;	//保存上一次的按键状态NowState=0;	//如果没有按键按下,则NowState为0//获取当前按键状态if(Key1==0){NowState=1;}if(Key2==0){NowState=2;}if(Key3==0){NowState=3;}if(Key4==0){NowState=4;}//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间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;default:break;}}//如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键if(LastState && NowState){KeyCount++;if(KeyCount%5==0)	//定时器中断函数中每隔20ms检测一次按键{	//长按后每隔100ms返回一次长按的键码if     (LastState==1 && NowState==1){KeyNumber=5;}else if(LastState==2 && NowState==2){KeyNumber=6;}else if(LastState==3 && NowState==3){KeyNumber=7;}else if(LastState==4 && NowState==4){KeyNumber=8;}}}else{KeyCount=0;}//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间if(NowState==0){switch(LastState){case 1:KeyNumber=9;break;case 2:KeyNumber=10;break;case 3:KeyNumber=11;break;case 4:KeyNumber=12;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甘腾胜@20250420
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8LED点阵屏、独立按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【操作说明】
(1)显示游戏英文名的界面按任意按键进入游戏
(2)K1、K2、K3三个按键分别用来更改玩家的三个方块的形状
(3)按下K4(确认键)后,玩家方块会快速向上移动,直至跟需要匹配的方块重合
(4)游戏结束全屏闪烁界面按K4进入显示得分的英文的界面
(5)显示得分的英文的界面可按K4跳过
(6)循环显示得分界面可按K3返回重新开始游戏
*/#include <REGX52.H>
#include "MatrixLED.h"	//8X8点阵屏
#include "KeyScan.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;	//各模式中切换为其他模式前只执行一次的标志(类似于主函数死循环前的那部分),1:执行,0:不执行
bit AllowChangeModeFlag=1;	//允许改变模式的标志,1:允许改变,0:不允许改变
bit MoveFlag;	//方块下落一个像素的标志,1:移动,0:不移动
unsigned char Offset;	//偏移量,用来控制英文字母向左滚动显示
bit RollFlag;	//字母滚动一个像素的标志,1:滚动,0:不滚动
unsigned int MoveSpeed=200;	//球移动的速度(其实是移动的时间间隔),值越小,速度越快,单位是10ms(定时器0定时10ms),初始2s移动一次
bit GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
bit FlashFlag;	//闪烁的标志,1:不显示,0:显示
unsigned char Score;	//游戏得分
//bit PauseFlag;	//暂停的标志,1:暂停,0:继续	此游戏不设置暂停的功能
unsigned char T0Count;	//定时器0计数全局变量
unsigned char Type1,Type2,Type3;	//玩家的三个方块的类型
unsigned char Type4,Type5,Type6;	//需要匹配的三个方块的类型
unsigned char Y2=0;	//正在下降的方块的Y坐标,范围:0~6,初始值为0
unsigned char Y1=6;	//玩家的方块的Y坐标,范围:0~6,初始值为6
bit ConfirmFlag;	//按下确认键的标志,1:确认,0:未确认
bit MatchFlag;	//匹配正确的标志,1:正确,0:不正确
bit UpFlag;	//玩家方块向上移动一个像素的标志,1:移动,0:不移动
unsigned char idata ScoreShow[]={	//二位数游戏得分(用于滚动显示)
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x00,0x00,0x00,0x00,0x00,	// 得分的十位
0x00,0x00,0x00,0x00,0x00,0x00,	// 得分的个位
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};//取模要求:阴码(亮点为1),纵向取模,高位在下
//我分享的工程文件夹中有6X8像素的ASCII字符字模
unsigned char code Table1[]={	//游戏名称“匹配或死亡”的英文:<<MATCH OR DIE>>
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,0x01,0x01,0x7F,0x01,0x01,	// T
0x00,0x3E,0x41,0x41,0x41,0x22,	// C
0x00,0x7F,0x08,0x08,0x08,0x7F,	// H
0x00,0x00,0x00,0x00,0x00,0x00,	//  
0x00,0x3E,0x41,0x41,0x41,0x3E,	// O
0x00,0x7F,0x09,0x19,0x29,0x46,	// R
0x00,0x00,0x00,0x00,0x00,0x00,	//  
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x00,0x41,0x7F,0x41,0x00,	// I
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x41,0x22,0x14,0x49,0x22,0x14,0x08,	// >>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};
unsigned char code Table2[]={	//“得分”的英文:“SCORE”,宽6高8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	//无显示
0x00,0x46,0x49,0x49,0x49,0x31,	// S
0x00,0x3E,0x41,0x41,0x41,0x22,	// C
0x00,0x3E,0x41,0x41,0x41,0x3E,	// O
0x00,0x7F,0x09,0x19,0x29,0x46,	// R
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;P2_5=0;	//防止开发板上的蜂鸣器发出声音Timer0_Init();  //定时器0初始化Timer1_Init();  //定时器1初始化MatrixLED_Init();	//点阵屏初始化while(1){KeyNum=Key();	//获取独立按键的键码/*键码处理*/if(KeyNum){srand(TL0);	//用定时器0的低8位做种子,从而产生真随机数if(Mode==0)	//如果是循环滚动显示游戏英文名“<<MATCH OR DIE>>”的界面{if(KeyNum>=9 && KeyNum<=12 && AllowChangeModeFlag)	//如果按下任意按键(松手瞬间),且允许改变模式{Mode=1;	//切换到模式1OnceFlag=1;	//切换模式前只执行一次的标志置1AllowChangeModeFlag=0;	//允许切换模式的标志置0}}else if(Mode==1)	//如果是游戏界面{if(KeyNum==1)	//如果按下K1(按下瞬间){MatrixLED_ClearBlock(0,Y1);	//清除显示玩家第一个2X2方块Type1++;	//玩家第一个2X2方块的类型自增Type1%=4;	//越界清零,Type的范围是0~3MatrixLED_DrawBlock(0,Y1,Type1);	//显示新的2X2方块}if(KeyNum==2)	//如果按下K2(按下瞬间){MatrixLED_ClearBlock(1,Y1);	//清除显示玩家第二个2X2方块Type2++;	//玩家第二个2X2方块的类型自增Type2%=4;	//越界清零,Type的范围是0~3MatrixLED_DrawBlock(1,Y1,Type2);	//显示新的2X2方块}if(KeyNum==3)	//如果按下K3(按下瞬间){MatrixLED_ClearBlock(2,Y1);	//清除显示玩家第三个2X2方块Type3++;	//玩家第三个2X2方块的类型自增Type3%=4;	//越界清零,Type的范围是0~3MatrixLED_DrawBlock(2,Y1,Type3);	//显示新的2X2方块}if(KeyNum==4)	//如果按下K4(按下瞬间){ConfirmFlag=1;	//确认的标志置1,然后玩家方块快速向上移动进行匹配}}else if(Mode==2)	//如果是游戏结束全屏闪烁{if(KeyNum==12)	//如果按下K4(松手瞬间){Mode=3;OnceFlag=1;}}else if(Mode==3)	//如果是滚动显示速度英文“speed”的界面{if(KeyNum==12)	//如果按下K4(松手瞬间){Mode=4;OnceFlag=1;}}else if(Mode==4)	//如果是循环滚动显示得分的界面{if(KeyNum==11)	//如果按下K3(松手瞬间){Mode=1;	//重新开始游戏OnceFlag=1;}}AllowChangeModeFlag=1;	//允许改变模式的标志置1}/*游戏处理*/if(Mode==0)	//循环滚动显示游戏英文名{if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次{OnceFlag=0;	//只执行一次的标志清零Offset=0;	//滚动显示的偏移量清零}if(RollFlag)	//如果滚动的标志RollFlag为真(非零即真){RollFlag=0;	//滚动的标志RollFlag清零MatrixLED_MoveLeft(Table1,Offset);	//向左滚动Offset++;	//每次向左移动一个像素Offset%=96;	//越界清零,循环滚动显示}}else if(Mode==1)	//游戏进行中{if(OnceFlag){OnceFlag=0;//游戏初始化MatrixLED_Clear();	//清屏GameOverFlag=0;	//游戏结束的标志置0Score=0;	//得分清零ConfirmFlag=0;	//确认的标志置0MatchFlag=0;	//匹配成功的标志置0MoveSpeed=200;	//下落的速度(时间间隔)重置Y1=6;	//玩家方块在最下边Y2=0;	//下落的需要匹配的方块在最上边Type1=rand()%4;Type2=rand()%4;Type3=rand()%4;	//随机产生玩家的三个方块Type4=rand()%4;Type5=rand()%4;Type6=rand()%4;	//随机产生需要匹配的三个方块MatrixLED_DrawBlock(0,Y2,Type4);MatrixLED_DrawBlock(1,Y2,Type5);MatrixLED_DrawBlock(2,Y2,Type6);	//显示需要匹配的方块MatrixLED_DrawBlock(0,Y1,Type1);MatrixLED_DrawBlock(1,Y1,Type2);MatrixLED_DrawBlock(2,Y1,Type3);	//显示玩家的方块T0Count=0;	//方块下落移动的定时器计数清零MoveFlag=0;	//移动的标志置0}if(MatchFlag)	//如果匹配成功{MatchFlag=0;	//匹配的标志置0Y2=0;	//需要匹配的方块从最高处下落Type4=rand()%4;Type5=rand()%4;Type6=rand()%4;	//随机产生需要匹配的三个方块MatrixLED_DrawBlock(0,Y2,Type4);MatrixLED_DrawBlock(1,Y2,Type5);MatrixLED_DrawBlock(2,Y2,Type6);	//显示需要匹配的方块//匹配成功后,控制下落移动的计数和标志清零T0Count=0;	//方块下落移动的定时器计数清零MoveFlag=0;	//移动的标志置0}if(MoveFlag)	//如果下落移动的标志为真{MoveFlag=0;	//移动的标志置0MatrixLED_ClearBlock(0,Y2);MatrixLED_ClearBlock(1,Y2);MatrixLED_ClearBlock(2,Y2);	//清除上一次方块的显示Y2++;	//Y2自增,即方块向下移动一个像素Y2%=7;	//Y2的范围是0~6(可以去掉这一行)MatrixLED_DrawBlock(0,Y2,Type4);MatrixLED_DrawBlock(1,Y2,Type5);MatrixLED_DrawBlock(2,Y2,Type6);	//显示移动位置后的方块//玩家方块和需要匹配的方块重合一个像素时,让玩家方块在上层,即覆盖需要匹配的方块if(ConfirmFlag==0)	//如果没按确认按键{MatrixLED_ClearBlock(0,6);MatrixLED_ClearBlock(1,6);MatrixLED_ClearBlock(2,6);MatrixLED_DrawBlock(0,6,Type1);MatrixLED_DrawBlock(1,6,Type2);MatrixLED_DrawBlock(2,6,Type3);}}if(Y2==6)	//如果需要匹配的方块到了最底部{GameOverFlag=1;	//游戏结束的标志置1(不管有没有匹配)MatrixLED_DrawBlock(0,6,Type1);MatrixLED_DrawBlock(1,6,Type2);MatrixLED_DrawBlock(2,6,Type3);	//用玩家的方块覆盖需要匹配的方块,即玩家的方块在上层}if(ConfirmFlag)	//如果按了K4(确认按键){if(UpFlag && Y1!=Y2)	//如果滚动的标志为真{	//Y1!=Y2的作用:防止MoveFlag和UpFlag同时为真时,两组方块“错过”,导致“穿越”UpFlag=0;	//玩家方块向上移动的标志置0MatrixLED_ClearBlock(0,Y1);MatrixLED_ClearBlock(1,Y1);MatrixLED_ClearBlock(2,Y1);	//清除玩家方块的显示Y1--;	//玩家方块向上移动一个像素MatrixLED_DrawBlock(0,Y1,Type1);MatrixLED_DrawBlock(1,Y1,Type2);MatrixLED_DrawBlock(2,Y1,Type3);	//显示移动位置后玩家的方块MatrixLED_ClearBlock(0,Y2);MatrixLED_ClearBlock(1,Y2);MatrixLED_ClearBlock(2,Y2);	//清除需要匹配的方块的显示MatrixLED_DrawBlock(0,Y2,Type4);MatrixLED_DrawBlock(1,Y2,Type5);MatrixLED_DrawBlock(2,Y2,Type6);	//重新显示需要匹配的方块,覆盖玩家的方块,即需要匹配的方块在上层}if(Y1==Y2)	//如果玩家方块和需要匹配的方块重合{ConfirmFlag=0;	//确认的标志置0UpFlag=0;	//玩家方块向上移动的标志置0MatrixLED_ClearBlock(0,Y1);MatrixLED_ClearBlock(1,Y1);MatrixLED_ClearBlock(2,Y1);	//清除方块的显示Y1=6;	//玩家方块回到最底部MatrixLED_DrawBlock(0,Y1,Type1);MatrixLED_DrawBlock(1,Y1,Type2);MatrixLED_DrawBlock(2,Y1,Type3);	//显示回到底部的玩家方块if(Type1==Type4 && Type2==Type5 && Type3==Type6)	//如果玩家方块和需要匹配的方块一一对应相同{MatchFlag=1;	//匹配的标志置1Score++;	//每匹配一次,分数加1if(Score%2==0){	//每匹配2次,方块下落的时间间隔减少0.1sMoveSpeed-=10;}}else	//如果没有匹配{MatrixLED_DrawBlock(0,Y2,Type4);MatrixLED_DrawBlock(1,Y2,Type5);MatrixLED_DrawBlock(2,Y2,Type6);	//需要匹配的方块继续显示,然后继续下落}}}if(GameOverFlag)	//如果游戏结束的标志为真{Mode=2;	//切换到全屏闪烁的模式}}else if(Mode==2)	//游戏结束全屏闪烁{//在定时器1中实现全屏闪烁}else if(Mode==3)	//显示得分的英文“SPEED”{if(OnceFlag){OnceFlag=0;Offset=0;}if(RollFlag && Offset<=38)	//只滚动显示一次英文“SCORE”{RollFlag=0;MatrixLED_MoveLeft(Table2,Offset);Offset++;}else if(Offset>38) //滚动结束后,自动切换到循环显示得分的模式{Mode=4;OnceFlag=1;}	}else if(Mode==4)	//循环滚动显示得分{if(OnceFlag){OnceFlag=0;Offset=0;for(i=0;i<6;i++)	//将得分的十位、个位的字模写入数组ScoreShow中{ScoreShow[ 8+i]=Table3[(Score/10)*6+i];	//十位}for(i=0;i<6;i++){ScoreShow[14+i]=Table3[(Score%10)*6+i];	//个位}}if(RollFlag){RollFlag=0;MatrixLED_MoveLeft(ScoreShow,Offset);Offset++;Offset%=20;	//循环滚动显示}}}
}/*** 函    数:定时器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++;T0Count4++;T0Count++;if(T0Count1>=2)	//每隔20ms检测一次键码{T0Count1=0;Key_Tick();}if(T0Count2>=50)	//每隔500ms置反FlashFlag{T0Count2=0;FlashFlag=!FlashFlag;}if(T0Count3>=10)	//每隔100ms滚动显示一次字母或数字{T0Count3=0;RollFlag=1;}if(T0Count4>=7)	//每隔70ms玩家方块向上移动一个像素(如果按了确认键的话){T0Count4=0;UpFlag=1;}if(T0Count>=MoveSpeed)	//控制方块下落的速度,MoveSpeed越小,方块移动的速度越快{T0Count=0;MoveFlag=1;}
}/*** 函    数:定时器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();}
}

总结

做的时候出现一些小问题:按了确认键之后,玩家的方块向上运动,需要匹配的方块和玩家方块在重合前如果只差一个像素,并且恰好同时对应的标志都置1了,同时移动,导致错开了,即玩家的方块越过了需要匹配的方块继续向上移动。

后来经过检查,判断条件增加了一个 Y1!=Y2 就解决了。

相关文章:

  • unity3d实现物体闪烁
  • Discuz论坛网站忘记管理员密码进不去管理中心怎么办?怎么改管理员密码?
  • 45.[前端开发-JavaScript高级]Day10-迭代器-生成器
  • Git创建空分支并推送到远程仓库
  • 市场分析 3 mysql (槽)
  • YOLO11改进,尺度动态损失函数Scale-based Dynamic Loss,减少标签不准确对损失函数稳定性的影响
  • 【网络安全】OWASP 十大漏洞
  • 蓝桥杯2024省A.成绩统计
  • 组件是怎样写的(1):虚拟列表-VirtualList
  • Activity之间交互
  • spark与hadoop的区别
  • Flutter 状态管理 Riverpod
  • 【Linux】多线程任务模块
  • 【Linux篇】轻松搭建命名管道通信:客户端与服务器的互动无缝连接
  • 卷积神经网络--手写数字识别
  • day33和day34图像处理OpenCV
  • 教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!
  • FastGPT Docker Compose本地部署与硅基流动免费AI接口集成指南
  • 【计算机网络】第五章 局域网技术
  • GPT,Genini, Claude Llama, DeepSeek,Qwen,Grok,选对LLM大模型真的可以事半功倍!
  • 广发基金刘格崧一季报:首次买入广东宏大、分众传媒,减仓亿纬锂能
  • 清华成立教育学院:加快高层次人才培养、加强教育学科建设
  • 跨市调任:李强已任河北唐山市检察院党组书记
  • 网信部门持续整治利用未成年人形象不当牟利问题
  • 工人日报社评:下放职称评审权,推动“以产聚才、以才兴产”
  • 工人日报刊文:首席技师当“博导”,激励技能人才更有作为