基于STC89C52RC和8X8点阵屏、独立按键的小游戏《打砖块》

news2025/4/24 6:31:41

目录

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

系列文章目录


前言

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

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

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

一、效果展示

1、显示游戏名称。
在这里插入图片描述

2、可以调节速度。
在这里插入图片描述

3、游戏准备阶段可以调节初始位置。
在这里插入图片描述

4、真随机确定发射方向。
在这里插入图片描述

5、可以暂停游戏。
在这里插入图片描述

6、不管初始位置在哪里,最终都可以全部打掉方块。
在这里插入图片描述

7、游戏结束后循环滚动显示得分
在这里插入图片描述

二、原理分析

1、基于单人弹球游戏进行更改

基于51单片机和8X8点阵屏、独立按键的单人弹球小游戏

挡板移动、球的移动、球的碰撞等的简单原理介绍可以看一下上面文章。

2、与砖块的碰撞

这个稍复杂一些,但也不难。

用两个变量DirectionX、DirectionY保存球在水平、竖直这两个方向上运动的方向,球移动前,先检测上下左右,如果是砖块,则会发生碰撞,导致方向改变。

例如,假设原来X方向(水平方向)是向右运动的,如果右侧有砖块,则DirectionX的值由2变成1,2表示向右运动,1表示向左运动。其他类似。

还要考虑特殊情况,例如,球向右上方运动,如果上和右都没砖块,但右上方有砖块,这个时候球要反弹,即要变成向左下方运动。

球跟砖块以及墙发生碰撞时要注意,球的位置变量BallX和BallY不能越界,即不能超过规定的范围。所以碰撞的各种情况都要考虑。

三、各模块代码

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);
bit 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:灭
  * 说    明:左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
  */
bit 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;}
	}
}

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;
unsigned char KeyNumber1;

