当前位置: 首页 > news >正文

基于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();}
}

总结

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

相关文章:

  • 海外服务器安装Ubuntu 22.04图形界面并配置VNC远程访问指南
  • 【工具使用-数据库】MySQL数据库创建和使用
  • 反射内存网技术应用于数控系统
  • GAIA-2:用于自动驾驶的可控多视图生成世界模型
  • 使用stream进行列表循环和直接forEach循环的差异及使用场景
  • 支持远程搜索、分页加载和多选功能的 el-select 组件
  • SpringBoot自定义验证器:企业级参数校验架构设计与实践
  • 流量抓取工具(wireshark)
  • docker 中将dpo后的模型转为ollama能够调用的gguf格式
  • 对程序中使用到的字体进行子集化处理
  • C语言高频面试题——常量指针与指针常量区别
  • 文档处理控件Aspose.Words 教程:在 Word 中删除空白页完整指南
  • 【C++游戏引擎开发】第19篇:Compute Shader实现Tile划分
  • Cursor可视化大屏搭建__0420
  • 软件功能测试和非功能测试有什么区别和联系?
  • 【同济】高等数学6.7.8版答案
  • 1+X: Python程序开发职业技能等级要求(初级)练习资料分享
  • 软件工程师中级考试-上午知识点总结(下)
  • 基于OKR的企业管理数字化实践——以围棋教育机构流程优化为例
  • 【数字图像处理】图像纹理分析
  • 新质观察|解构低空经济产业集群发展战略
  • 浙江一季度GDP为22300亿元,同比增长6.0%
  • 杨国荣丨阐释学的内涵与意义——张江《阐释学五辨》序
  • 张宝亮任山东临沂市委书记
  • 女子伸腿阻止高铁关门等待同行人员,相关部门已介入调查
  • 遭遇FIFA三个转会窗禁令,申花有苦难言将选择赔偿