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

news2025/5/24 5:52:01

目录

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

系列文章目录


前言

用的是普中A2开发板。

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

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

一、效果展示

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

二、原理分析

1、8X8点阵屏的驱动

用定时器1专门扫描显示点阵屏,要注意设置定时器1比定时器0的优先级高,否则显示可能会有闪烁的现象。

很简单,跟数码管的扫描的原理是一样的。

写几个专门的函数,通过与、或、非、移位等操作,能随意控制任意一个LED亮和灭,以及获取任意一个LED的亮灭状态,就OK了,这是最基本的。

2、独立按键的检测

如果通过延时来消抖,会阻塞程序的运行,所以要用定时器来扫描检测。本代码设置每隔20ms检测一次,时间间隔大点小点都问题不大。

为了让控制不出现问题,代码设置了短按或长按按键都能控制点的移动。

3、分数和加速的次数的显示

为了充分利用屏幕,第一列用二进制显示加速的总次数,高位在上。最后一列用二进制显示分数除以256的余数,同样是高位在上。

4、障碍物的创造和显示

通过一个数组保存产生的障碍物,为了产生真随机的障碍物,每次获取非零的键码后,都以定时器0的低八位作为随机数的种子,通过随机函数rand()来产生随机数。

5、游戏结束的判定

移动前检测玩家控制的点如果移动后是否与障碍物的点重合,如果是的话,则不移动,并且判定游戏结束,全屏闪烁。

6、移动的时间间隔的最小值

分数每增加3,都让移动的时间间隔变为当前的9/10,但是要控制其最小值,否则可玩性会降低。

三、各模块代码

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__

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.0592MHz
	TH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	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=0x66;	//设置定时初值,定时1ms,12T@11.0592MHz
	TH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	T0Count++;
	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.0592MHz
	TH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	TF1=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.0592MHz
	TH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	T1Count++;
	if(T1Count>=1000)
	{
		T1Count=0;
		
	}
}
*/

四、主函数

main.c