/**
  * 函    数:获取独立按键键码
  * 参    数:无
  * 返 回 值:按下按键的键码,范围: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.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甘腾胜@20250327
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】8X8LED点阵屏、独立按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【操作说明】
(1)显示游戏名称界面和显示速度的英文的界面按任意按键跳过
(2)速度选择界面通过K1和K2选择速度
(3)K4为确定键,K3为返回键
(4)玩家通过独立按键K1和K2控制挡板左右移动
(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:游戏进行中,5:游戏结束全屏闪烁,6:显示得分的英文,7:循环显示二位数得分
unsigned char AllowChangeModeFlag=1;	//允许改变模式的标志,1:允许改变,0:不允许改变
unsigned char Player=4;	//玩家挡板(下挡板)的中心位置,范围:1~6
unsigned char BallX=4;	//球的X坐标,左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
unsigned char BallY=6;	//球的Y坐标,左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
unsigned char DirectionX=2;	//球左右移动的方向,0:左右方向不移动,1:向左移动,2:向右移动
unsigned char DirectionY=1;	//球上下移动的方向,1:向上移动,2:向下移动
unsigned char MoveFlag;	//球移动的标志,1:移动,0:不移动
unsigned char OnceFlag;	//各模式中切换为其他模式前只执行一次的标志(类似于主函数死循环前的那部分),1:执行,0:不执行
unsigned char Offset1;	//偏移量,用来控制英文字母向左滚动显示(切换模式后清零)
unsigned char Offset2;	//偏移量,用来控制速度对应的数字上下滚动显示(切换模式后不清零)
unsigned char UpFlag;	//速度选择界面,数字向上滚动显示的标志,1:滚动,0:不滚动
unsigned char DownFlag;	//速度选择界面,数字向下滚动显示的标志,1:滚动,0:不滚动
unsigned char RollFlag;	//字母或数字滚动显示的标志,1:滚动,0:不滚动
unsigned char RollCount;	//上下滚动的计次
unsigned char MoveSpeed=100;	//球移动的速度,值越小,速度越快,单位是10ms(定时器0定时10ms),默认0.5s移动一次
unsigned char GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char FlashFlag;	//闪烁的标志,1:不显示,0:显示
unsigned char T0Count1,T0Count2,T0Count3,T0Count4;	//定时器计数变量
unsigned char Score;	//游戏得分
unsigned char PauseFlag;	//暂停的标志,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),纵向取模,高位在下
unsigned char code Table1[]={	//游戏名称“弹球游戏”的英文:<<BREAKOUT>>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x08,0x14,0x22,0x49,0x14,0x22,0x41,	// <<	宽8高8(自定义书名号:两个小于号)
0x00,0x7F,0x49,0x49,0x49,0x36,	//	B	宽6高8
0x00,0x7F,0x09,0x19,0x29,0x46,	//	R
0x00,0x7F,0x49,0x49,0x49,0x41,	//	E
0x00,0x7C,0x12,0x11,0x12,0x7C,	//	A
0x00,0x7F,0x08,0x14,0x22,0x41,	//	K
0x00,0x3E,0x41,0x41,0x41,0x3E,	//	O
0x00,0x3F,0x40,0x40,0x40,0x3F,	//	U
0x00,0x01,0x01,0x7F,0x01,0x01,	//	T

0x00,0x41,0x22,0x14,0x49,0x22,0x14,0x08,	// >>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
};
unsigned char code Table2[]={	//“速度”的英文:SPEED,宽6高8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x46,0x49,0x49,0x49,0x31,	// S
0x00,0x7F,0x09,0x09,0x09,0x06,	// P
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x7F,0x49,0x49,0x49,0x41,	// E
0x00,0x7F,0x41,0x41,0x22,0x1C,	// D
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 无显示
0x00,0x00,0x00,0x42,0x7F,0x40,0x00,0x00,	// 1	如果不按按键跳过,则在显示“1”后自动切换到下一个模式
};
unsigned char code Table3[]={	//速度选择界面速度对应的数字:“123451”,宽8高8,数值越大,速度越快
0x00,0x00,0x00,0x42,0x7F,0x40,0x00,0x00,	// 1
0x00,0x00,0x42,0x61,0x51,0x49,0x46,0x00,	// 2
0x00,0x00,0x21,0x41,0x45,0x4B,0x31,0x00,	// 3
0x00,0x00,0x18,0x14,0x12,0x7F,0x10,0x00,	// 4
0x00,0x00,0x27,0x45,0x45,0x45,0x39,0x00,	// 5
0x00,0x00,0x00,0x42,0x7F,0x40,0x00,0x00,	// 1
};
unsigned char code Table4[]={	//得分的英文:“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 Table5[]={	//游戏得分的字模数据,宽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)
		{
			if(Mode==0)	//如果是循环滚动显示游戏英文名“<<BREAKOUT>>”的界面
			{
				if(KeyNum>=9 && KeyNum<=12 && AllowChangeModeFlag)	//如果按下任意按键(松手瞬间),且允许改变模式
				{
					Mode=1;	//切换到滚动显示速度的英文“SPEED”的界面
					OnceFlag=1;	//切换模式前只执行一次的标志置1
					AllowChangeModeFlag=0;	//允许切换模式的标志置零
				}
			}
			else if(Mode==1)	//如果是滚动显示速度的英文“SPEED”的界面
			{
				if(KeyNum>=9 && KeyNum<=12 && AllowChangeModeFlag)	//如果按下任意按键(松手瞬间),且允许改变模式
				{
					Mode=2;	//切换到难度选择界面
					OnceFlag=1;
					AllowChangeModeFlag=0;
				}
			}
			else if(Mode==2)	//如果是速度选择界面
			{
				if(KeyNum==9)	//如果按了“上”键K1(松手瞬间)
				{
					UpFlag=1;	//数字向上滚动的标志置1
				}
				if(KeyNum==10)	//如果按了“下”键K2(松手瞬间)
				{
					DownFlag=1;	//数字向下滚动的标志置1
				}
				if(KeyNum==12 && AllowChangeModeFlag)	//如果按了确认键K4(松手瞬间),且允许改变模式
				{
					Mode=3;	//切换到游戏准备界面
					OnceFlag=1;
					AllowChangeModeFlag=0;
					MatrixLED_Clear();
				}
			}
			else if(Mode==3)	//如果是游戏准备界面
			{
				if(KeyNum==12 && AllowChangeModeFlag)	//如果按了开始键K4(松手瞬间),且允许改变模式
				{
					Mode=4;	//切换到游戏模式
					OnceFlag=1;
					AllowChangeModeFlag=0;
				}
			}
			else if(Mode==4)	//如果是正在游戏
			{
				if(KeyNum==12)	//如果按了开始键K4(松手瞬间),切换暂停和继续
				{
					PauseFlag=!PauseFlag;
					if(PauseFlag==0)
					{
						T0Count2=0;
						MoveFlag=0;
					}
				}
			}
			else if(Mode==5)	//如果是游戏结束全屏闪烁的界面
			{
				if(KeyNum==12 && AllowChangeModeFlag)	//如果按了确认键K4(松手瞬间),且允许改变模式
				{
					Mode=6;	//切换到显示英文“SCORE”的界面
					OnceFlag=1;
					AllowChangeModeFlag=0;
				}
			}
			else if(Mode==6)	//如果是显示英文“SCORE”的界面
			{
				if(KeyNum==12 && AllowChangeModeFlag)	//如果按了确认键K4(松手瞬间),且允许改变模式
				{
					Mode=7;	//切换到循环显示二位数得分的界面
					OnceFlag=1;
					AllowChangeModeFlag=0;
				}
			}
			else if(Mode==7)	//如果是循环显示二位数得分的界面
			{
				if(KeyNum==11 && AllowChangeModeFlag)	//如果按了返回键K3(松手瞬间),且允许改变模式
				{
					Mode=2;	//返回到速度选择界面
					OnceFlag=1;
					AllowChangeModeFlag=0;
				}
			}

			AllowChangeModeFlag=1;	//允许改变模式的标志置1
		}


		/*循环滚动显示游戏英文名“<<BREAKOUT>>”*/
		if(Mode==0)
		{
			if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次
			{
				OnceFlag=0;	//只执行一次的标志清零
				Offset1=0;	//偏移量清零
			}
			if(RollFlag)	//如果滚动的标志RollFlag为真(非零即真)
			{
				RollFlag=0;	//滚动的标志RollFlag清零
				MatrixLED_MoveLeft(Table1,Offset1);	//向左滚动
				Offset1++;	//每次向左移动一个像素
				Offset1%=72;	//越界清零,循环滚动显示
			}
		}
		/*滚动显示速度的英文“SPEED”*/
		else if(Mode==1)
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				Offset1=0;
			}
			if(RollFlag && Offset1<=46)	//只向左滚动显示一次,不循环滚动显示
			{
				RollFlag=0;
				MatrixLED_MoveLeft(Table2,Offset1);
				Offset1++;
			}
			else if(Offset1>46)	//显示数字“1”之后,自动切换到速度选择界面
			{
				Mode=2;
				Offset1=0;
			}
		}
		/*速度选择界面*/
		else if(Mode==2)
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				MatrixLED_MoveUp(Table3,Offset2);
			}
			if(RollFlag && UpFlag)	//如果滚动标志为1,且向上滚动的标志也为1
			{
				RollFlag=0;
				Offset2++;	//向上移动一个像素
				Offset2%=40;	//越界清零,总共5个数字,每个数字的高度是8,所以是5*8=40
				MatrixLED_MoveUp(Table3,Offset2);	//更新显示
				RollCount++;
				if(RollCount==8)	//移动了8个像素后停止移动
				{
					RollCount=0;
					UpFlag=0;
					Offset2=(Offset2/8)*8;	//防止移动到一半的时候按下“上”或“下”按键导致数字没有在点阵屏中间,Offset2的值必须是8的整数倍
					switch(Offset2/8)
					{
						case 0:MoveSpeed=100;break;	//速度1,1.00s移动1次
						case 1:MoveSpeed= 80;break;	//速度2,0.80s移动1次
						case 2:MoveSpeed= 60;break;	//速度3,0.60s移动1次
						case 3:MoveSpeed= 40;break;	//速度4,0.40s移动1次
						case 4:MoveSpeed= 20;break;	//速度5,0.20s移动1次
						default:break;
					}
				}
			}
			if(RollFlag && DownFlag)	//如果滚动标志为1,且向下滚动的标志也为1
			{
				RollFlag=0;
				if(Offset2==0){Offset2=40;}
				Offset2--;
				MatrixLED_MoveUp(Table3,Offset2);
				RollCount++;
				if(RollCount==8)
				{
					RollCount=0;
					DownFlag=0;
					Offset2=(Offset2/8)*8;
					switch(Offset2/8)
					{
						case 0:MoveSpeed=100;break;	//速度1,1.00s移动1次
						case 1:MoveSpeed= 80;break;	//速度2,0.80s移动1次
						case 2:MoveSpeed= 60;break;	//速度3,0.60s移动1次
						case 3:MoveSpeed= 40;break;	//速度4,0.40s移动1次
						case 4:MoveSpeed= 20;break;	//速度5,0.20s移动1次
						default:break;
					}
				}
			}
		}
		/*游戏准备阶段*/
		else if(Mode==3)
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				//显示前三行“砖块”
				for(i=0;i<8;i++)
				{
					DisplayBuffer[i]|=0x07;
				}

				//显示挡板
				MatrixLED_DrawPoint(Player,7);
				MatrixLED_DrawPoint(Player+1,7);
				MatrixLED_DrawPoint(Player-1,7);

				//显示球
				MatrixLED_ClearPoint(BallX,BallY);
				MatrixLED_DrawPoint(Player,6);

				BallX=Player;BallY=6;
			}
		}
		/*游戏进行中*/
		else if(Mode==4)
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				//游戏初始化
				PauseFlag=0;
				GameOverFlag=0;
				MoveFlag=1;
				T0Count2=0;
				Score=0;
				srand(TL0);	//以定时器0的低八位做种子,从而产生真随机数
				DirectionX=rand()%2+1;	//球可能向左上方发射或者向右上方发射
				DirectionY=1;
			}
			if(MoveFlag && PauseFlag==0)	//如果球移动的标志为真,且不是暂停
			{
				MoveFlag=0;	//球移动的标志清零

				if(BallY==6)	//如果球在从上往下数的第7行
				{
					if(BallX!=Player && BallX!=Player-1 && BallX!=Player+1)	//且正下方不是挡板
					{
						GameOverFlag=1;	//则游戏结束
					}
				}

				if(GameOverFlag==0)	//如果游戏没结束
				{

					MatrixLED_ClearPoint(BallX,BallY);	//清除球的显示

					if(BallX==7){DirectionX=1;}	//如果球向右到了第8列,则球(反弹)改成向左移动
					if(BallX==0){DirectionX=2;}	//如果球向左到了第1列,则球(反弹)改成向右移动
					if(BallY==6){DirectionY=1;}	//如果球向下到了第7行,则球(反弹)改成向上移动
					if(BallY==0){DirectionY=2;}	//如果球向上到了第1行,则球(反弹)改成向下移动

					if(DirectionY==1 && MatrixLED_GetPoint(BallX,BallY-1) && BallY>0)
					{	//如果是向上运动,且正上方是“砖块”,且不是在第一行
						DirectionY=2;	//改成向下运动
						MatrixLED_ClearPoint(BallX,BallY-1);	//消除正上方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionY==2 && MatrixLED_GetPoint(BallX,BallY+1) && BallY<3)
					{	//如果是向下运动,且正下方是“砖块”,且在前三行
						DirectionY=1;	//改成向上运动
						MatrixLED_ClearPoint(BallX,BallY+1);	//消除正下方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==1 && MatrixLED_GetPoint(BallX-1,BallY) && BallX>0)
					{	//如果是向左运动,且正左方是“砖块”,且不是在第一列
						DirectionX=2;	//改成向右运动
						MatrixLED_ClearPoint(BallX-1,BallY);	//消除正左方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==2 && MatrixLED_GetPoint(BallX+1,BallY) && BallX<7)
					{	//如果是向右运动,且正右方是“砖块”,且不是在第八列
						DirectionX=1;	//改成向左运动
						MatrixLED_ClearPoint(BallX+1,BallY);	//消除正右方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==1 && DirectionY==1 && MatrixLED_GetPoint(BallX-1,BallY-1) && BallX>0 && BallY>0)
					{	//如果是向左上方运动,且左上方是“砖块”,且不是在第一列和第一行
						if(BallX<7){DirectionX=2;}	//如果不是在第八列,则改成向右运动
						DirectionY=2;	//改成向下运动
						MatrixLED_ClearPoint(BallX-1,BallY-1);	//消除左上方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==2 && DirectionY==1 && MatrixLED_GetPoint(BallX+1,BallY-1) && BallX<7 && BallY>0)
					{	//如果是向右上方运动,且右上方是“砖块”,且不是在第八列和第一行
						if(BallX>0){DirectionX=1;}	//如果不是在第一列,则改成向左运动
						DirectionY=2;	//改成向下运动
						MatrixLED_ClearPoint(BallX+1,BallY-1);	//消除右上方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==1 && DirectionY==2 && MatrixLED_GetPoint(BallX-1,BallY+1) && BallX>0 && BallY<3)
					{	//如果是向左下方运动,且左下方是“砖块”,且不是在第一列,且在前三行
						if(BallX<7){DirectionX=2;}	//如果不是在第八列,则改成向右运动
						if(BallY>0){DirectionY=1;}	//如果不是在第一行,则改成向上运动
						MatrixLED_ClearPoint(BallX-1,BallY+1);	//消除左下方的“砖块”
						Score++;	//分数加一
					}
					if(DirectionX==2 && DirectionY==2 && MatrixLED_GetPoint(BallX+1,BallY+1) && BallX<7 && BallY<3)
					{	//如果是向右下方运动,且右下方是“砖块”,且不是在第八列,且在前三行
						if(BallX>0){DirectionX=1;}	//如果不是在第一列,则改成向左运动
						if(BallY>0){DirectionY=1;}	//如果不是在第一行,则改成向上运动
						MatrixLED_ClearPoint(BallX+1,BallY+1);	//消除右下方的“砖块”
						Score++;	//分数加一
					}

					if(Score>=24)	//如果分数达到24分,即全部“砖块”被打掉
					{
						GameOverFlag=1;	//游戏结束
						Mode=5;	//切换到模式5
					}
					else	//如果游戏没结束
					{
						/*更新球的位置*/
						if(DirectionX==2){BallX+=1;}
						else if(DirectionX==1){BallX-=1;}
						if(DirectionY==2){BallY+=1;}
						else if(DirectionY==1){BallY-=1;}
					}

					/*显示球的下一个位置*/
					MatrixLED_DrawPoint(BallX,BallY);
				}
				else	//如果游戏结束
				{
					Mode=5;	//切换到模式5
				}
			}
		}
		/*游戏结束全屏闪烁*/
		else if(Mode==5)
		{
			//在定时器中断中实现全屏闪烁
		}
		/*显示得分的英文“SCORE”*/
		else if(Mode==6)
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				Offset1=0;
			}
			if(RollFlag && Offset1<=38)	//只滚动显示一次英文“SCORE”
			{
				RollFlag=0;
				MatrixLED_MoveLeft(Table4,Offset1);
				Offset1++;
			}
			else if(Offset1>38) //滚动结束后,自动切换到循环显示得分的模式
			{
				Mode=7;
				OnceFlag=1;
			}	
		}
		/*循环显示得分*/
		else if(Mode==7)
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				Offset1=0;
				for(i=0;i<6;i++)	//将得分的十位、个位的字模写入数组ScoreShow中
				{
					ScoreShow[ 8+i]=Table5[(Score/10)*6+i];	//十位
				}
				for(i=0;i<6;i++)
				{
					ScoreShow[14+i]=Table5[(Score%10)*6+i];	//个位
				}
			}
			if(RollFlag)
			{
				RollFlag=0;
				MatrixLED_MoveLeft(ScoreShow,Offset1);
				Offset1++;
				Offset1%=20;	//循环滚动显示
			}
		}
	}
}

