基于51单片机和8X8点阵屏、独立按键的滑动躲闪类小游戏

目录

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

系列文章目录


前言

用的是普中A2开发板。

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

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

一、效果展示

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

二、原理分析

1、8X8点阵屏的显示

普中开发板上的64个LED通过74HC595和P0口来驱动,通过P0口来选择导通哪一列(低电平导通),通过74HC595串行移位寄存器来确定每一列显示的数据。

跟8位数码管(同样有64个LED)一样,用扫描的方法来驱动显示,每隔1ms切换下一列显示。本代码中专门用定时器1来扫描点阵屏,并设置定时器1的优先级比定时器0的高。

2、8X8点阵屏的控制

关键是下面这三个函数,通过与、或、移位、取反等操作,控制任意一个LED的亮灭,或者获取任意一个LED的亮灭状态。

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

3、玩家控制的点的移动

利用上边的三个函数中的前两个,即void MatrixLED_DrawPoint(unsigned char X,unsigned char Y)和void MatrixLED_ClearPoint(unsigned char X,unsigned char Y),移动的时候,先清除原来位置LED的显示,再点亮新的位置的LED。

4、障碍物的下落

缓存数组的8个字节对应的是8列,对缓存数组的字节进行移位即可实现全屏的LED向下移动一个像素,这样会让玩家控制的点熄灭,但是问题不大,移位之后立刻又点亮玩家控制的那个点的LED即可。

5、随机障碍物的产生

本代码中产生的障碍物的尺寸都是宽1高2,如何实现呢?通过函数unsigned char MatrixLED_GetPoint(unsigned char X,unsigned char Y)来获取LED亮灭状态,缓存数组的8个字节移位之后,检测每一列,如果这一列的LED的第二行为亮,第三行为灭,则点亮这一列的第一行的那个LED,否则不点亮。

但是要注意,让障碍物之间有起码一个像素的空隙,否则容易堵死,降低可玩性。同样用到函数unsigned char MatrixLED_GetPoint(unsigned char X,unsigned char Y)。缓存数组的8个字节移位之后,先产生一个0~7的随机数(对应1~8列),检测该列、前一列(如果不是第一列的话)、后一列(如果不是最后一列的话)的第二行的LED是否都是熄灭状态,如果是的话,就在此列产生新的障碍物,否则向左或向右寻找满足条件的列。

6、独立按键的检测

独立按键的检测跟点阵屏的驱动类似,利用定时器每隔一段时间检测一次,然后跟上次的结果对比,从而判断是按下瞬间、长按还是松手瞬间。