/*by甘腾胜@20250520
【效果查看/操作演示】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:游戏未结束
unsigned char Offset;	//偏移量,用来控制字母或数字向左滚动显示
bit RollFlag;	//字母或数字滚动一个像素的标志,1:滚动,0:不滚动
bit MoveFlag;	//障碍物向下移动一个像素的标志,1:移动,0:不移动
unsigned int Duration=500;	//移动的时间间隔,单位是1ms,初始500ms移动一次
unsigned char Speed;	//用于屏幕显示的加速的次数
unsigned int Score;	//游戏得分
bit PauseFlag;	//暂停的标志,1:暂停,0:继续
unsigned char T0Count;	//定时器0计数全局变量
bit NowPosition;	//玩家现在位置,0:左边,1:右边
bit LastPosition;	//玩家上次位置,0:左边,1:右边
bit DodgedFlag;	//已经移动了的标志,1:已移动,0:未移动
unsigned char idata Obstacles[32];	//保存生成的障碍物,1:障碍物,0:空气,索引0~15为左列,索引16~31为右列
unsigned char NowPointer;	//现在显示的障碍物对应数组Obstacles中的位置,范围:0~15,0:对应数组索引0和16,1:对应数组索引1和17,以此类推
unsigned char CreatPointer;	//即将要创造的障碍物对应数组Obstacles中的位置,范围:0~15,0:对应数组索引0和16,1:对应数组索引1和17,以此类推
unsigned char ObstacleLength;	//障碍物的长度
unsigned char GapLength;	//障碍物与障碍物之间间隙的长度
bit LeftOrRight;	//左或右,0:左,1:右
unsigned char OneSideTimes;	//障碍物连续在同一侧的次数
bit NowOtherSideState;	//玩家另外一侧是否为障碍物,1:是,0:否
bit LastOtherSideState;	//记录上一次玩家另外一侧是否为障碍物,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,	// 无显示
};

//取模要求:阴码(亮点为1),纵向取模,高位在下
//我分享的工程文件夹中有6X8像素的ASCII字符字模
//code:数据保存在flash中
unsigned char code Table1[]={	//游戏名称“躲闪或者死亡”的英文:<<DODGE OR DIE>>,宽6高8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x08,0x14,0x22,0x49,0x14,0x22,0x41,	// <<	宽8高8(自定义书名号:两个小于号)
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x3E,0x41,0x41,0x41,0x3E,	// O
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x3E,0x41,0x49,0x49,0x7A,	// G
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;	//切换到模式1
					OnceFlag=1;	//切换模式前只执行一次的标志置1
				}
			}
			else if(Mode==1)	//如果是正在游戏的界面
			{
				if( (KeyNum==1 || KeyNum==5) && PauseFlag==0 )	//如果是游戏进行中并且不是暂停
				{	//短按K1或长按K1,玩家移动到左边
					NowPosition=0;
					if(NowPosition!=LastPosition)	//由右边移到左边时,躲闪了的标志置1
					{	//如果已经在左边了,再按K1,躲闪了的标志不置1,防止另一侧玩家正对的点显示不正常
						DodgedFlag=1;	//用来更新玩家的位置
					}
					LastPosition=NowPosition;	//更新变量的值
				}
				if( (KeyNum==2 || KeyNum==6) && PauseFlag==0 )	//如果是游戏进行中并且不是暂停
				{	//短按K2或长按K2,玩家移动到右边
					NowPosition=1;
					if(NowPosition!=LastPosition)	//由左边移到右边时,躲闪了的标志置1
					{	//如果已经在右边了,再按K2,躲闪了的标志不置1,防止另一侧玩家正对的点显示不正常
						DodgedFlag=1;	//用来更新玩家的位置
					}
					LastPosition=NowPosition;	//更新变量的值
				}
				if(KeyNum==12)	//如果按下K4(松手瞬间)
				{
					PauseFlag=!PauseFlag;	//置反暂停的标志
					MoveFlag=0;	//障碍物向下移动的标志置0
					T0Count=0;	//定时器0的全局计数变量清零
				}
			}
			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;	//游戏结束的标志置0
				Score=0;	//得分清零
				NowPosition=rand()%2;	//确定游戏开始时玩家的随机位置
				LastPosition=NowPosition;	//确定游戏开始时玩家的随机位置
				PauseFlag=0;	//暂停的标志置0
				NowPointer=0;	//屏幕正在显示的内容对应缓存数组中的数据的位置
				CreatPointer=8;		//即将创造的障碍物对应缓存数组中的数据的位置
				LeftOrRight=rand()%2;	//用来随机确定第一个障碍物的左右位置
				OneSideTimes=0;	//连续在同一侧的次数清零
				ObstacleLength=0;	//障碍物的长度清零
				GapLength=0;	//障碍物之间的间隙长度清零
				Duration=500;	//初始每隔500ms移动一次障碍物
				NowOtherSideState=0;	//玩家另一侧障碍物状态置0
				LastOtherSideState=0;	//玩家另一侧障碍物状态置0
				Speed=0;	//用于屏幕显示的加速的次数清零
				for(i=0;i<8;i++)	//显示第3列和第6列,作为边界
				{
					MatrixLED_DrawPoint(2,i);
					MatrixLED_DrawPoint(5,i);
				}
				if(NowPosition==0){MatrixLED_DrawPoint(3,6);}	//显示玩家位置
				else{MatrixLED_DrawPoint(4,6);}
				for(i=0;i<32;i++){Obstacles[i]=0;}	//清空数组的数据
				MoveFlag=0;	//障碍物移动的标志置0
				T0Count=0;	//定时器0全局计数变量清零
			}

			while(( (CreatPointer>NowPointer) && ((NowPointer+16-CreatPointer)>(ObstacleLength+GapLength)) ) 
				|| ( (CreatPointer<NowPointer) && ((NowPointer-CreatPointer)>(ObstacleLength+GapLength)) ) )
			{	//如果缓存数组中未创造障碍物的区域足够多
				while(ObstacleLength)	//障碍物
				{	//一直创造障碍物,并保存到数组中,直到障碍物长度减为0
					if(LeftOrRight==0)
					{
						Obstacles[CreatPointer]=1;
						Obstacles[CreatPointer+16]=0;
					}
					else
					{
						Obstacles[CreatPointer]=0;
						Obstacles[CreatPointer+16]=1;
					}
					CreatPointer++;
					CreatPointer%=16;
					ObstacleLength--;	//障碍物长度ObstacleLength自减
				}
				while(GapLength)	//障碍物之间的间隙
				{	//一直创造障碍物之间的间隙,并保存到数组中,直到间隙长度减为0
					Obstacles[CreatPointer]=0;
					Obstacles[CreatPointer+16]=0;
					CreatPointer++;
					CreatPointer%=16;
					GapLength--;	//障碍物之间的间隙长度GapLength自减
				}
				ObstacleLength=rand()%2+1;	//障碍物长度范围:1~2
				GapLength=rand()%2+2;	//障碍物之间的间隙长度:2~3
				if(OneSideTimes==0)
				{
					OneSideTimes=rand()%3+1;	//障碍物连续在同一侧的次数UpOrDownLength的范围:1~3
					LeftOrRight=!LeftOrRight;
				}
				OneSideTimes--;
			}

			if(PauseFlag==0)	//如果不是暂停状态
			{
				if(DodgedFlag)	//如果移动了的标志为1,则更新玩家的位置
				{
					DodgedFlag=0;	//躲闪了的标志置0

					if( (NowPosition==0 && Obstacles[(1+NowPointer)%16]) || (NowPosition==1 && Obstacles[(1+NowPointer)%16+16]) )
					{	//如果碰撞到了障碍物
						Mode=2;	//切换到游戏结束全屏闪烁模式
						GameOverFlag=1;	//游戏结束标志置1
					}
					else	//如果没碰撞到障碍物
					{
						if(NowPosition==0)	//如果在左边
						{
							MatrixLED_ClearPoint(4,6);
							MatrixLED_DrawPoint(3,6);
						}
						else	//如果在右边
						{
							MatrixLED_ClearPoint(3,6);
							MatrixLED_DrawPoint(4,6);
						}
					}
				}

				if(MoveFlag && GameOverFlag==0)	//如果移动的标志为真,且游戏还未结束
				{
					MoveFlag=0;

					NowPointer++;
					NowPointer%=16;

					if( (NowPosition==0 && Obstacles[(1+NowPointer)%16]) || (NowPosition==1 && Obstacles[(1+NowPointer)%16+16]) )
					{	//如果碰撞到了障碍物
						Mode=2;	//切换到游戏结束全屏闪烁模式
						GameOverFlag=1;	//游戏结束标志置1
					}
					else
					{
						if(NowPosition==0){NowOtherSideState=Obstacles[(1+NowPointer)%16+16];}	//获取玩家对侧的状态,看是否为障碍物
						else{NowOtherSideState=Obstacles[(1+NowPointer)%16];}
						if(LastOtherSideState==1 && NowOtherSideState==0)	//如果玩家的另一侧由障碍物变成空隙
						{
							Score++;	//则分数加1
							if(Score%3==0)	//每经过三次障碍物,速度增加一次,变为原来的10/9
							{
								Duration=Duration*9/10;	//移动的时间间隔变为原来的9/10
								if(Duration<90)	//控制Duration的最小值为90
								{
									Duration=90;
								}
								else
								{
									Speed++;	//加速的次数自增
								}
							}
							for(i=0;i<8;i++)	//最后一列以二进制的方式显示分数(高位在上)
							{
								if(Score & (0x80>>i)){MatrixLED_DrawPoint(7,i);}
								else{MatrixLED_ClearPoint(7,i);}
							}
							for(i=0;i<8;i++)	//第一列以二进制的方式显示已加速的次数(高位在上)
							{
								if(Speed & (0x80>>i)){MatrixLED_DrawPoint(0,i);}
								else{MatrixLED_ClearPoint(0,i);}
							}
						}
						LastOtherSideState=NowOtherSideState;	//更新变量

						for(i=0;i<8;i++)	//更新障碍物和玩家的显示
						{
							if(i==1 && NowPosition==0)	//玩家位置
							{
								MatrixLED_DrawPoint(3,6);
							}
							else	//障碍物和间隙
							{
								if(Obstacles[(NowPointer+i)%16]){MatrixLED_DrawPoint(3,7-i);}
								else{MatrixLED_ClearPoint(3,7-i);}
							}
							if(i==1 && NowPosition==1)	//玩家位置
							{
								MatrixLED_DrawPoint(4,6);
							}
							else	//障碍物和间隙
							{
								if(Obstacles[(NowPointer+i)%16+16]){MatrixLED_DrawPoint(4,7-i);}
								else{MatrixLED_ClearPoint(4,7-i);}
							}
						}
					}
				}
			}
			else	//如果是暂停状态,则闪烁玩家控制的“点”
			{
				if(FlashFlag)	//如果闪烁的标志为真
				{	//则不显示玩家控制的点
					if(NowPosition==0){MatrixLED_ClearPoint(3,6);}
					else{MatrixLED_ClearPoint(4,6);}
				}
				else	//否则显示
				{
					if(NowPosition==0){MatrixLED_DrawPoint(3,6);}
					else{MatrixLED_DrawPoint(4,6);}
				}
			}
		}
		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.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
	T0Count1++;
	T0Count2++;
	T0Count3++;
	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(T0Count>=Duration/10)	//每隔Duration ms移动一次障碍物
	{
		T0Count=0;
		MoveFlag=1;
	}
}

/**
  * 函    数:定时器1中断函数
  * 参    数:无
  * 返 回 值:无
  * 说    明:专门用定时器1来扫描显示LED点阵屏,定时器1的优先级要比定时器0的高,否则显示会有闪烁现象
  */