/**
  * 函    数:定时器0中断函数
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Routine() interrupt 1
{
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
	T0Count1++;
	T0Count2++;
	T0Count3++;
	T0Count4++;
	if(T0Count1>=2)	//每隔20ms检测一次键码,且更新挡板的显示
	{
		T0Count1=0;

		/*在中断函数中更新挡板的位置*/ //在主循环中更新显示会有卡顿的现象
		if(KeyNumber1 && PauseFlag==0 && (Mode==3 || Mode==4))	//如果有独立按键按下且处于游戏准备阶段或正在游戏且不是暂停
		{
			if(KeyNumber1==1 || KeyNumber1==5)	//如果短按K1或长按K1
			{
				Player--;	//下挡板向左移动一格
				if(Player<1){Player=1;}	//限制范围
				MatrixLED_ClearPoint(Player+2,7);	//更新显示
				MatrixLED_DrawPoint(Player-1,7);	//更新显示
				if(Mode==3){OnceFlag=1;}	//限制范围
			}
			if(KeyNumber1==2 || KeyNumber1==6)	//如果短按K2或长按K2
			{
				Player++;	//下挡板向右移动一格
				if(Player>6){Player=6;}
				MatrixLED_ClearPoint(Player-2,7);
				MatrixLED_DrawPoint(Player+1,7);
				if(Mode==3){OnceFlag=1;}
			}
			KeyNumber1=0;	//独立按键的键码清零
		}
	}
	if(T0Count2>=MoveSpeed)	//控制球移动的速度,MoveSpeed越小,球移动的速度越快
	{
		T0Count2=0;
		MoveFlag=1;
	}
	if(T0Count3>=10)	//每隔100ms滚动显示一次字母或数字
	{
		T0Count3=0;
		RollFlag=1;
	}
	if(T0Count4>=50)	//每隔500ms置反FlashFlag
	{
		T0Count4=0;
		FlashFlag=!FlashFlag;
	}
}

