基于51单片机和8X8点阵屏、独立按键的射击消除类小游戏

article/2025/9/7 15:15:09

目录

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

系列文章目录


前言

使用的是普中A2开发板。

【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8点阵屏、独立按键

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

一、效果展示

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

二、原理分析

1、点阵屏驱动显示

/*
每一个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];

想改变屏幕显示,则改变现实缓存的8个字节的64位(64Bit)就行了。通过移位、与、或、取反等操作来实现。写几个基础函数就能轻松控制屏幕显示了。

2、定时器扫描

点阵屏的显示和按键的扫描都用到了定时器。这里专门用定时器1来扫描显示点阵屏,要注意默认定时器0的优先级比定时器1的高,所以要设置一下优先级,让定时器1的优先级比定时器0的高,否则可能会出现显示不流畅的情况。

按键的检测也用到了定时器。如果通过延时来消抖,则会导致按下按键的时候阻塞程序运行。这里每隔20ms检测一次按键,将本次的检测结果跟上次的检测结果对比,可以判断是按下瞬间、长按还是松手瞬间,从而实现更加多的功能。

3、游戏结束判断

每隔一段时间,整个屏幕的“物体”会向下移动一个像素,并且第一行会生成新的待射击的“物体”,移动前先进性判断,如果第7行(玩家的上一行)有“物体”未清除,则游戏结束,全屏闪烁。

4、障碍物消除

/*** 函    数: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;}
}

需要用到一个获取LED状态的函数,通过for循环,从玩家所在的列的第7行往上检测,一直到第1行,检测到的第一个点亮着的LED后,则把这个LED熄灭,然后退出循环。熄灭某个LED灯需要用到以下函数。

/*** 函    数: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); }
}

5、随机物的生成

屏幕向下平移一个像素后,第一行是空的,需要重新生成新的障碍物。

为了实现真随机,每次获取非零键码后,都会以定时器0的低八位作为随机数的种子。虽然种子确定后,产生的随机数也是确定的,但是由于按下按键的时刻是由人来决定的,所以可以看成是真随机。

第一行总共有8个LED,对应的是一个字节的8位,所以需要生成一个随机的字节,如果生成的随机数为0的话,则什么都没有,所以限定了随机数的范围是1~254,也就是生成“物体”的个数是1~7。

NewCreation=rand()%254+1; //重新生成障碍物

6、射击位置的移动

如果放在main函数中控制,则会收到代码的影响导致响应可能不及时,所以放在定时器0的中断函数中来实现移动。

三、各模块代码

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);#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;}
}

2、独立按键

h文件

#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__extern unsigned char KeyNumber1;	//在main.c里使用
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;
unsigned char KeyNumber1;	//在外部的main.c里使用/*** 函    数:获取独立按键键码* 参    数:无* 返 回 值:按下按键的键码,范围: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;}}KeyNumber1=KeyNumber;
}

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甘腾胜@20250526
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8LED点阵屏、独立按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】点阵屏旁边的跳线帽要接三个排针的左边两个
【操作说明】
(1)循环滚动显示游戏英文名的界面按任意按键开始游戏
(2)K1、K2两个按键控制左右移动
(3)游戏中按K3发射“子弹”
(4)游戏时按K4可以暂停或继续游戏
(5)游戏结束全屏闪烁界面按K4进入滚动显示得分的英文的界面
(6)滚动显示得分的英文的界面可按K4跳过
(7)循环显示得分界面可按K3返回,重新开始游戏
*/#include <REGX52.H>		//51单片机头文件
#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 FlashFlag;	//闪烁的标志,1:不显示,0:显示
bit GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char Offset;	//偏移量,用来控制字母或数字向左滚动显示
bit RollFlag;	//字母或数字滚动一个像素的标志,1:滚动,0:不滚动
bit MoveFlag;	//障碍物向下移动一个像素的标志,1:移动,0:不移动
unsigned int Duration;	//移动的时间间隔,单位是1ms
unsigned int Score;	//游戏得分,范围:0~65535
unsigned char ScoreLength;	//游戏得分的位数,范围:1~5
bit PauseFlag;	//暂停的标志,1:暂停,0:继续
unsigned char T0Count;	//定时器0计数全局变量
unsigned char Player;	//玩家位置,范围:0~7,对应1~8列
unsigned char NewCreation;	//整个屏幕向下平移一个像素后,第一行产生的随机的创造物,范围:1~254
bit ShootFlag;	//射击的标志,1:射击,0:不射击
unsigned char idata ScoreShow[]={	//游戏得分(用于滚动显示),idata:变量保存在片内的间接寻址区
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,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应6个字节
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};//取模要求:阴码(亮点为1),纵向取模,高位在下
//我分享的工程文件夹中有6X8像素的ASCII字符字模
//code:数据保存在flash中
unsigned char code Table1[]={	//游戏名称“射击或者死亡”的英文:<<SHOOT OR DIE>>,宽6高8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x08,0x14,0x22,0x49,0x14,0x22,0x41,	// <<	宽8高8(自定义书名号:两个小于号)
0x00,0x46,0x49,0x49,0x49,0x31,	// S
0x00,0x7F,0x08,0x08,0x08,0x7F,	// H
0x00,0x3E,0x41,0x41,0x41,0x3E,	// O
0x00,0x3E,0x41,0x41,0x41,0x3E,	// O
0x00,0x01,0x01,0x7F,0x01,0x01,	// T
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
};/*** 函    数:幂函数/指数函数* 参    数:X 底* 参    数:Y 幂* 返 回 值:X的Y次方* 说    明:辅助取出Score中的某一位数字*/
unsigned int Pow(unsigned char X,unsigned char Y)
{unsigned char i;unsigned int Result=1;for(i=0;i<Y;i++){Result*=X;}return Result;
}/*** 函    数:主函数(有且仅有一个)* 参    数:无* 返 回 值:无* 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌*/
void main()
{unsigned char i,j;	//循环用到的临时变量P2_5=0;	//防止开发板上的蜂鸣器发出声音Timer0_Init();  //定时器0初始化Timer1_Init();  //定时器1初始化MatrixLED_Init();	//点阵屏初始化while(1){KeyNum=Key();	//获取键码/*键码处理*/if(KeyNum){srand(TL0);	//每次获取非零键码都用定时器0的低8位做种子,从而产生真随机数if(Mode==0)	//如果是循环滚动显示游戏英文名的界面{if(KeyNum>=9 && KeyNum<=12)	//如果按下任意按键(松手瞬间){Mode=1;	//切换到模式1OnceFlag=1;	//切换模式前只执行一次的标志置1}}else if(Mode==1)	//如果是正在游戏的界面{if(KeyNum==3 || KeyNum==7)	//如果按下K3(短按或长按){ShootFlag=1;	//射击的标志置1}if(KeyNum==12)	//如果按下K4(松手瞬间){PauseFlag=!PauseFlag;	//置反暂停的标志MoveFlag=0;	//障碍物向下移动的标志置0T0Count=0;	//定时器0的全局计数变量清零if(PauseFlag==0){MatrixLED_DrawPoint(Player,7);}	//由暂停变成继续,则重新显示玩家位置(防止显示不及时)}}else if(Mode==2)	//如果是游戏结束全屏闪烁的界面{if(KeyNum==12)	//如果按下K4(松手瞬间){Mode=3;OnceFlag=1;}}else if(Mode==3)	//如果是滚动显示英文“SCORE”的界面{if(KeyNum==12)	//如果按下K4(松手瞬间){Mode=4;OnceFlag=1;}}else if(Mode==4)	//如果是循环滚动显示得分的界面{if(KeyNum==11)	//如果按下K3(松手瞬间){Mode=1;	//重新开始游戏OnceFlag=1;}}}/*游戏处理*/if(Mode==0)	//循环滚动显示游戏英文名{if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次{OnceFlag=0;	//只执行一次的标志置0Offset=0;	//滚动显示的偏移量清零}if(RollFlag)	//如果滚动的标志RollFlag为真(非零即真){RollFlag=0;	//滚动的标志RollFlag置0MatrixLED_MoveLeft(Table1,Offset);	//向左滚动Offset++;	//每次向左移动一个像素Offset%=96;	//越界清零,循环滚动显示}}else if(Mode==1)	//游戏进行中{if(OnceFlag){OnceFlag=0;//游戏初始化MatrixLED_Clear();	//清屏GameOverFlag=0;	//游戏结束的标志置0Score=0;	//得分清零PauseFlag=0;	//暂停的标志置0Duration=2000;	//初始每隔2s向下移动一次要射击的障碍物Player=rand()%8;	//确定玩家初始的随机的位置MatrixLED_DrawPoint(Player,7);	//显示玩家(射击)位置for(i=0;i<46;i++){ScoreShow[i]=0;}	//清空数组ScoreShow的数据MoveFlag=0;	//障碍物移动的标志置0T0Count=0;	//定时器0全局计数变量清零}if(PauseFlag==0)	//如果不是暂停状态{if(ShootFlag)	//如果射击的标志为真{ShootFlag=0;	//射击的标志置0for(i=0;i<7;i++)	//玩家所在列的第7行往上寻找第一个点亮了的LED{if(MatrixLED_GetPoint(Player,6-i))	//如果找到了{MatrixLED_ClearPoint(Player,6-i);	//则熄灭该LEDScore++;	//分数加一if(Score%10==0)	//分数每增加10,加速一次{Duration=Duration*9/10;	//下落的时间间隔变为上一次的90%if(Duration<1000){Duration=1000;}	//下落的时间间隔的最小值为1000ms}break;	//退出循环}}}if(MoveFlag)	//如果移动的标志为真{MoveFlag=0;	//移动的标志置0for(i=0;i<8;i++)	//移动前先判断第七行(玩家所在第八行的上一行)是否有未熄灭的LED{if(MatrixLED_GetPoint(i,6))	//如果有,则游戏结束{Mode=2;GameOverFlag=1;}}if(GameOverFlag==0)	//如果游戏未结束{for(i=0;i<8;i++)	//整个屏幕的显示向下平移一个像素{	//会导致玩家控制的点熄灭DisplayBuffer[i]<<=1;}MatrixLED_DrawPoint(Player,7);	//重新显示玩家位置NewCreation=rand()%254+1;	//重新生成障碍物for(i=0;i<8;i++)	//将NewCreation按照高位在左的方式放在第一行{if( (0x80>>i) & NewCreation ){MatrixLED_DrawPoint(i,0);}}}}}else	//如果是暂停状态{if(FlashFlag)	//如果闪烁的标志为真{MatrixLED_ClearPoint(Player,7);	//不显示}else{MatrixLED_DrawPoint(Player,7);	//显示}}}else if(Mode==2)	//游戏结束全屏闪烁{//在定时器1中实现全屏闪烁}else if(Mode==3)	//滚动显示得分的英文“SCORE”{if(OnceFlag){OnceFlag=0;Offset=0;}if(RollFlag && Offset<=38)	//只滚动显示一次英文{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;//判断得分是多少位数if(Score>=10000){ScoreLength=5;}else if(Score>=1000){ScoreLength=4;}else if(Score>=100){ScoreLength=3;}else if(Score>=10){ScoreLength=2;}else{ScoreLength=1;}//将得分的字模写入数组ScoreShow中for(j=0;j<ScoreLength;j++){for(i=0;i<6;i++){ScoreShow[8+6*j+i]=Table3[(Score/Pow(10,ScoreLength-1-j)%10)*6+i];}}}if(RollFlag){RollFlag=0;MatrixLED_MoveLeft(ScoreShow,Offset);Offset++;Offset%=8+ScoreLength*6;	//循环滚动显示}}}
}/*** 函    数:定时器0中断函数* 参    数:无* 返 回 值:无*/
void Timer0_Routine() interrupt 1
{static unsigned char T0Count1,T0Count2,T0Count3;	//定时器计数变量TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHzTH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHzT0Count1++;T0Count2++;T0Count3++;T0Count++;if(T0Count1>=2)	//每隔20ms检测一次键码{T0Count1=0;Key_Tick();/*在中断函数中更新玩家的位置*/ //在主循环中更新显示会受代码影响,导致移动不流畅if(KeyNumber1 && Mode==1)	//如果有按键按下且处于游戏进行中{if( (KeyNumber1==1 || KeyNumber1==5) && PauseFlag==0)	//如果短按K1或长按K1,且不是暂停状态{if(Player>0)	//如果不是在最左{MatrixLED_ClearPoint(Player,7);	//清除原来位置的显示Player--;	//向左移动一个像素MatrixLED_DrawPoint(Player,7);	//显示移动后的新位置}}if( (KeyNumber1==2 || KeyNumber1==6) && PauseFlag==0)	//如果短按K2或长按K2,且不是暂停状态{if(Player<7)	//如果不是在最右{MatrixLED_ClearPoint(Player,7);	//清除原来位置的显示Player++;	//向右移动一个像素MatrixLED_DrawPoint(Player,7);	//显示移动后的新位置}}KeyNumber1=0;	//独立按键的键码清零}}if(T0Count2>=50)	//每隔500ms置反FlashFlag{T0Count2=0;FlashFlag=!FlashFlag;}if(T0Count3>=10)	//每隔100ms滚动显示一次字母或数字{T0Count3=0;RollFlag=1;}if(T0Count>=Duration/10)	//每隔Duration ms移动一次障碍物{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~254的随机字节就行了,把这个字节的8位与点阵屏的8列对应即可,就可以产生1~7个随机“障碍物”,不用对每一列产生随机数。


http://www.hkcw.cn/article/EewxNYLGIj.shtml

相关文章

ubuntu22.04安装docker

1. 准备工作 更新系统软件包索引 sudo apt update2. 卸载旧版本 Docker&#xff08;可选&#xff09; 清理旧版 Docker 及相关依赖 sudo apt-get remove docker docker-engine docker.io containerd runc3. 设置 Docker 仓库 安装依赖工具 (apt-transport-https, ca-certi…

burpsuit抓包完整示例

1.确保浏览器&#xff08;这里使用的是火狐浏览器&#xff09;和burpsuit配置完整&#xff08;有需要留言&#xff09;&#xff0c;配置完整包括jdk安装&#xff0c;配置环境变量&#xff0c;下载burp,下载并导入证书&#xff0c;ip端口一致&#xff0c;代理能正常打开。 2.注意…

其他 | 边缘端应用的轻量级优化调研

1.调研目标 由于边缘计算场景的性能受限&#xff0c;无法提供与常规服务器相同或略低的环境&#xff0c;因此对我们的上层业务应用有着较高的资源要求。 目前我们的应用程序基于 Oracle JDK&#xff08;开发者端&#xff09;与 OpenJDK&#xff08;生产环境&#xff09;进行开…

Shell 脚本常用命令笔记

一、系统配置命令 1. 主机名设置 文件方式 修改文件&#xff1a;vim /etc/hostname&#xff0c;写入新主机名&#xff08;如czg.easylee.org&#xff09;。生效方式&#xff1a;需重新打开 Shell 或重启系统。 命令方式 即时生效命令&#xff1a;hostnamectl set-hostname 新…

不规则瀑布流布局拖拽重排序

因为业务&#xff0c;所以需要用flutter去实现一种不规则图形的瀑布流&#xff0c;但是同时需要支持拖拽并重新排序。效果类似如下。 查询过现有的插件&#xff0c;要么是仅支持同样大小的组件进行排序&#xff0c;要么就是动画效果不是很满意&#xff0c;有点死板&#xff0c;…

线程de安全性备忘

文章仅供学习参考 线程安全的本质定义 public class Counter {private int value 0; // 共享资源// 线程安全方法public synchronized void increment() {value; // 原子操作} }原子性&#xff08;Atomicity&#xff09;&#xff1a;操作不可分割&#xff08;如 synchroniz…

进行性核上性麻痹健康护理全指南:从症状管理到生活照护

进行性核上性麻痹&#xff08;PSP&#xff09;是一种罕见的神经退行性疾病&#xff0c;主要影响运动、平衡及眼球运动功能&#xff0c;常表现为步态不稳、吞咽困难、眼球上视受限、情绪改变等。由于目前尚无根治方法&#xff0c;科学的健康护理对延缓病情进展、提升患者生活质量…

[智能算法]蚁群算法原理与TSP问题示例

目录 ​编辑 一、生物行为启发的智能优化算法 1.1 自然界的群体智能现象 1.2 人工蚁群算法核心思想 二、算法在组合优化中的应用演进 2.1 经典TSP问题建模 2.2 算法流程优化 三、TSP问题实战:Python实现与可视化 3.1 算法核心类设计 3.2 参数敏感性实验 3.3 可视化…

安卓学习笔记-数据存储

阅读说明 本文是基于上一篇文章《安卓学习笔记-声明式UI》的后续。上篇文章实现了UI层以及业务逻辑层ViewModel的解耦。本篇关注的是数据存储层与业务逻辑层的解耦。 补充知识StateFlow 在 MVVM 架构中如何使用 Kotlin 协程的 StateFlow 来管理和暴露 UI 状态。 private va…

cutlass学习教程

一 接口 1.1 内存类 1.1.1 DeviceAllocation 1 位置 2 内置函数 &#xff08;a&#xff09;reset /// Deletes the managed object and resets capacity to zero void reset() {capacity 0;smart_ptr.reset(); } &#xff08;b&#xff09;get /// Returns a pointer to t…

自定义异常小练习

在开始之前,让我们高喊我们的口号&#xff1a; ​​​​​​​ 键盘敲烂,年薪百万&#xff01; 目录 键盘敲烂,年薪百万&#xff01; 异常综合练习&#xff1a; 自定义异常 异常综合练习&#xff1a; 自定义异常&#xff1a; 定义异常类写继承关系空参构造带参构造 自定…

计算机网络

OSI七层模型 应用层&#xff1a;直接为用户提供网络服务&#xff0c;例如网页浏览、邮件收发表示层&#xff1a;处理数据格式&#xff0c;如加密、解密、压缩、编码等会话层&#xff1a;利用传输层提供的服务&#xff0c;在应用程序之间建立和维持会话&#xff0c;并能使会话获…

【循环神经网络RNN第一期】循环神经网络RNN原理概述

目录 &#x1f9e0; 什么是循环神经网络&#xff08;RNN&#xff09;&#xff1f;&#x1f501; RNN 的结构图&#x1f504; RNN 的“记忆”与问题RNN梯度推导 &#x1f9ec; LSTM&#xff1a;解决长期依赖问题&#x1f9f1; LSTM 的核心结构LSTM总结 参考 人类在思考的时候&am…

自动驾驶与智能交通:构建未来出行的智能引擎

随着人工智能、物联网、5G和大数据等前沿技术的发展&#xff0c;自动驾驶汽车和智能交通系统正以前所未有的速度改变人类的出行方式。这一变革不仅是技术的融合创新&#xff0c;更是推动城市可持续发展的关键支撑。 一、自动驾驶与智能交通的定义 1. 自动驾驶&#xff08;Auto…

5.3.1_2二叉树的层次遍历

遍历过程&#xff1a; 从根节点开始&#xff0c;从左到右一层一层遍历&#xff0c;如下&#xff1a;ABCDEFGHIJKL 初始化一个辅助队列&#xff0c;让根节点先入队&#xff0c;每次判断队列是否为空&#xff0c;不空则让队头节点出队访问该节点让该节点左右孩子入队尾(先左孩子…

Qt DateTimeEdit(时间⽇期的微调框)

使⽤ QDateEdit 作为⽇期的微调框. 使⽤ QTimeEdit 作为时间的微调框 使⽤ QDateTimeEdit 作为时间⽇期的微调框. 这⼏个控件⽤法⾮常相似, 我们以 QDateTimeEdit 为例进⾏介绍. QDateTimeEdit 核⼼属性 属性说明dateTime时间⽇期的值. 形如 2000/1/1 0:00:00date单纯⽇期…

NISCO里境全新VALUE系列合肥首店启幕,携手正反设计打造0压生活空间

2025年5月28日,NISCO里境全新VALUE系列全国首家旗舰店正式落子合肥,梦百合集团近三十位核心经销商代表齐聚,深度体验新系列门店的空间场景与创新产品矩阵。 作为品牌“双线并进”的重要布局,VALUE系列以高性价比为核心,聚焦二三线城市消费需求,通过沉浸式空间场景与革新性产品设…

制造企业搭建AI智能生产线怎么部署?

制造商需要精准协调生产和发货&#xff0c;确保订单及时交付。MES、ERP、CRM 系统与生产线集成&#xff0c;对生产管理流程、物料跟踪、品控、确定货期至关重要。如果某个系统发生延迟或者效率低下&#xff0c;会在造成整个生产环节停滞&#xff0c;影响最终交付&#xff0c;导…

Linux系统管理与编程24:基础条件准备-混搭“本地+阿里云”yum源

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1.添加宿主机共享文件夹 Linux虚拟机可以和宿主机共享文件夹&#xff0c;这样有利于工具文件的共享。具体操作如下&#xff1a; 1&#xff09;vmware workstation共享文件夹 虚拟机…

VCS elab选项 -simprofile功能

#废话不多说&#xff0c;直接上干货 1.简介 VCS提供的simprofile功能是用于分析仿真过程中的CPU time和machine memory消耗情况&#xff0c;可以协助定位一些垃圾代码写法导致的仿真资源消耗过大问题&#xff1b;本篇内容包含&#xff0c;应用此功能的必要工具准备、makefile…