void Timer1_Routine() interrupt 3
{
	TL1=0x66;	//设置定时初值,定时1ms,12T@11.0592MHz
	TH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	if(Mode==2 && FlashFlag){P0=0xFF;}	//控制游戏结束后的全屏闪烁
	else{MatrixLED_Tick();}
}

总结

之前做过一个LCD1602版本的躲闪类游戏,是按照之前的思路将代码移植过来的。

感觉用点阵屏玩这个游戏效果更好,LCD1602版本的,由于硬件问题,速度较快时会有重影。

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

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

相关文章

告别“盘丝洞”车间:4-20mA无线传输如何重构工厂神经网?

4-20ma无线传输是利用无线模块将传统的温度、压力、液位等4-20mA电流信号转换为无线信号进行传输。这一技术突破了有线传输的限制&#xff0c;使得信号可以在更广泛的范围内进行灵活、快速的传递&#xff0c;无线传输距离可达到50KM。达泰4-20ma无线传输模块在实现工业现场应用…

VMware虚拟机突然无法ssh连接

遇到的情况&#xff1a; 功能全部正常的情况下&#xff0c;没有修改任何配置&#xff0c;重启电脑之后无法ssh连接 其实不太可能的可能原因&#xff1a; 1、虚拟机内部sshd服务未运行 systemctl status sshd systemctl start sshd 2、检查SSH端口监听 netstat -an | grep :…