/**
  * 函    数:定时器1中断函数
  * 参    数:无
  * 返 回 值:无
  */
void Timer1_Routine() interrupt 3
{
	static unsigned char T1Count;
	TL1=0x66;	//设置定时初值,定时1ms,12T@11.0592MHz
	TH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	if(Mode==5 && FlashFlag){P0=0xFF;}	//控制游戏结束后的全屏闪烁
	else{MatrixLED_Tick();}
	T1Count++;
	if(T1Count>=20)	//每隔20ms检测一次按键
	{
		T1Count=0;
		Key_Tick();
	}
}

总结

此游戏关键在于球与砖块、墙壁碰撞的各种情况都要考虑。

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

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

相关文章

数字电子技术基础(五十)——硬件描述语言简介

目录 1 硬件描述语言简介 1.1 硬件描述语言简介 1.2 硬件编程语言的发展历史 1.3 两种硬件描述的比较 1.4 硬件描述语言的应用场景 1.5 基本程序结构 1.5.1 基本程序结构 1.5.2 基本语句和描述方法 1.5.3 仿真 1 硬件描述语言简介 1.1 硬件描述语言简介 硬件描述语…

【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件common.py解读

【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件common.py解读 文章目录 【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件common.py解读前言autopad函数Conv类__init__成员函数forward成员函数forward_fuse成员函数 Bottleneck类__init__成员…

