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

目录

  • 系列文章目录
  • 前言
  • 一、效果展示
  • 二、原理分析
  • 三、各模块代码
    • 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.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甘腾胜@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,	//	T0x00,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;	//切换模式前只执行一次的标志置1AllowChangeModeFlag=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=40MatrixLED_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.0592MHzTH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHzT0Count1++;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.0592MHzTH1=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHzif(Mode==5 && FlashFlag){P0=0xFF;}	//控制游戏结束后的全屏闪烁else{MatrixLED_Tick();}T1Count++;if(T1Count>=20)	//每隔20ms检测一次按键{T1Count=0;Key_Tick();}
}

总结

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

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

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

相关文章

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

C学习&#xff1a;六个月从基础到就业——C学习之旅&#xff1a;STL迭代器系统 本文是我C学习之旅系列的第二十四篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第二篇&#xff0c;主要介绍C STL迭代器系统。查看完整系列目录了解更多内容。 引言 在上一篇文章中…

leetcode刷题——判断对称二叉树(C语言版)

题目描述&#xff1a; 示例 1&#xff1a; 输入&#xff1a;root [6,7,7,8,9,9,8] 输出&#xff1a;true 解释&#xff1a;从图中可看出树是轴对称的。 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 解释&#xff1a;从图中可看出最…

无法右键下载文档?网页PDF下载方法大全

适用场景&#xff1a;绕过付费限制/无法右键下载/动态加载PDF 方法1&#xff1a;浏览器原生下载&#xff08;成功率60%&#xff09; Chrome/Edge&#xff1a; 在PDF预览页点击工具栏 ⬇️下载图标&#xff08;右上角&#xff09; 快捷键&#xff1a;CtrlS → 保存类型选PDF …

基于缺失数据的2024年山东省专项债发行报告

一、数据情况 本次报告选取了山东省财政局公开的2024年专项债数据,共计2723条,发行期数是从第1期到第58期,由于网络原因,其中25期到32期,54到57期的数据有缺失,如下图所示。 从上图看出,一年52周,平均每周都有一期发布,因此持续做专项债的谋划很重要,一定要持续谋划…

Ubuntu数据连接访问崩溃问题

目录 一、分析问题 1、崩溃问题本地调试gdb调试&#xff1a; 二、解决问题 1. 停止 MySQL 服务 2. 卸载 MySQL 相关包 3. 删除 MySQL 数据目录 4. 清理依赖和缓存 5.重新安装mysql数据库 6.创建程序需要的数据库 三、验证 1、动态库更新了 2、头文件更新了 3、重新…

Linux系统编程 day10 接着线程(中期头大,还要写论文)

线程有点懵逼 线程之前函数回顾以及总结部分&#xff08;对不清楚的问题再思考&#xff09; 线程控制原语 进程控制原语 pthread_create(); fork(); pthread_self(); getpid(); pthread_exit(); exit(); pthread_join(); …

《浔川AI翻译v6.1.0问题已修复公告》

《浔川AI翻译v6.1.0问题已修复公告》 尊敬的浔川AI翻译用户&#xff1a; 感谢您对浔川AI翻译的支持与反馈&#xff01;我们已针对 **v6.1.0** 版本中用户反馈的多个问题进行了全面修复&#xff0c;并优化了系统稳定性。以下是本次修复的主要内容&#xff1a; 已修复问题 ✅…

深入理解 java synchronized 关键字

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

华三(H3C)与华为(Huawei)设备配置IPsec VPN的详细说明,涵盖配置流程、参数设置及常见问题处理

以下是针对华三&#xff08;H3C&#xff09;与华为&#xff08;Huawei&#xff09;设备配置IPsec VPN的详细说明&#xff0c;涵盖配置流程、参数设置及常见问题处理&#xff1a; 一、华三&#xff08;H3C&#xff09;设备IPsec VPN配置详解 1. 配置流程 华三IPsec VPN配置主要…

KBEngine 源代码分析(一):pyscript 目录文件介绍

pyscript 目录文件 pyscript 目录提供了 KBEngine 把 C++ 代码中的类注册到 Python 的机制 同时也提供了 C++ 调用 Python 方法的例子 相对现在的 C++ 17/20 ,这个目录的分装相对不优雅 不过不影响学习如何使用 Python 官方库提供的 API ,实现 C++ Python 混合编程 C++ …