班迪录屏--解决视频剪辑时声音和画面不同步的问题

原文网址&#xff1a;班迪录屏--解决视频剪辑时声音和画面不同步的问题_IT利刃出鞘的博客-CSDN博客 简介 本文介绍如何用班迪录屏解决视频剪辑时声音和画面不同步的问题。 问题描述 我用班迪录屏录了视频&#xff0c;用剪映进行剪辑&#xff0c;结果发现在剪辑时声音和画面…

Git上传项目到GitHub

Git上传项目到GitHub 下载Git客户端配置Git设置GitHub上传本地项目到Github 下载Git客户端 网址&#xff1a;Git Windows客户端。选择Standalone Installer(单独安装程序)&#xff0c;并点击64bit Git for Windows Setup(64位Git for Windows安装程序)进行下载。然后一路默认选…

【工具】Quicker/VBA|PPT 在指定位置添加有颜色的参考线

文章目录 效果展示使用方式技术原理更多原理ActivePresentation.Guides 概述主要属性和方法使用示例添加水平参考线添加垂直参考线删除所有参考线获取参考线数量 注意事项 致谢 效果展示 先展示效果&#xff1a; Quicker 动作&#xff1a;VBA 添加参考线 - Quicker 动作 使用…

第34节:迁移学习中的特征提取方法

迁移学习中的特征提取方法:原理、技术与应用 1. 迁移学习与特征提取概述 迁移学习(Transfer Learning)作为机器学习领域的重要范式 通过将源领域(source domain)学到的知识迁移到目标领域(target domain),有效解决了传统机器学习需要大量标注数据的瓶颈问题。 在迁…

(万字长文)Django数据库操作——ORM:数据交互显示前端网页

&#x1f31f; 如果这篇文章触动了你的心弦&#xff0c;请不要吝啬你的支持&#xff01; 亲爱的读者&#xff0c; 感谢你花时间阅读这篇分享。希望这里的每一个字都能为你带来启发或是让你会心一笑。如果你觉得这篇文章有价值&#xff0c;或者它解决了你一直以来的一个疑问&a…

实验-使用递归计算阶乘-RISC-V(计算机组成原理)

目录 一、实验内容 二、实验步骤 三、实验效果 四、实验环境 五、实验小结和思考 一、实验内容 一个典型的计算阶乘的递归过程如下图所示&#xff1a; 在这个任务中&#xff0c;一份汇编代码的框架“task4-阶乘”你需要使用RISC-V或MIPS汇编程序以递归的形式解决这个问题。…

ISO 26262-5 评估硬件架构度量值

两种硬件架构的度量&#xff0c; 用于评估相关项架构应对随机硬件失效的有效性。 应评估&#xff08;评估仅限于ASIL (B)、 C 和 D 的安全目标&#xff09; 1 应将按照附录 C 单点故障度量和潜伏故障度量的诊断覆盖率来评估 2 应结合残余故障和相关的潜伏故障来预估安全机制…