16.Chromium指纹浏览器开发教程之WebGPU指纹定制

WebGPU指纹概述 WebGPU是下一代的Web图形和计算API&#xff0c;旨在提供高性能的图形渲染和计算能力。它是WebGL的后继者&#xff0c;旨在利用现代GPU的强大功能&#xff0c;使得Web应用能够实现接近原生应用的图形和计算性能。而且它是一个低级别的API&#xff0c;可以直接与…

SQL预编译——预编译真的能完美防御SQL注入吗

SQL注入原理 sql注入是指攻击者拼接恶意SQL语句到接受外部参数的动态SQL查询中&#xff0c;程序本身 未对插入的SQL语句进行过滤&#xff0c;导致SQL语句直接被服务端执行。 拼接的SQL查询例如&#xff0c;通过在id变量后插入or 11这样的条件&#xff0c;来绕过身份验证&#…

运行neo4j.bat console 报错无法识别为脚本,PowerShell 教程:查看语言模式并通过注册表修改受限模式

无法将“D:\neo4j-community-4.4.38-windows\bin\Neo4j-Management\Get-Args.ps1”项识别为cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 前提配置好环境变量之后依然报上面的错…

【EDA软件】【设计约束和分析操作方法】

1. 设计约束 设计约束主要分为物理约束和时序约束。 物理约束主要包括I/O接口约束&#xff08;如引脚分配、电平标准设定等物理属性的约束&#xff09;、布局约束、布线约束以及配置约束。 时序约束是FPGA内部的各种逻辑或走线的延时&#xff0c;反应系统的频率和速度的约束…