三、各模块代码

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甘腾胜@20250521
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8LED点阵屏、独立按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】点阵屏旁边的跳线帽要接三个排针的左边两个
【操作说明】
(1)循环滚动显示游戏英文名的界面按任意按键开始游戏
(2)K1、K2两个按键控制左右移动
(3)游戏结束全屏闪烁界面按K4进入滚动显示得分的英文的界面
(4)滚动显示得分的英文的界面可按K4跳过
(5)循环显示得分界面可按K3返回,重新开始游戏
(6)游戏时按K4可以暂停或继续游戏
*/#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:游戏未结束
bit PauseFlag;	//暂停的标志,1:暂停,0:继续
bit RollFlag;	//字母或数字滚动一个像素的标志,1:滚动,0:不滚动
bit MoveFlag;	//障碍物向下移动一个像素的标志,1:移动,0:不移动
unsigned char Offset;	//偏移量,用来控制字母或数字向左滚动显示
unsigned int Score;	//游戏得分
unsigned char T0Count;	//定时器0计数全局变量
unsigned int Duration=500;	//移动的时间间隔,单位是1ms,初始500ms移动一次
unsigned char Player;	//玩家位置,范围:0~7,对应1~8列
unsigned char RandomColumn;	//产生的随机的列,范围:0~7
unsigned char FallCount;	//下落的像素的计数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,	// 无显示
};//取模要求:阴码(亮点为1),纵向取模,高位在下
//我分享的工程文件夹中有6X8像素的ASCII字符字模
//code:数据保存在flash中
unsigned char code Table1[]={	//游戏名称“滑动或者死亡”的英文:<<SLIDE 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,0x40,0x40,0x40,0x40,	// L
0x00,0x00,0x41,0x7F,0x41,0x00,	// I
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
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)	//如果是循环滚动显示游戏英文名的界面{if(KeyNum>=9 && KeyNum<=12)	//如果按下任意按键(松手瞬间){Mode=1;	//切换到模式1OnceFlag=1;	//切换模式前只执行一次的标志置1}}else if(Mode==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;	//只执行一次的标志清零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;	//得分清零PauseFlag=0;	//暂停的标志置0Duration=500;	//初始每隔500ms移动一次障碍物Player=rand()%8;	//随机确定玩家的初始位置MatrixLED_DrawPoint(Player,7);	//显示玩家位置MoveFlag=0;	//移动的标志置0T0Count=0;	//定时器0全局计数变量清零}if(PauseFlag==0)	//如果不是暂停状态{if(MoveFlag)	//如果移动的标志为真{MoveFlag=0;	//移动的标志置0if(MatrixLED_GetPoint(Player,6))	//如果玩家正上方的点是障碍物{	//则游戏结束Mode=2;	//切换到模式2GameOverFlag=1;	//游戏结束的标志置1}else	//如果游戏未结束{for(i=0;i<8;i++)	//整个屏幕的显示向下平移一个像素{	//会导致玩家控制的点熄灭DisplayBuffer[i]<<=1;}MatrixLED_DrawPoint(Player,7);	//重新显示玩家位置for(i=0;i<8;i++)	//每个障碍物都是宽1高2的条状物{if( MatrixLED_GetPoint(i,1)==1 && MatrixLED_GetPoint(i,2)==0 ){MatrixLED_DrawPoint(i,0);}}RandomColumn=rand()%8;	//产生0~7的随机数(用来投放障碍物)if(rand()%2)	//通过随机数确定向左或向右寻找合适的列投放障碍物{for(i=0;i<8;i++)	//向右寻找{if( MatrixLED_GetPoint((RandomColumn+i)%8,1)==0 && MatrixLED_GetPoint((RandomColumn+i+1)%8,1)==0&& MatrixLED_GetPoint((RandomColumn+i+7)%8,1)==0 )	//保证障碍物之间起码有一个像素的空隙{MatrixLED_DrawPoint((RandomColumn+i)%8,0);break;	//找到合适的位置则退出循环}}}else	//向左寻找{for(i=8;i>0;i--){if( MatrixLED_GetPoint((RandomColumn+i)%8,1)==0 && MatrixLED_GetPoint((RandomColumn+i+1)%8,1)==0&& MatrixLED_GetPoint((RandomColumn+i+7)%8,1)==0 ){MatrixLED_DrawPoint((RandomColumn+i)%8,0);break;}}}FallCount++;	//记录下落的像素数if(FallCount%8==0)	//每下落8个像素,分数加1{FallCount=0;	//变量清零Score++;Duration=Duration*9/10;	//每增加一分,速度变为10/9,即时间间隔变为9/10if(Duration<100){Duration=100;}	//控制时间间隔的最小值为100ms}}}}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;//将得分的十位、个位的字模写入数组ScoreShow中for(i=0;i<6;i++){ScoreShow[ 8+i]=Table3[(Score/100%10)*6+i];	//百位}for(i=0;i<6;i++){ScoreShow[14+i]=Table3[(Score/10%10)*6+i];	//十位}for(i=0;i<6;i++){ScoreShow[20+i]=Table3[(Score%10)*6+i];	//个位}}if(RollFlag){RollFlag=0;MatrixLED_MoveLeft(ScoreShow,Offset);Offset++;Offset%=26;	//循环滚动显示}}}
}/*** 函    数:定时器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 && PauseFlag==0)	//如果短按K1,且不是暂停状态{if(Player>0)	//如果不是在最左{if(MatrixLED_GetPoint(Player-1,7))	//如果向左移动后,左侧为障碍物{	//则游戏结束Mode=2;	//切换到全屏闪烁界面GameOverFlag=1;	//游戏结束的标志置1}else	//如果游戏未结束{MatrixLED_ClearPoint(Player,7);	//清除原来位置的显示Player--;	//向左移动一个像素MatrixLED_DrawPoint(Player,7);	//显示移动后的新位置}}}if(KeyNumber1==2 && PauseFlag==0)	//如果短按K2,且不是暂停状态{if(Player<7)	//如果不是在最右{if(MatrixLED_GetPoint(Player+1,7))	//如果向右移动后,右侧为障碍物{	//则游戏结束Mode=2;	//切换到全屏闪烁界面GameOverFlag=1;	//游戏结束的标志置1}else{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();}
}

总结

难在随机的障碍物的产生,要保证障碍物之间有空隙,防止全部堵死。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面向对象 一

系列文章目录 Java面向对象 二-CSDN博客 Java面向对象 三-CSDN博客 目录 系列文章目录 前言 一、初步认识面向对象 1.类和对象的简单理解 2.类的构成 二、类的实例化 1.对象的创建 2.对象的初始化 三、this引用的作用 四、构造方法 1.构造方法的提供 2.对象的构…

深度学习Y8周:yolov8.yaml文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a;根据yolov8n、yolov8s模型的结构输出&#xff0c;手写出yolov8l的模型输出、 文件位置&#xff1a;./ultralytics/cfg/models/v8/yolov8.…

【RocketMQ 生产者和消费者】- 生产者启动源码 - MQClientInstance 定时任务(4)

文章目录 1. 前言2. startScheduledTask 启动定时任务2.1 fetchNameServerAddr 拉取名称服务地址2.2 updateTopicRouteInfoFromNameServer 更新 topic 路由信息2.2.1 topic 路由信息2.2.2 updateTopicRouteInfoFromNameServer 获取 topic2.2.3 updateTopicRouteInfoFromNameSer…

解决Docker容器内yum: not found、apt: not found、apk: command not found等命令找不到问题

Linux有很多发行版&#xff0c;各发行版的包管理工具不一定相同。 Alpine的包管理工具是 apk Debian/Ubuntu的包管理工具是 apt Centos/RHEL的包管理工具是 yum 在安装软件之前&#xff0c;需要先查看Docker容器内的Linux是什么发行版&#xff0c;可使用 cat /etc/os-rele…

每日c/c++题 备战蓝桥杯(修理牛棚 Barn Repair)

修理牛棚 Barn Repair 题解 问题背景与挑战 在一个暴风雨交加的夜晚&#xff0c;Farmer John 的牛棚遭受了严重的破坏。屋顶被掀飞&#xff0c;大门也不翼而飞。幸运的是&#xff0c;许多牛正在度假&#xff0c;牛棚并未住满。然而&#xff0c;为了保护那些还在牛棚里的牛&am…

鸿蒙版Flutter库torch_light手电筒功能深度适配

鸿蒙版Flutter库torch_light手电筒功能深度适配&#xff1a;跨平台开发者的光明之路 本项目作者&#xff1a;kirk/坚果 适配仓库地址 作者仓库&#xff1a;https://github.com/svprdga/torch_light# 在数字化浪潮的推动下&#xff0c;跨平台开发框架如 Flutter 凭借其高效、…

【信息系统项目管理师】一文掌握高项常考题型-项目进度类计算

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、进度类计算的基本概念1.1 前导图法1.2 箭线图法1.3 时标网络图1.4 确定依赖关系1.5 提前量与滞后量1.6 关键路径法1.7 总浮动时间1.8 自由浮动时间1.9 关键链法1.10 资源优化技术1.11 进度压缩二、基本公式…

深入了解linux系统—— 操作系统的路径缓冲与链接机制

前言 在之前学习当中&#xff0c;我们了解了被打开的文件是如何管理的&#xff1b;磁盘&#xff0c;以及ext2文件系统是如何存储文件的。 那我们要打开一个文件&#xff0c;首先要先找到这个文件&#xff0c;操作系统又是如何去查找的呢&#xff1f; 理解操作系统搜索文件 …

Docker Hub仓库介绍

Docker Hub仓库全解析&#xff1a;从公共市场到私有化部署指南 一、Docker Hub公共镜像市场 1.1 核心功能解析 全球最大容器镜像库&#xff1a;累计托管超500万镜像核心服务矩阵&#xff1a; #mermaid-svg-CAMkhmtSWKEUw7z0 {font-family:"trebuchet ms",verdana,a…

redis使用RDB文件恢复数据

设置存盘间隔为120秒且10个key改变数据自动存盘使用RDB文件恢复数据 IP地址主机名192.168.10.170redis170 [rootredis170 ~]# yum install -y redis [rootredis170 ~]# systemctl start redis步骤一&#xff1a;设置存盘间隔为120秒且10个key改变自动存盘 [rootredis170 ~]#…

SpringBoot多环境配置文件切换

resources下application.yml、application-dev.yml、application-prod.yml多个配置文件。 spring:profiles:active: devspring:profiles:active: prod一般都是通过修改spring.profiles.active值来修改加载不同环境的配置信息&#xff0c;可以把切换的dev/prod放到pom.xml文件来…

Java 并发编程高级技巧:CyclicBarrier、CountDownLatch 和 Semaphore 的高级应用

Java 并发编程高级技巧&#xff1a;CyclicBarrier、CountDownLatch 和 Semaphore 的高级应用 一、引言 在 Java 并发编程中&#xff0c;CyclicBarrier、CountDownLatch 和 Semaphore 是三个常用且强大的并发工具类。它们在多线程场景下能够帮助我们实现复杂的线程协调与资源控…

【Java多线程】多线程状态下如何安全使用ArrayList以及哈希表

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 多线程安全使用ArrayList 手动加锁 日常中最常用的方法&#xff0c;使用synchronized进行加锁&#xff0c;把代码打包成一份&a…

InnoDB引擎底层解析(二)之InnoDB的Buffer Pool(三)

Buffer Pool 实例 我们上边说过&#xff0c;Buffer Pool 本质是 InnoDB 向操作系统申请的一块连续的内存空间&#xff0c;在多线程环境下&#xff0c;访问 Buffer Pool 中的各种链表都需要加锁处理&#xff0c;在Buffer Pool特别大而且多线程并发访问特别高的情况下&#xff0…

Netty学习专栏(三):Netty重要组件详解(Future、ByteBuf、Bootstrap)

文章目录 前言一、Future & Promise&#xff1a;异步编程的救星1.1 传统NIO的问题1.2 Netty的解决方案1.3 代码示例&#xff1a;链式异步操作 二、ByteBuf&#xff1a;重新定义数据缓冲区2.1 传统NIO ByteBuffer的缺陷2.2 Netty ByteBuf的解决方案2.3 代码示例&#xff1a;…

Vue3逐步抛弃虚拟Dom,React如何抉择

虚拟DOM&#xff1a;前端界的替死鬼 这玩意儿就是个前端开发的充气娃娃&#xff01; 你以为它很牛逼&#xff1f;无非是给真DOM当替死鬼&#xff01; 每次数据变&#xff0c;虚拟DOM先搁内存里自嗨一顿&#xff0c;diff算法跟便秘似的算半天&#xff0c;最后才敢碰真DOM。 说白…

分布式锁总结

文章目录 分布式锁什么是分布式锁&#xff1f;分布式锁的实现方式基于数据库(mysql)实现基于缓存(redis)多实例并发访问问题演示项目代码(使用redis)配置nginx.confjmeter压测复现问题并发是1&#xff0c;即不产生并发问题并发30测试,产生并发问题(虽然单实例是synchronized&am…

解决自签名证书HTTPS告警:强制使用SHA-256算法生成证书

解决自签名证书HTTPS告警&#xff1a;强制使用SHA-256算法生成证书 一、问题场景 在使用OpenSSL生成和配置自签名证书时&#xff0c;常遇到以下现象&#xff1a; 浏览器已正确导入根证书&#xff08;.pem文件&#xff09;&#xff0c;但访问HTTPS站点时仍提示不安全连接或证…

线上 Linux 环境 MySQL 磁盘 IO 高负载深度排查与性能优化实战

目录 一、线上告警 二、问题诊断 1. 系统层面排查 2. 数据库层面分析 三、参数调优 1. sync_binlog 参数优化 2. innodb_flush_log_at_trx_commit 参数调整 四、其他优化建议 1. 日志文件位置调整 2. 生产环境核心参数配置模板 3. 突发 IO 高负载应急响应方案 五、…

window 显示驱动开发-初始化和 DMA 缓冲区创建

若要指示 GPU 支持 GDI 硬件加速&#xff0c;显示微型端口驱动程序的 DriverEntry 函数实现必须使用指向驱动程序实现的 DxgkDdiRenderKm 函数的指针填充 DRIVER_INITIALIZATION_DATA 结构的 DxgkDdiRenderKm 成员。 DirectX 图形内核子系统调用 DxgkDdiRenderKm 函数&#xf…