线程入门3

synchronized修饰方法 synchronized可以修饰代码块(在线程入门2中有例子)&#xff0c;也可以修饰普通方法和静态方法。 修饰普通方法 修饰普通方法简化写法&#xff1a; 修饰静态方法 修饰静态方法简化写法&#xff1a; 注意&#xff1a;利用synchronized上锁&#xff0c;锁的…

linux上Flexlm命令

FlexLM 是一种灵活的许可证管理系统&#xff0c;广泛用于各种软件产品中&#xff0c;如 Autodesk 的 AutoCAD 和 Autodesk 的其他产品。它允许软件开发商控制软件的使用和分发&#xff0c;同时提供灵活的许可证管理策略。在 Linux 系统中使用 FlexLM 通常涉及到几个关键步骤&am…

【Java学习方法】终止循环的关键字

终止循环的关键字 一、break 作用&#xff1a;跳出最近的循环&#xff08;直接结束离break最近的那层循环&#xff09; 使用场景&#xff1a;一般搭配if条件判断&#xff0c;如果满足某个条件&#xff0c;就结束循环&#xff0c;&#xff08;场景&#xff1a;常见于暴力枚举中…

【论文精读】Reformer:高效Transformer如何突破长序列处理瓶颈?

目录 一、引言&#xff1a;当Transformer遇到长序列瓶颈二、核心技术解析&#xff1a;从暴力计算到智能优化1. 局部敏感哈希注意力&#xff08;LSH Attention&#xff09;&#xff1a;用“聚类筛选”替代“全量计算”关键步骤&#xff1a;数学优化&#xff1a; 2. 可逆残差网络…

关于在Springboot中设置时间格式问题

目录 1-设置全局时间格式1.Date类型的时间2.JDK8时间3.使Date类和JDK8时间类统统格式化时间 2-关于DateTimeFormat注解 1-设置全局时间格式 1.Date类型的时间 对于老项目来说&#xff0c;springboot中许多类使用的是Date类型的时间&#xff0c;没有用到LocalDateTime等JDK8时…

面试篇:Java并发与多线程

基础概念 什么是线程&#xff1f;线程和进程的区别是什么&#xff1f; 线程 是程序执行的最小单位&#xff0c;它是 CPU 调度和执行的基本单元。一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff08;如内存&#xff09;&#xff0c;但每个线程有自己的栈…

【Qt/C++】QPrinter关于QInternal::Printer的解析

1. 问题分析 QInternal::Printer在Qt框架中并不是一个直接暴露给用户的API。相反&#xff0c;它是一个枚举值&#xff0c;用于标识QPaintDevice的类型。在Qt中&#xff0c;QPaintDevice是一个抽象类&#xff0c;用于任何可以进行绘制的设备&#xff0c;如窗口、图像、打印机等…

uniapp返回上一页接口数据更新了,页面未更新

注意&#xff1a;不是组件套组件可以不使用setTimeout延时 返回上一页一般会走onshow&#xff0c;但是接口更新了页面未更新 onShow(() > {// 切换城市后重新调用数据if (areaId.value) {const timer setTimeout(async () > {timer && clearTimeout(timer);…

MCU开发学习记录11 - ADC学习与实践(HAL库) - 单通道ADC采集、多通道ADC采集、定时器触发连续ADC采集 - STM32CubeMX

名词解释&#xff1a; ADC&#xff1a; Analog-to-Digital SAR&#xff1a;Successive Approximation Register 本文将介绍ADC的概念、相关函数以及STM32CubeMX生成ADC的配置函数。针对于ADC实践&#xff1a;单通道采集芯片内部温度传感器&#xff08;ADC1_ch16&#xff09;&a…

68元撬动未来:明远智睿2351开发板重塑嵌入式开发生态

在嵌入式开发领域&#xff0c;价格与性能的矛盾始终存在&#xff1a;高端开发板功能强大但成本高昂&#xff0c;低价产品则往往受限于性能与扩展性。明远智睿2351开发板以68元&#xff08;含税&#xff09;的定价打破这一僵局&#xff0c;通过四核1.4G处理器、全功能Linux系统与…