【Lua】Lua 入门知识点总结

Lua 入门学习笔记 本教程旨在帮助有编程基础的学习者快速入门Lua编程语言。包括Lua中变量的声明与使用&#xff0c;包括全局变量和局部变量的区别&#xff0c;以及nil类型的概念、数值型、字符串和函数的基本操作&#xff0c;包括16进制表示、科学计数法、字符串连接、函数声明…

光谱相机在肤质检测中的应用

光谱相机在肤质检测中具有独特优势&#xff0c;能够通过多波段光谱分析皮肤深层成分及生理状态&#xff0c;实现‌非侵入式、高精度、多维度的皮肤健康评估‌。以下是其核心应用与技术细节&#xff1a; ‌一、工作原理‌ ‌光谱反射与吸收特性‌&#xff1a; ‌血红蛋白‌&a…

机器学习第一篇 线性回归

数据集&#xff1a;公开的World Happiness Report | Kaggle中的happiness dataset2017. 目标&#xff1a;基于GDP值预测幸福指数。&#xff08;单特征预测&#xff09; 代码&#xff1a; 文件一&#xff1a;prepare_for_traning.py """用于科学计算的一个库…

CS144 Lab1实战记录:实现TCP重组器

文章目录 1 实验背景与要求1.1 TCP的数据分片与重组问题1.2 实验具体任务 2 重组器的设计架构2.1 整体架构2.2 数据结构设计 3 重组器处理的关键场景分析3.1 按序到达的子串&#xff08;直接写入&#xff09;3.2 乱序到达的子串&#xff08;需要存储&#xff09;3.3 与已处理区…