【Qt开发】显示类控件——QLCDNumber

目录 1&#xff0c;QLCDNumber的说明 2&#xff0c;QLCDNumber的运用 1&#xff0c;QLCDNumber的说明 QLCDNumer 是一个专门用来显示数字的控件。它类似于 "老式计算器" 的效果。它的核心属性如下&#xff1a; 2&#xff0c;QLCDNumber的运用 定时器 运用QLCDNumb…

音频AAC编码与RV1126的AENC模块的讲解

一.音频编码的原理 AAC编码的基本概念 AAC&#xff08;Advanced Audio Coding&#xff09;是一种高级音频编码格式&#xff0c;旨在提供比MP3更高的音质和更低的比特率。AAC是MPEG-2和MPEG-4标准的一部分&#xff0c;广泛应用于音乐、视频流媒体和广播等领域 音频为什么要进…

vue页面目录菜单有些属性是根据缓存读取的。如果缓存更新了。希望这个菜单也跟着更新。

父组件中有两个子组件。如果在B组件数据更新之后。A组件也跟着一起改变呢&#xff1f;如图如果我右边基本信息里面勾选了高血压&#xff0c;左侧菜单里面也要立刻出现一个高血压随访菜单&#xff0c;如果我取消勾选了左侧菜单就去掉。 左侧菜单的显示和隐藏的数据实际上是放在…

在TIA 博途中下载程序时找不到对应的网卡怎么办?

1. 检查物理连接 确认网线已正确连接PLC和PC&#xff0c;接口指示灯正常。 尝试更换网线或交换机端口&#xff0c;排除硬件故障。 2. 确认网卡驱动已安装 设备管理器检查&#xff1a; 右键点击“此电脑” → “管理” → “设备管理器”。 展开“网络适配器”&#xff0c;确…

《量子计算实战》PDF下载

内容简介 在加密、科学建模、制造物流、金融建模和人工智能等领域&#xff0c;量子计算可以极大提升解决问题的效率。量子系统正变得越来越强大&#xff0c;逐渐可用于生产环境。本书介绍了量子计算的思路与应用&#xff0c;在简要说明与量子相关的科学原理之后&#xff0c;指…

Linux入门(部分基础相关知识+常用命令+权限)

目录 1.基础背景了解 2、基本操作系统、linux相关知识 1.操作系统是一款用来管理软硬件资源的软件。 2.对于一个文件来说&#xff0c;是由文件内容文件属性构成的。空文件&#xff08;内容为空&#xff09;也占磁盘空间。 3.linux下的目录结构 4.linux下的删除 5.环境 6…

海拔案例分享-实践活动报名测评小程序

大家好&#xff0c;今天湖南海拔科技想和大家分享一款实践活动报名测评小程序&#xff0c;客户是长沙一家专注青少年科创教育的机构&#xff0c;这家机构平时要组织各种科创比赛、培训课程&#xff0c;随着学员增多&#xff0c;管理上的问题日益凸显&#xff1a;每次组织活动&a…

cmd里可以使用npm,vscode里使用npm 报错

cmd里可以使用npm,vscode里使用npm 报错 报错提示原因解决方法 报错提示 npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1&#xff0c;因为在此系 统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/ fwlink/?LinkID135170 中的 about_Executi…

JAVA开发工具延长方案

亲测稳定的延长方案与避坑指南 真的搞不懂了&#xff0c;说点专业的术语竟然成了 QINQUAN。那就直接点&#xff0c;把这个方案带给需要的开发者。 延长工具直通车 保姆级教程 延长方案https://mp.weixin.qq.com/s/uajM2Y9Vz6TnolzcLur_bw还是让大家看看&#xff0c;发什么会被…

CSS 浮动(Float)及其应用

1. 什么是浮动&#xff08;Float&#xff09;&#xff1f; 浮动元素会脱离正常的文档流&#xff08;Document Flow&#xff09;&#xff0c;并向左或向右移动&#xff0c;直到碰到父元素的边缘或另一个浮动元素。 基本语法 .float-left {float: left; }.float-right {float:…

CC53.【C++ Cont】一维前缀和

目录 1.定义 2.作用 3.例题:【模板】一维前缀和 分析 方法1:暴力解法 方法2:前缀和(简单的动态规划) 第一步:预处理 4.练习:P1115 最大子段和 分析 方法1:段长从1枚举到n 方法2:改进方法1 代码 提交结果 1.定义 快速求出数组中某一段的区间和,时间复杂度为(速度极…