基于51单片机和8X8点阵屏、独立按键的单人弹球小游戏
目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、8X8点阵屏
- 2、独立按键
- 3、定时器0
- 4、定时器1
- 四、主函数
- 总结
系列文章目录
前言
用的是普中A2开发板,用到板上的独立按键、8X8点阵屏。
【单片机】STC89C52RC
【频率】12T@11.0592MHz
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
1、上电后向左循环滚动显示游戏名称的英文
2、改变球移动的速度时数字上下滚动显示
3、游戏开始前调整初始位置
4、游戏中
5、游戏结束后向左循环滚动显示得分
二、原理分析
1、向左或向上滚动显示字母或数字
每一个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];
将所需滚动显示的内容进行取模,保存到一个数组中(用code修饰,即把数据保存到Flash中,否则RAM空间不够用),滚动显示函数的参数中需要有一个偏移量Offset,定时器进行计时,每隔一段时间让Offset自增(或自减),就可以实现滚动显示的效果,滚动显示的实现利用与、或、移位等运算进行操作。向左滚动和向上滚动,对应的操作不同。向左滚动很简单,因为字符字模是纵向取模的,而上下滚动则需要用到移位等操作。
ASCII字符字模数据提取自B站江协科技STM32视频教程(OLED模块的使用)所分享的源码。所有ASCII的字符尺寸都是宽6高8。
2、独立按键的检测
用定时器扫描检测,这样可以防止阻塞主程序的运行。每隔20ms检查一次按键按下的状态。
检测出哪个按键被按下后,再跟上一次的检测结果对比,判断是按下瞬间,长按,还是松手瞬间,不同情况返回不同的值。长按后,设置成每隔100ms返回一次长按的键码,这样方便挡板连续移动,而且速度刚刚好,不快不慢。
除了游戏时移动挡板用到按下瞬间和长按,其他模式的操作用的是松手瞬间,因为检测出松手(下一次按下按键前)且在键码被获取前,键码不会改变。
3、挡板、球的显示、移动,球的碰撞
用8个字节作为显示缓存,对应64Bit(64位),每个Bit对应一个LED,1为亮,0为灭。
最基本的是其中的两个函数。通过与、或、移位运算可以只改变其中的一个Bit,这样就可以随意控制屏幕的显示了。
挡板用三个LED显示,挡板的移动只需要改变显示缓存的两个Bit,例如,向右移动,则将挡板最左边的LED熄灭,将挡板最右边LED的右侧的LED点亮,就可以完成移动了。建立一个平面直角坐标系,左上方为原点(0,0),向右为X轴正方向,向下为Y轴正方向,则右下角的坐标为(7,7)。然后用一个变量保存挡板中间的LED的位置就行了,要注意限制移动的范围,不要超出屏幕区域。
用一个LED表示球,用两个变量BallX、BallY保存球的X、Y坐标。球移动的时候,先将移动前所在位置的LED熄灭,再将移动的下一个位置的LED点亮,最后还要更新球的X、Y坐标对应的两个变量。
用两个变量记录球移动的方向,Direction1记录X方向上的移动,Direction2记录Y方向上的移动。值为0时无意义,值为1时向坐标轴的正方向移动,即向右或向下移动,值为2时向坐标轴的负方向移动,即向左或向上移动。当来到边界时,改变这两个变量的值就可以改变移动方向了。例如,球来到右边界,即来到第八列,Direction2不变,Direction1由1变成2。又例如,如果球向右下方运动,来到第七行与挡板碰撞,则Direction1保持为1不变,Direction2由1变成2。Direction1、Direction2这两个记录方向的变量是为了确定球的下一个位置,并更新BallX、BallY的值。
球的碰撞跟光的反射的原理类似。
需要注意的是,挡板的移动需要在定时器中断函数中进行更新显示,我自己测试的时候发现如果在主函数中更新显示的话,移动的时候会有卡顿的感觉。
通过变量MoveSpeed来保存球移动的速度,实际是移动的时间间隔,值越小,移动越快,结合定时器控制球的移动速度。
4、游戏结束的判定
当球来到第七行(挡板在第八行),如果球的正下方不是挡板,则视为游戏结束。
5、游戏结束的全屏闪烁
因开发板的LED点阵是通过定时器扫描显示的,所以需要在定时器中断函数中操作。如果游戏没结束,则正常扫描显示,如果游戏结束,则结合闪烁变量FlashFlag(FlashFlag在定时器中每隔500ms置反一次)让屏幕以1s的周期进行闪烁。如果FlashFlag为1,让屏幕停止显示(8列都不导通,即P0赋值为0xFF),如果FlashFlag为0,则让定时器正常扫描,让屏幕正常显示。
6、游戏分数
每接住一次球,分数增加1,游戏结束后循环滚动显示三位数得分。用16位的变量(无符号整型)记录分数,游戏结束后取出其中的百位、十位、个位,再根据0~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);
#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);}
}
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)
{
// AUXR&=0x7F; //定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)
TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)
TMOD|=0x01; //设置定时器模式(通过低四位设为16位不自动重装)
TL0=0xF0; //设置定时初值,定时10ms,12T@12.0000MHz
TH0=0xD8; //设置定时初值,定时10ms,12T@12.0000MHz
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=0xF0; //设置定时初值,定时10ms,12T@12.0000MHz
TH0=0xD8; //设置定时初值,定时10ms,12T@12.0000MHz
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)
{
// AUXR&=0xBF; //定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)
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 <STDLIB.H> //包含随机函数的声明
#include "MatrixLED.h"
#include "KeyScan.h"
#include "Timer0.h"
#include "Timer1.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 Direction1=2; //球左右移动的方向,0:左右方向不移动,1:向左移动,2:向右移动
unsigned char Direction2=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 int Score; //游戏得分
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,0x00,0x00,0x00,0x00,0x00,0x00, // 无显示
};
//取模要求:阴码(亮点为1),纵向取模,高位在下
unsigned char code Table1[]={ //游戏名称“弹球游戏”的英文:<<PINBALL>>
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 无显示
0x00,0x08,0x14,0x22,0x49,0x14,0x22,0x41, // << 宽8高8(自定义书名号:两个小于号)
0x00,0x7F,0x09,0x09,0x09,0x06, // P 宽6高8
0x00,0x00,0x41,0x7F,0x41,0x00, // I
0x00,0x7F,0x04,0x08,0x10,0x7F, // N
0x00,0x7F,0x49,0x49,0x49,0x36, // B
0x00,0x7C,0x12,0x11,0x12,0x7C, // A
0x00,0x7F,0x40,0x40,0x40,0x40, // L
0x00,0x7F,0x40,0x40,0x40,0x40, // L
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) //如果是循环滚动显示游戏英文名“<<PINBALL>>”的界面
{
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==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
}
/*循环滚动显示游戏英文名“<<PINBALL>>”*/
if(Mode==0)
{
if(OnceFlag) //切换到其他模式前,此if中的代码只执行1次
{
OnceFlag=0; //只执行一次的标志清零
Offset1=0; //偏移量清零
}
if(RollFlag) //如果滚动的标志RollFlag为真(非零即真)
{
RollFlag=0; //滚动的标志RollFlag清零
MatrixLED_MoveLeft(Table1,Offset1); //向左滚动
Offset1++; //每次向左移动一个像素
Offset1%=66; //越界清零,循环滚动显示
}
}
/*滚动显示速度的英文“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=50;break; //速度1,0.5s移动1次
case 1:MoveSpeed=40;break; //速度2,0.4s移动1次
case 2:MoveSpeed=30;break; //速度3,0.3s移动1次
case 3:MoveSpeed=20;break; //速度4,0.2s移动1次
case 4:MoveSpeed=10;break; //速度5,0.1s移动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=50;break;
case 1:MoveSpeed=40;break;
case 2:MoveSpeed=30;break;
case 3:MoveSpeed=20;break;
case 4:MoveSpeed=10;break;
default:break;
}
}
}
}
/*游戏准备阶段*/
else if(Mode==3)
{
if(OnceFlag)
{
OnceFlag=0;
//显示挡板
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;
//游戏初始化
GameOverFlag=0;
MoveFlag=1;
T0Count2=0;
Score=0;
Direction1=2;
Direction2=1;
}
if(MoveFlag) //如果球移动的标志为真
{
MoveFlag=0; //球移动的标志清零
if(BallY==6) //如果球在从上往下数的第7行
{
if(BallX!=Player && BallX!=Player-1 && BallX!=Player+1) //且正下方不是挡板
{
GameOverFlag=1; //则游戏结束
}
else
{
Score++; //每接住一次球,分数增加1
}
}
if(GameOverFlag==0) //如果游戏没结束
{
MatrixLED_ClearPoint(BallX,BallY); //清除球的显示
if(BallX==7){Direction1=1;} //如果球向右到了第8列,则球(反弹)改成向左移动
if(BallX==0){Direction1=2;} //如果球向左到了第1列,则球(反弹)改成向右移动
if(BallY==6){Direction2=1;} //如果球向下到了第7行,则球(反弹)改成向上移动
if(BallY==0){Direction2=2;} //如果球向上到了第1行,则球(反弹)改成向下移动
/*更新球的位置*/
if(Direction1==2){BallX+=1;}
else if(Direction1==1){BallX-=1;}
if(Direction2==2){BallY+=1;}
else if(Direction2==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;
Score--; //计算得分的时候加多了一分,要减去一
for(i=0;i<6;i++) //将得分的百位、十位、个位的字模写入数组ScoreShow中
{
ScoreShow[8+i]=Table5[(Score/100)*6+i]; //百位
}
for(i=0;i<6;i++)
{
ScoreShow[14+i]=Table5[(Score/10%10)*6+i]; //十位
}
for(i=0;i<6;i++)
{
ScoreShow[20+i]=Table5[(Score%10)*6+i]; //个位
}
}
if(RollFlag)
{
RollFlag=0;
MatrixLED_MoveLeft(ScoreShow,Offset1);
Offset1++;
Offset1%=26; //循环滚动显示
}
}
}
}
/**
* 函 数:定时器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 && (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();
}
}
总结
很简单的一个小游戏,难度不大,可以用来练练手。