Linux安装mysql_exporter

mysqld_exporter 是一个用于监控 MySQL 数据库的 Prometheus exporter。可以从 MySQL 数据库的 metrics_schema 收集指标&#xff0c;相关指标主要包括: MySQL 服务器指标:例如 uptime、version 等数据库指标:例如 schema_name、table_rows 等表指标:例如 table_name、engine、…

BeautifulSoup 库的使用——python爬虫

文章目录 写在前面python 爬虫BeautifulSoup库是什么BeautifulSoup的安装解析器对比BeautifulSoup的使用BeautifulSoup 库中的4种类获取标签获取指定标签获取标签的的子标签获取标签的的父标签(上行遍历)获取标签的兄弟标签(平行遍历)获取注释根据条件查找标签根据CSS选择器查找…

HTTP的Header

一、HTTP Header 是什么&#xff1f; HTTP Header 是 HTTP 协议中的头部信息部分&#xff0c;位于请求或响应的起始行之后&#xff0c;用来在客户端&#xff08;浏览器等&#xff09;与服务器之间传递元信息&#xff08;meta-data&#xff09;&#xff08;简单理解为传递信息的…

linux虚拟机网络问题处理

yum install -y yum-utils \ > device-mapper-persistent-data \ > lvm2 --skip-broken 已加载插件&#xff1a;fastestmirror, langpacks Loading mirror speeds from cached hostfile Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&arch…

AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B专属管家大模型

环境&#xff1a; AI-Sphere-Butler WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 Qwen2.-1.5B/3B Llama factory llama.cpp 问题描述&#xff1a; AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B管家大模型 解决方案&#xff1a; 一、准备数据集我这…

协同推荐算法实现的智能商品推荐系统 - [基于springboot +vue]

&#x1f6cd;️ 智能商品推荐系统 - 基于springboot vue &#x1f680; 项目亮点 欢迎来到未来的购物体验&#xff01;我们的智能商品推荐系统就像您的私人购物顾问&#xff0c;它能读懂您的心思&#xff0c;了解您的喜好&#xff0c;为您精心挑选最适合的商品。想象一下&am…

Jenkins的地位和作用

所处位置 Jenkins 是一款开源的自动化服务器&#xff0c;广泛应用于软件开发和测试流程中&#xff0c;主要用于实现持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;。它在开发和测试中的位置和作用可以从以下几个方面来理解&#xff1a; 1. 在开发和测…

【集合】底层原理实现及各集合之间的区别

文章目录 集合2.1 介绍一下集合2.2 集合遍历的方法2.3 线程安全的集合2.4 数组和集合的区别2.5 ArrayList和LinkedList的区别2.6 ArrayList底层原理2.7 LinkedList底层原理2.8 CopyOnWriteArrayList底层原理2.9 HashSet底层原理2.10 HashMap底层原理2.11 HashTable底层原理2.12…

srp batch

参考网址&#xff1a; Unity MaterialPropertyBlock 正确用法&#xff08;解决无法合批等问题&#xff09;_unity_define_instanced_prop的变量无法srp合批-CSDN博客 URP | 基础CG和HLSL区别 - 哔哩哔哩 (bilibili.com) 【直播回放】Unity 批处理/GPU Instancing/SRP Batche…

【Linux运维涉及的基础命令与排查方法大全】

文章目录 前言1、计算机网络常用端口2、Kali Linux中常用的命令3、Kali Linux工具的介绍4、Ubuntu没有网络连接解决方法5、获取路由6、数据库端口 前言 以下介绍计算机常见的端口已经对应的网络协议&#xff0c;Linux中常用命令&#xff0c;以及平时运维中使用的排查网络故障的…