基于51单片机和16X16点阵屏、矩阵按键的小游戏《俄罗斯方块》

发布于:2025-07-17 ⋅ 阅读:(15) ⋅ 点赞:(0)

系列文章目录


前言

《俄罗斯方块》,一款经典的、怀旧的小游戏,单片机入门必写程序。

有两个版本,一个使用MAX7219驱动的16X16LED点阵屏,用普中A2开发板的矩阵按键控制;另一个使用74HC138和74HC595驱动的16X16LED点阵屏,使用自制的八位独立按键控制。

本文代码对应的是MAX7219驱动的16X16LED点阵屏。

【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】16X16点阵屏(MAX7219驱动)、矩阵按键

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

一、效果展示

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

二、原理分析

跟8X8点阵屏的俄罗斯方块的原理是一样的,简单的原理分析可以看我上一篇博客:

基于51单片机和8X8点阵屏、矩阵按键的小游戏《俄罗斯方块》

显存数据的数据要由 unsigned char 改为 unsigned int ,因为游戏区域大小为10像素X16像素,显存数组的数据要改为10个,因为有10列。其他相关函数也需要轻轻改一下。

第1列~第10列是游戏区域,第11列的LED全部点亮,作为分隔线,右侧还有5列,右侧上方显示下一个方块,右侧中间用二进制(四个像素)显示速度值,速度值的范围是1~10,右侧下方用16个像素(15列、16列)通过二进制显示得分,也是用16个像素(13列、14列)通过二进制显示所消行数。

三、各模块代码

1、16X16点阵屏(MAX7219驱动)

h文件

#ifndef __MATRIXLED__
#define __MATRIXLED__

void MAX7219_WriteByte(unsigned char Byte);
void MAX7219_Set(unsigned char Address,unsigned char Data);
void MatrixLED_Update(void);
void MatrixLED_Clear(void);
void MatrixLED_Init(void);
void MatrixLED_HS(unsigned char *Array,unsigned int Offset);
void MatrixLED_VS(unsigned char *Array,unsigned int Offset);
void MatrixLED_ControlPoint(char X,char Y,unsigned char State);
unsigned char MatrixLED_GetPoint(char X,char Y);
void MatrixLED_WhetherToDisplay(unsigned char WhetherOrNot);

#endif

c文件

#include <REGX52.H>

//引脚配置
sbit MAX7219_DIN=P2^0;	//串行数据
sbit MAX7219_CS=P2^1;	//片选,上升沿有效
sbit MAX7219_CLK=P2^2;	//串行时钟,上升沿有效

/** 屏幕显存数组数据存储格式:
  * 
  * 纵向8点,高位在下,先从上到下
  * 每一个Bit对应一个像素点,即对应一个LED
  * 
  * 	   ————        ————
  *       |	   |      |	   |
  *       |	   |      |	   |
  *       |	   v      |	   v
  *       |	          |	   
  * B0	  |	   B0	  |	   B0
  * B1	  |	   B1	  |	   B1
  * B2	  |	   B2	  |	   B2
  * B3	  |	   B3	  |	   B3	 ...	
  * B4	  |	   B4	  |	   B4
  * B5	  |	   B5	  |	   B5
  * B6	  |	   B6	  |	   B6
  * B7	  |	   B7	  |	   B7
  *                        
  * |          |           |
  * |          |           |
  * |          |           |
  * |          |           |
  * v          |           |
  *                        
  * B0	  |	   B0	  |	   B0
  * B1	  |	   B1	  |	   B1
  * B2	  |	   B2	  |	   B2
  * B3	  |	   B3	  |	   B3	 ...
  * B4	  |	   B4	  |	   B4
  * B5	  |	   B5	  |	   B5
  * B6	  |	   B6	  |	   B6
  * B7	  |	   B7	  |	   B7
  *       |           |	    
  * |	  |    |	  |	   |
  * |	  |    |	  |	   |	  
  *   ———>       ———>  	     ———> 
  */

/** 屏幕坐标轴定义:
  * 
  * 左上角为原点(0,0)
  * 横向向右为X轴,取值范围:0~15
  * 纵向向下为Y轴,取值范围:0~15
  * 
  * 	0		X轴		15
  * 	.————————————————>
  *   0 |
  * 	|
  * 	|
  * Y轴	|
  * 	|
  * 	|
  *  15 |
  * 	v
  */

/** 点阵屏显存数组
  * 
  * 想改变显示内容,先对此显存数组进行修改
  * 随后调用MatrixLED_Update函数
  * 才会将显存数组的数据发送到MAX7219芯片,进行显示
  */
unsigned char DisplayBuffer[32];

/**
  * 函    数:MAX7219写入字节
  * 参    数:Byte 要写入的字节
  * 返 回 值:无
  */
void MAX7219_WriteByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		MAX7219_CLK=0;	//拉低时钟总线
		MAX7219_DIN=Byte&(0x80>>i);	//高位先发
		MAX7219_CLK=1;	//时钟上升沿,发送数据
	}
}

/**
  * 函    数:MAX7219设置内部的控制寄存器
  * 参    数:Address 寄存器地址
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void MAX7219_Set(unsigned char Address,unsigned char Data)
{
	unsigned char i;
	MAX7219_CS=0;	//拉低片选线,选中器件
	for(i=0;i<4;i++)	//因有4个8X8点阵屏,需要写入4次相同的数据
	{
		MAX7219_WriteByte(Address);
		MAX7219_WriteByte(Data);
	}
	MAX7219_CS=1;	//上升沿锁存数据
}

/**
  * 函    数:更新屏幕显示
  * 参    数:无
  * 返 回 值:无
  */
void MatrixLED_Update(void)
{
	unsigned char i;
	for(i=0;i<8;i++)	//将显存数组DisplayBuffer中的数据写入MAX7219寄存器
    {
		MAX7219_CS=0;
		MAX7219_WriteByte(i+1);	//点阵的列的序号,范围:1~8
		MAX7219_WriteByte(DisplayBuffer[i*2+1]);	//显存数组数据写入顺序跟硬件连接有关,也跟点阵屏摆放方向有关
		MAX7219_WriteByte(i+1);	//点阵的列的序号,范围:1~8
		MAX7219_WriteByte(DisplayBuffer[i*2]);	//显存数组数据写入顺序跟硬件连接有关,也跟点阵屏摆放方向有关
		MAX7219_WriteByte(i+1);	//点阵的列的序号,范围:1~8
		MAX7219_WriteByte(DisplayBuffer[i*2+17]);	//显存数组数据写入顺序跟硬件连接有关,也跟点阵屏摆放方向有关
		MAX7219_WriteByte(i+1);	//点阵的列的序号,范围:1~8
		MAX7219_WriteByte(DisplayBuffer[i*2+16]);	//显存数组数据写入顺序跟硬件连接有关,也跟点阵屏摆放方向有关
		MAX7219_CS=1;
   }
}

/**
  * 函    数:将点阵屏显存数组全部清零,并更新屏幕显示
  * 参    数:无
  * 返 回 值:无
  */
void MatrixLED_Clear(void)
{
	unsigned char i;
	for(i=0;i<32;i++)
	{
		DisplayBuffer[i]=0;
	}
	MatrixLED_Update();
}

/**
  * 函    数:点阵屏初始化
  * 参    数:无
  * 返 回 值:无
  */
void MatrixLED_Init(void)
{
	MAX7219_Set(0x09,0x00);	//可按位选用译码器,0x00表示每位都不用译码器,0xFF表示8位都为该模式;
							//译码方式:BCD码,例如二进制的0001 0011表示十进制的13
	MAX7219_Set(0x0A,0x01);	//亮度,范围:0x00~0x0F
	MAX7219_Set(0x0B,0x07);	//扫描界限,范围:0x00~0x07(1~8列)
	MAX7219_Set(0x0C,0x01);	//0:停机状态,1:正常工作状态
	MAX7219_Set(0x0F,0x00);	//0:按设定模式正常工作;1:测试状态,全部LED将按最大亮度显示
	MatrixLED_Clear();	//清屏
}

/**
  * 函    数:点阵屏按指定的偏移量显示指定数组的数据(用来控制屏幕左右滚动显示内容)
  * 参    数:Array 数组的地址(指针),数组名就是数组的首地址
  * 参    数:Offset 偏移量,向左偏移Offset个像素,范围:0~65535
  * 返 回 值:无
  * 说    明:Offset增加则屏幕向左滚动显示,Offset减小则屏幕向右滚动显示
  * 说    明:要求数组数据逐列式取模,高位在下
  * 说    明:HS:Horizontal Scroll,水平滚动
  */
void MatrixLED_HS(unsigned char *Array,unsigned int Offset)
{
    unsigned char i;
	Array+=Offset*2;
	for(i=0;i<32;i++)	//将数据写入显存数组
    {
		DisplayBuffer[i]=*(Array+i);
	}
	MatrixLED_Update();	//更新屏幕显示
}

/**
  * 函    数:点阵屏按指定的偏移量显示指定数组的数据(用来控制屏幕上下滚动显示内容)
  * 参    数:Array 数组的地址(指针),数组名就是数组的首地址
  * 参    数:Offset 偏移量,向上偏移Offset个像素,范围:0~65535
  * 返 回 值:无
  * 说    明:Offset增加则屏幕向上滚动显示,Offset减小则屏幕向下滚动显示
  * 说    明:要求数组数据逐列式取模,高位在下
  * 说    明:VS:Vertical Scroll,竖直滚动
  */
void MatrixLED_VS(unsigned char *Array,unsigned int Offset)
{
    unsigned char i,m,n;
	m=Offset/16;
	n=Offset%16;
	Array+=m*32;
	if(n<8)	//将偏移后的数据写入显存数组
	{
		for(i=0;i<16;i++)
		{
			DisplayBuffer[i*2] = (*(Array+i*2)>>n) | (*(Array+i*2+1)<<(8-n));
			DisplayBuffer[i*2+1] = (*(Array+i*2+1)>>n) | (*(Array+i*2+32)<<(8-n));
		}
	}
	else
	{
		n=n-8;
		for(i=0;i<16;i++)
		{
			DisplayBuffer[i*2] = (*(Array+i*2+1)>>n) | (*(Array+i*2+32)<<(8-n));
			DisplayBuffer[i*2+1] = (*(Array+i*2+32)>>n) | (*(Array+i*2+33)<<(8-n));
		}
	}
	MatrixLED_Update();	//更新屏幕显示
}

/**
  * 函    数:控制点阵屏指定位置LED按指定状态显示
  * 参    数:X 指定点的横坐标,范围:-128~127,屏幕区域:0~15
  * 参    数:Y 指定点的纵坐标,范围:-128~127,屏幕区域:0~15
  * 参    数:State 亮灭状态,0:灭,非0:亮
  * 返 回 值:无
  */
void MatrixLED_ControlPoint(char X,char Y,unsigned char State)
{
	if(State==0)
	{
		if(X>=0 && X<=15 && Y>=0 && Y<=15)	//超出屏幕的内容不显示
		{
			DisplayBuffer[2*X+Y/8] &= ~(0x01<<Y%8);	//将显存数组指定位置的一个Bit数据置0
		}
	}
	else
	{
		if(X>=0 && X<=15 && Y>=0 && Y<=15)	//超出屏幕的内容不显示
		{
			DisplayBuffer[2*X+Y/8] |= 0x01<<Y%8;	//将显存数组指定位置的一个Bit数据置1
		}
	}
}

/**
  * 函    数:点阵屏获取指定位置LED的状态
  * 参    数:X 指定点的横坐标,范围:-128~127,屏幕区域:0~15
  * 参    数:Y 指定点的纵坐标,范围:-128~127,屏幕区域:0~15
  * 返 回 值:LED点亮返回1,LED熄灭返回0,超出屏幕范围返回2
  */
unsigned char MatrixLED_GetPoint(char X,char Y)
{
	if(X>=0 && X<=15 && Y>=0 && Y<=15)
	{
		if( DisplayBuffer[2*X+Y/8] & (0x01<<Y%8) ) {return 1;}
		else {return 0;}
	}
	else{return 2;}
}

/**
  * 函    数:点阵屏按要求显示或不显示
  * 参    数:WhetherOrNot,是否显示,非0:显示,0:不显示
  * 返 回 值:无
  */
void MatrixLED_WhetherToDisplay(unsigned char WhetherOrNot)
{
	if(WhetherOrNot)
	{
		MAX7219_Set(0x0C,0x01);	//屏幕显示
	}
	else
	{
		MAX7219_Set(0x0C,0x00);	//屏幕不显示
	}
}

2、矩阵按键

h文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__

//各按键对应数组的索引
#define S1	0
#define S2	1
#define S3	2
#define S4	3
#define S5	4
#define S6	5
#define S7	6
#define S8	7
#define S9	8
#define S10	9
#define S11	10
#define S12	11
#define S13	12
#define S14	13
#define S15	14
#define S16	15

//标志位掩码
#define HOLD	0x01	//按住
#define DOWN	0x02	//按下
#define UP		0x04	//松开
#define SINGLE	0x08	//单击
#define DOUBLE	0x10	//双击
#define LONG	0x20	//长按
#define REPEAT	0x40	//重复

void Key_Clear(void);
unsigned char Key(unsigned char n,unsigned char Flag);
void Key_Tick(void);

#endif

c文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#include <REGX52.H>
#include "MatrixKey.h"

#define KEY_PRESSED		1	//按键已按下
#define KEY_UNPRESSED	0	//按键未按下

//数值单位:定时器中断函数中相邻两次调用函数Key_Tick的时间间隔
#define KEY_TIME_DOUBLE		0	//双击判定的等待时长
#define KEY_TIME_LONG		4	//长按判定的等待时长
#define KEY_TIME_REPEAT		10	//长按后,重复标志位再次置1的时长
//本案例中,以上数值的单位是:10ms
//按住按键,从将Down标志位置1开始计时,第一次经过140ms将重复标志位置1,之后每隔100ms将重复标志位置1

//引脚配置
#define Port P1
sbit Row1=P1^7;	//行1
sbit Row2=P1^6;	//行2
sbit Row3=P1^5;	//行3
sbit Row4=P1^4;	//行4
sbit Column1=P1^3;	//列1
sbit Column2=P1^2;	//列2
sbit Column3=P1^1;	//列3
sbit Column4=P1^0;	//列4

/** 按键标志数组
  * 
  * 一个字节对应8位,分别为:B7 B6 B5 B4 B3 B2 B1 B0
  * 【B0】1:一直按住,0:未按下
  * 【B1】1:按下瞬间,0:不是按下瞬间
  * 【B2】1:松开瞬间,0:不是松开瞬间
  * 【B3】1:单击,0:不是单击
  * 【B4】1:双击,0:不是双击
  * 【B5】1:长按,0:不是长按
  * 【B6】1:重复,0:未重复
  * 【B7】保留位
  * 其中单击、双击、长按互斥(即这三个对应的标志位最多只能有一个是1)
  * 除B0外,其他标志位在读取后置0
  * xdata:变量保存在片外RAM
  */
unsigned char xdata Key_Flag[16];

/**
  * 函    数:获取按键状态(检测按键是否按下)
  * 参    数:n 按键索引,范围:0~15
  * 返 回 值:按下返回1,未按下返回0
  */
unsigned char Key_GetState(unsigned char n)
{
	if(n==S1)
	{
		Row1=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S2)
	{
		Row1=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S3)
	{
		Row1=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S4)
	{
		Row1=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S5)
	{
		Row2=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S6)
	{
		Row2=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S7)
	{
		Row2=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S8)
	{
		Row2=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S9)
	{
		Row3=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S10)
	{
		Row3=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S11)
	{
		Row3=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S12)
	{
		Row3=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S13)
	{
		Row4=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S14)
	{
		Row4=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S15)
	{
		Row4=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S16)
	{
		Row4=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	Port=0xFF;
	return KEY_UNPRESSED;
}

/**
  * 函    数:清空所有按键的所有标志位
  * 参    数:无
  * 返 回 值:无
  * 说    明:防止切换模式的时候受上一模式所按按键的影响
  */
void Key_Clear(void)
{
	unsigned char i;
	for(i=0;i<16;i++)
	{
		Key_Flag[i]=0;
	}
}
/**
  * 函    数:获取按键标志位的值
  * 参    数:n 按键索引,范围:0~15
  * 参    数:Flag 标志位掩码,用来获取标志的某一位的值为1还是0
  * 返 回 值:1或者0
  */
unsigned char Key(unsigned char n, unsigned char Flag)
{
	if(Key_Flag[n] & Flag)
	{
		if(Flag != HOLD)
		{
			Key_Flag[n] &= ~Flag;
		}
		return 1;
	}
	return 0;
}

/**
  * 函    数:按键检测驱动函数,在定时器中断函数中使用
  * 参    数:无
  * 返 回 值:无
  * 说    明:最后的else的目的是防止S[i]的初值在0~4之外导致检测不了单击、双击、长按、重复
  */
void Key_Tick(void)
{
	static unsigned char xdata Count,i;
	static unsigned char xdata NowState[16];
	static unsigned char xdata LastState[16];
	static unsigned char xdata S[16];
	static unsigned char xdata KeyTime[16];

	for(i=0;i<16;i++)
	{
		if(KeyTime[i]>0)
		{
			KeyTime[i]--;
		}
	}

	Count++;
	if(Count>=2)	//本案例中每隔20ms检测一次按键
	{
		Count=0;

		for(i=0;i<16;i++)
		{
			LastState[i]=NowState[i];
			NowState[i]=Key_GetState(i);
			
			if(NowState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= HOLD;
			}
			else
			{
				Key_Flag[i] &= ~HOLD;
			}
			
			if(NowState[i] == KEY_PRESSED && LastState[i] == KEY_UNPRESSED)
			{
				Key_Flag[i] |= DOWN;
			}
			
			if(NowState[i] == KEY_UNPRESSED && LastState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= UP;
			}
			
			if(S[i] == 0)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					KeyTime[i]=KEY_TIME_LONG;
					S[i]=1;
				}
			}
			else if(S[i] == 1)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					KeyTime[i]=KEY_TIME_DOUBLE;
					S[i]=2;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= LONG;
					S[i]=4;
				}
			}
			else if(S[i]==2)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					Key_Flag[i] |= DOUBLE;
					S[i]=3;
				}
				else if(KeyTime[i] == 0)
				{
					Key_Flag[i] |= SINGLE;
					S[i]=0;
				}
			}
			else if(S[i] == 3)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
			}
			else if(S[i]==4)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= REPEAT;
					S[i]=4;
				}
			}
			else
			{
				S[i]=0;
			}
		}
	}
}

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;	//设置定时器模式为16位不自动重装模式
	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;		//设置定时器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;
		
	}
}
*/

四、主函数

main.c

/*by甘腾胜@20250615
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】16X16点阵屏(MAX7219驱动)、矩阵按键
【接线】
(1)16X16点阵屏:DIN接P20,CS接P21,CLK接P22
(2)矩阵按键:接P1口(板上已接好)
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】因点阵屏有256个LED,为防止电流过大,已经将点阵屏的亮度调到最低了
【操作说明】
(1)循环滚动显示游戏名称的界面按任意按键开始游戏
(2)按S10旋转方块(逆时针),按S13、S15控制方块左右移动,按S14方块加速下落,按S16方块瞬间下落到底
(3)游戏结束全屏闪烁界面按S1进入滚动显示汉字“得分”的界面
(5)滚动显示汉字“得分”的界面可按S1跳过
(6)循环滚动显示得分界面可按S2返回,重新开始游戏
*/

#include <REGX52.H>		//51单片机头文件
#include <STDLIB.H>		//随机函数
#include "MatrixLED.h"	//点阵屏
#include "MatrixKey.h"	//矩阵按键
#include "Timer0.h"		//定时器0

/** 图层0显存数组Layer0数据、图层1显存数组Layer1数据存储格式:
  * 
  * 图层0和图层1显存数组存储的是游戏区域中已固定的和正在下落的方块的位置信息
  * 显存数组数据为int型,共16位
  * 纵向16点,高位在下,从左到右
  * 每一个Bit对应一个像素点,即对应一个LED
  * 
  * B0	B0				B0	B0	
  * B1	B1	  			B1	B1	
  * B2	B2	  			B2	B2	
  * B3	B3				B3	B3	
  * B4	B4	  			B4	B4	
  * B5	B5	  			B5	B5	
  * B6	B6	  			B6	B6	
  * B7	B7	  			B7	B7	
  * B8	B8	------->	B8	B8	
  * B9	B9	  			B9	B9	
  * B10	B10  			B10	B10 
  * B11	B11				B11	B11 
  * B12	B12  			B12	B12 
  * B13	B13  			B13	B13 
  * B14	B14  			B14	B14 
  * B15	B15  			B15	B15 
  */

/** 游戏区域(10X16像素)坐标轴定义:
  * 
  * 左上角为原点(0,0)
  * 横向向右为X轴,取值范围:0~9
  * 纵向向下为Y轴,取值范围:0~15
  * 坐标轴上一个点对应一个LED
  * 
  * 	0		X轴		9
  * 	.————————————————>
  *   0 |
  * 	|
  * 	|
  * Y轴	|
  * 	|
  * 	|
  *  15 |
  * 	v
  */

unsigned char Mode;	//游戏模式,0:循环滚动显示游戏名称,1:游戏中,2:游戏结束全屏闪烁,3:滚动显示汉字“得分”,4:循环滚动显示分数
unsigned char LastMode;	//上一次的游戏模式,切换模式时用来清空所有按键的所有标志位
unsigned char Offset;	//偏移量,用来控制字母或数字向左滚动显示
bit OnceFlag;	//各模式中(切换为其他模式前)只执行一次的标志,类似于主函数主循环前的那部分,用于该模式的初始化,1:执行,0:不执行
bit FlashFlag;	//闪烁的标志,1:不显示,0:显示
bit ScrollFlag;	//字母或数字滚动显示时,切换显示的标志,1:切换,0:不切换
bit PauseFlag;	//游戏时暂停的标志,1:暂停,0:不暂停
unsigned char GameOverFlag;	//游戏结束的的标志(函数返回值类型为unsigned char,不能定义为bit),1:游戏结束,0:游戏未结束
unsigned char T0Count_0;	//定时器0全局计数变量
unsigned int Score;	//游戏得分,范围:0~65535
unsigned char ScoreLength;	//游戏得分的位数,范围:1~5
unsigned char xdata ScoreShow[]={	//游戏得分(用于循环滚动显示),取模要求:逐列式取模,高位在下,亮点为1,xdata:变量保存在片外RAM
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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应16个字节
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应16个字节
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应16个字节
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应16个字节
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 得分最多五位数,每一个数字对应16个字节
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,	// 无显示
};
unsigned char code Table1[]={	//“俄罗斯方块”的字模,宽16高16,取模要求:逐列式取模,高位在下,亮点为1,code:数据存储在Flash中
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,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x90,0x04,
0x48,0x09,0x24,0x12,0x12,0x24,0x09,0x48,0x04,0x10,0x02,0x20,0x00,0x00,0x00,0x00,/*"《",0*/
0x80,0x00,0x60,0x00,0xF8,0xFF,0x07,0x00,0x20,0x04,0x24,0x44,0x24,0x82,0xFE,0x7F,
0x22,0x81,0x20,0x40,0xFF,0x27,0x20,0x18,0x21,0x26,0xA6,0x41,0x20,0xF0,0x00,0x00,/*"俄",1*/
0x00,0x80,0x00,0x84,0x3E,0x84,0x22,0x42,0x22,0x45,0xBE,0x49,0x62,0x31,0x22,0x21,
0x22,0x11,0x3E,0x09,0x22,0x05,0x22,0x03,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"罗",2*/
0x00,0x88,0x04,0x48,0xFF,0x2F,0x24,0x09,0x24,0x09,0x24,0x19,0xFF,0x2F,0x04,0xC8,
0x00,0x60,0xFC,0x1F,0x44,0x00,0x44,0x00,0xC4,0xFF,0x42,0x00,0x40,0x00,0x00,0x00,/*"斯",3*/
0x08,0x00,0x08,0x80,0x08,0x40,0x08,0x20,0x08,0x18,0xF8,0x07,0x89,0x00,0x8E,0x00,
0x88,0x40,0x88,0x80,0x88,0x40,0x88,0x3F,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00,/*"方",4*/
0x20,0x10,0x20,0x30,0x20,0x10,0xFF,0x0F,0x20,0x88,0x20,0x48,0x08,0x21,0x08,0x11,
0x08,0x0D,0xFF,0x03,0x08,0x0D,0x08,0x11,0x08,0x21,0xF8,0x41,0x00,0x81,0x00,0x00,/*"块",5*/
0x00,0x00,0x00,0x00,0x02,0x20,0x04,0x10,0x09,0x48,0x12,0x24,0x24,0x12,0x48,0x09,
0x90,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"》",6*/
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,	// 无显示
};
unsigned char code Table2[]={	//“得分”的字模,宽16高16,取模要求:逐列式取模,高位在下,亮点为1
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,0x02,0x10,0x01,0x88,0x00,0xC4,0xFF,0x33,0x00,0x00,0x02,0xBE,0x0A,0xAA,0x12,
0xAA,0x02,0xAA,0x42,0xAA,0x82,0xAA,0x7F,0xBE,0x02,0x80,0x02,0x00,0x02,0x00,0x00,/*"得",0*/
0x80,0x00,0x40,0x80,0x20,0x40,0x90,0x20,0x88,0x18,0x86,0x07,0x80,0x00,0x80,0x40,
0x80,0x80,0x83,0x40,0x8C,0x3F,0x10,0x00,0x20,0x00,0x40,0x00,0x80,0x00,0x00,0x00,/*"分",1*/
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,	// 无显示
};
unsigned char code Table3[]={	//游戏得分的数字的字模数据,宽8高16,取模要求:逐列式取模,高位在下,亮点为1
0x00,0x00,0xE0,0x0F,0x10,0x10,0x08,0x20,0x08,0x20,0x10,0x10,0xE0,0x0F,0x00,0x00,/*"0",0*/
0x00,0x00,0x00,0x00,0x10,0x20,0x10,0x20,0xF8,0x3F,0x00,0x20,0x00,0x20,0x00,0x00,/*"1",1*/
0x00,0x00,0x70,0x30,0x08,0x28,0x08,0x24,0x08,0x22,0x08,0x21,0xF0,0x30,0x00,0x00,/*"2",2*/
0x00,0x00,0x30,0x18,0x08,0x20,0x08,0x21,0x08,0x21,0x88,0x22,0x70,0x1C,0x00,0x00,/*"3",3*/
0x00,0x00,0x00,0x06,0x80,0x05,0x40,0x24,0x30,0x24,0xF8,0x3F,0x00,0x24,0x00,0x24,/*"4",4*/
0x00,0x00,0xF8,0x19,0x88,0x20,0x88,0x20,0x88,0x20,0x08,0x11,0x08,0x0E,0x00,0x00,/*"5",5*/
0x00,0x00,0xE0,0x0F,0x10,0x11,0x88,0x20,0x88,0x20,0x90,0x20,0x00,0x1F,0x00,0x00,/*"6",6*/
0x00,0x00,0x18,0x00,0x08,0x00,0x08,0x3E,0x88,0x01,0x68,0x00,0x18,0x00,0x00,0x00,/*"7",7*/
0x00,0x00,0x70,0x1C,0x88,0x22,0x08,0x21,0x08,0x21,0x88,0x22,0x70,0x1C,0x00,0x00,/*"8",8*/
0x00,0x00,0xF0,0x01,0x08,0x12,0x08,0x22,0x08,0x22,0x10,0x11,0xE0,0x0F,0x00,0x00,/*"9",9*/
};

bit FallFlag;	//方块下落的标志,1:下落一个像素,0:暂不下落
unsigned char ReferencePointX;	//参考点的X坐标(以该参考点为原点,新建一个坐标系)
unsigned char ReferencePointY;	//参考点的Y坐标(以该参考点为原点,新建一个坐标系)
unsigned int idata Layer0[10];	//图层0的显示缓存,用来存储已经固定的方块的位置,idata:变量保存在片内间接寻址区
unsigned int idata Layer1[10];	//图层1的显示缓存,用来存储正在下落的方块的位置,idata:变量保存在片内间接寻址区
unsigned int idata GameBuffer[10];	//游戏区显示缓存,用来存储图层0和图层1合并后的数据
char idata BlockX[4];	//方块的X坐标,左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
char idata BlockY[4];	//方块的Y坐标,左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
char idata AfterRotateX[4];	//方块旋转后的X坐标,用来判断方块能否旋转
char idata AfterRotateY[4];	//方块旋转后的Y坐标,用来判断方块能否旋转
char idata NextBlockX[4];	//下一个方块的X坐标,用来在屏幕显示下一个方块
char idata NextBlockY[4];	//下一个方块的Y坐标,用来在屏幕显示下一个方块
unsigned char BlockSelect;	//方块选择变量,范围:0~27,根据数组AllBlocks来确定是哪个方块
unsigned char NextBlock;	//下一个方块,范围:0~27,根据数组AllBlocks来确定是哪个方块
unsigned int Line;	//所消的总行数
unsigned char LinePlus;	//一次所消的行数
unsigned char NowSpeed;	//现在的速度
unsigned char LastSpeed;	//消行前的速度,跟NowSpeed对比,用来判断消行后速度有无变化
unsigned int Duration;	//方块下落的时间间隔
char code AllBlocks[28][4][2]={

//存储了7种方块的28种方向的具体信息(相对于参考点的坐标),程序中根据此数据计算方块在屏幕的位置
//以参考点为原点,建立一个新的坐标系,同样是向右为X轴正方向,向下为Y轴的正方向
//下面数据的每一行(8个字节)对应一个方块相对于参考点的坐标,分别为方块的四小块的X、Y坐标
//以第一个方块(“田”)的数据(前8个数据)为例
//0,0,对应的是“田”的左下角
//1,0,对应的是“田”的右下角
//0,-1,对应的是“田”的左上角
//1,-1,对应的是“田”的右下角
//相对于参考点的X坐标都是大于等于零,相对于参考点的Y坐标都是小于等于零

//	口口
//	口口
0,0, 1,0, 0,-1, 1,-1,	//0

//	口口口口
0,0, 1,0, 2,0, 3,0,		//1

//	  口
//	口口口
0,0, 1,0, 2,0, 1,-1,	//2

//	口
//	口口
//	  口
1,0, 0,-1, 1,-1, 0,-2,	//3

//	  口
//	口口
//	口
0,0, 0,-1, 1,-1, 1,-2,	//4

//	    口
//	口口口
0,0, 1,0, 2,0, 2,-1,	//5

//	口
//	口口口
0,0, 1,0, 2,0, 0,-1,	//6

//	口口
//	口口
0,0, 1,0, 0,-1, 1,-1,	//7

//	口
//	口
//	口
//	口
0,0, 0,-1, 0,-2, 0,-3,	//8

//	  口
//	口口
//	  口
1,0, 0,-1, 1,-1, 1,-2,	//9

//	  口口
//	口口
0,0, 1,0, 1,-1, 2,-1,	//10

//	口口
//	  口口
1,0, 2,0, 0,-1, 1,-1,	//11

//	口口
//	  口
//	  口
1,0, 1,-1, 0,-2, 1,-2,	//12

//	  口
//	  口
//	口口
0,0, 1,0, 1,-1, 1,-2,	//13

//	口口
//	口口
0,0, 1,0, 0,-1, 1,-1,	//14

//	口口口口
0,0, 1,0, 2,0, 3,0,		//15

//	口口口
//	  口
1,0, 0,-1, 1,-1, 2,-1,	//16

//	口
//	口口
//	  口
1,0, 0,-1, 1,-1, 0,-2,	//17

//	  口
//	口口
//	口
0,0, 0,-1, 1,-1, 1,-2,	//18

//	口口口
//  口
0,0, 0,-1, 1,-1, 2,-1,	//19

//	口口口
//	    口
2,0, 0,-1, 1,-1, 2,-1,	//20

//	口口
//	口口
0,0, 1,0, 0,-1, 1,-1,	//21

//	口
//	口
//	口
//	口
0,0, 0,-1, 0,-2, 0,-3,	//22

//	口
//	口口
//	口
0,0, 0,-1, 1,-1, 0,-2,	//23

//	  口口
//	口口
0,0, 1,0, 1,-1, 2,-1,	//24

//	口口
//	  口口
1,0, 2,0, 0,-1, 1,-1,	//25

//	口
//	口
//	口口
0,0, 1,0, 0,-1, 0,-2,	//26

//	口口
//	口
//	口
0,0, 0,-1, 0,-2, 1,-2,	//27

};

/**
  * 函    数:游戏区域显存数组获取指定位置的值
  * 参    数:X 指定点的横坐标,范围:-128~127,游戏区域:0~9
  * 参    数:Y 指定点的纵坐标,范围:-128~127,游戏区域:0~15
  * 返 回 值:游戏区域内返回0或1,超出游戏区域返回2
  */
unsigned char GameBuffer_GetPoint(char X,char Y)
{
	if(X>=0 && X<=9 && Y>=0 && Y<=15)
	{
		if( GameBuffer[X] & (0x01<<Y) ) {return 1;}
		else {return 0;}
	}
	else{return 2;}
}

/**
  * 函    数:图层0和图层1的数据叠加后用数组GameBuffer存储,并更新游戏区的显示到屏幕
  * 参    数:无
  * 返 回 值:无
  */
void UpdateGameArea(void)
{
	unsigned char i,j;
	for(i=0;i<10;i++)
	{
		GameBuffer[i] = Layer0[i] | Layer1[i];
	}
	for(i=0;i<10;i++)
	{
		for(j=0;j<16;j++)
		{
			if(GameBuffer_GetPoint(i,j))
			{
				MatrixLED_ControlPoint(i,j,1);
			}
			else
			{
				MatrixLED_ControlPoint(i,j,0);
			}
		}
	}
	MatrixLED_Update();	//更新屏幕显示
}

/**
  * 函    数:图层0显存数组指定位置按指定的数值赋值
  * 参    数:X 指定点的横坐标,范围:-128~127,游戏区域:0~9
  * 参    数:Y 指定点的纵坐标,范围:-128~127,游戏区域:0~15
  * 参    数:Value 数值,0:指定Bit置0,非0:指定Bit置1
  * 返 回 值:无
  */
void Layer0_ControlPoint(char X,char Y,unsigned char Value)
{
	if(Value==0)
	{
		if(X>=0 && X<=9 && Y>=0 && Y<=15){ Layer0[X] &= ~(0x01<<Y); }
	}
	else
	{
		if(X>=0 && X<=9 && Y>=0 && Y<=15){ Layer0[X] |= 0x01<<Y; }
	}
}

/**
  * 函    数:图层1显存数组指定位置按指定的数值赋值
  * 参    数:X 指定点的横坐标,范围:-128~127,游戏区域:0~9
  * 参    数:Y 指定点的纵坐标,范围:-128~127,游戏区域:0~15
  * 参    数:Value 数值,0:指定Bit置0,非0:指定Bit置1
  * 返 回 值:无
  */
void Layer1_ControlPoint(char X,char Y,unsigned char Value)
{
	if(Value==0)
	{
		if(X>=0 && X<=9 && Y>=0 && Y<=15){ Layer1[X] &= ~(0x01<<Y); }
	}
	else
	{
		if(X>=0 && X<=9 && Y>=0 && Y<=15){ Layer1[X] |= 0x01<<Y; }
	}
}

/**
  * 函    数:图层0显存数组获取指定位置点的值
  * 参    数:X 指定点的横坐标,范围:-128~127,游戏区域:0~9
  * 参    数:Y 指定点的纵坐标,范围:-128~127,游戏区域:0~15
  * 返 回 值:游戏区域内返回0或1,超出游戏区域返回2(特殊:游戏区域正上方会返回0)
  */
unsigned char Layer0_GetPoint(char X,char Y)
{
	if(X>=0 && X<=9)	//如果是第1列到第10列
	{
		if(Y>=0 && Y<=15)	//如果是第1行到第16行
		{
			if( Layer0[X] & (0x01<<Y) ) {return 1;}	//游戏区域内,指定的位为1,则返回1
			else {return 0;}	//游戏区域内,指定的位为0,则返回0
		}
		else if(Y<0)	//如果是游戏区域上方,则返回0,目的是让方块未完全进入游戏区域时不会被固定
		{
			return 0;
		}
		else	//如果是游戏区域下方,则返回2,用来检测下落方块是否来到底部
		{
			return 2;
		}
	}
	else{return 2;}	//如果不是第1列到第10列
}

/**
  * 函    数:将图层0显存数组全部清零
  * 参    数:无
  * 返 回 值:无
  * 说    明:图层0显存数组存储已经固定的方块的位置
  */
void Layer0_Clear(void)
{
	unsigned char i;
	for(i=0;i<10;i++)
	{
		Layer0[i]=0;
	}
}

/**
  * 函    数:将图层1显存数组全部清零
  * 参    数:无
  * 返 回 值:无
  * 说    明:图层1显存数组存储正在下落的方块的位置
  */
void Layer1_Clear(void)
{
	unsigned char i;
	for(i=0;i<10;i++)
	{
		Layer1[i]=0;
	}
}

/**
  * 函    数:图层1显存数组更新方块的位置信息
  * 参    数:无
  * 返 回 值:无
  * 说    明:一个方块由四个LED组成
  */
void Layer1_UpdateBlock(void)
{
	unsigned char i;
	Layer1_Clear();	//图层1清空数据
	for(i=0;i<4;i++)	//更新方块坐标
	{
		BlockX[i]=ReferencePointX+AllBlocks[BlockSelect][i][0];	//X坐标,根据参考点坐标和相对参考点的坐标来计算
		BlockY[i]=ReferencePointY+AllBlocks[BlockSelect][i][1];	//Y坐标,根据参考点坐标和相对参考点的坐标来计算
	}
	for(i=0;i<4;i++)	//根据方块坐标,将方块的位置信息更新到图层1显存数组
	{
		Layer1_ControlPoint(BlockX[i],BlockY[i],1);
	}
}

/**
  * 函    数:检查是否要固定方块
  * 参    数:无
  * 返 回 值:要固定方块返回1,不固定方块返回0
  */
unsigned char IsFix(void)
{
	unsigned char i;
	for(i=0;i<4;i++)
	{
		if( Layer0_GetPoint(BlockX[i],BlockY[i]+1) )
		{	//检查方块的下方是否是区域底部或其他已经固定了的方块
			return 1;
		}
	}
	return 0;
}

/**
  * 函    数:固定方块
  * 参    数:无
  * 返 回 值:无
  * 说    明:方块下落时,如果下方是区域底部或其他已经固定了的方块,则该正在下落的方块会被固定
  * 说    明:用图层0显存数组保存固定后的方块的位置
  */
void FixBlock(void)
{
	unsigned char i;
	for(i=0;i<10;i++)
	{
		Layer0[i] = Layer0[i] | Layer1[i];
	}
}

/**
  * 函    数:检查方块能否左移
  * 参    数:无
  * 返 回 值:能左移返回1,不能左移返回0
  */
unsigned char CanMoveLeft(void)
{
	unsigned char i;
	for(i=0;i<4;i++)
	{
		if( Layer0_GetPoint(BlockX[i]-1,BlockY[i]) )
		{
			return 0;
		}
	}
	return 1;
}

/**
  * 函    数:检查方块能否右移
  * 参    数:无
  * 返 回 值:能右移返回1,不能右移返回0
  */
unsigned char CanMoveRight(void)
{
	unsigned char i;
	for(i=0;i<4;i++)
	{
		if( Layer0_GetPoint(BlockX[i]+1,BlockY[i]) )
		{
			return 0;
		}
	}
	return 1;
}

/**
  * 函    数:检查能否旋转
  * 参    数:X 参考点的X坐标
  * 参    数:Y 参考点的Y坐标
  * 返 回 值:能旋转返回1,不能旋转返回0
  * 说    明:左上角的LED为原点(0,0),向右为X轴正方向,向下为Y轴正方向
  */
unsigned char CanRotate(char X,char Y)
{
	unsigned char i;
	for(i=0;i<4;i++)	//根据提供的参考坐标,获取旋转后的方块的坐标
	{
		AfterRotateX[i]=X+AllBlocks[(BlockSelect+7)%28][i][0];
		AfterRotateY[i]=Y+AllBlocks[(BlockSelect+7)%28][i][1];
	}
	for(i=0;i<4;i++)
	{	//检测旋转后的方块有没有超过屏幕区域,以及有没有跟固定的方块有重叠
		if( Layer0_GetPoint(AfterRotateX[i],AfterRotateY[i]) )
		{	//如果有,则返回0,即不能旋转
			return 0;
		}
	}
	return 1;	//如果没有,则返回1,即能旋转
}

/**
  * 函    数:旋转方块
  * 参    数:无
  * 返 回 值:无
  */
void RotateBlock(void)
{
	BlockSelect+=7;
	BlockSelect%=28;
	Layer1_UpdateBlock();
	UpdateGameArea();
}

/**
  * 函    数:检查和消除
  * 参    数:无
  * 返 回 值:所消的行数,范围:0~4
  * 说    明:方块固定后从下遍历每一行,行满了后则消除该行
  */
unsigned char CheckAndEliminate(void)
{
	unsigned char i,j,k;
	unsigned char Count,Temp=0;
	for(i=0;i<16;i++)	//固定方块后,从下往上,遍历每一行
	{
		Count=0;
		for(j=0;j<10;j++)
		{
			if(Layer0_GetPoint(j,15-i))
			{
				Count++;
			}
		}
		if(Count==10)
		{
			Temp++;
			for(k=i;k<15;k++)	//该行以上的所有像素下移一格
			{
				for(j=0;j<10;j++)
				{
					Layer0_ControlPoint(j,15-k,Layer0_GetPoint(j,14-k));
				}
			}
			for(j=0;j<10;j++)	//如果有消行,则下移后清空第一行
			{
				Layer0_ControlPoint(j,0,0);
			}
			i--;	//下移后重新检查该行
		}
	}
	return Temp;
}

/**
  * 函    数:判断游戏是否结束
  * 参    数:无
  * 返 回 值:游戏结束返回1,游戏未结束返回0
  * 说    明:如果下一个方块无法下落一点,则游戏结束
  */
unsigned char IsGameOver(void)
{
	unsigned char i;
	for(i=0;i<4;i++)
	{
		BlockX[i]=4+AllBlocks[NextBlock][i][0];
		BlockY[i]=0+AllBlocks[NextBlock][i][1];
	}
	for(i=0;i<4;i++)
	{
		if(BlockY[i]==0)
		{
			if(Layer0_GetPoint(BlockX[i],0))
			{
				return 1;
			}
		}
	}
	return 0;
}

/**
  * 函    数:幂函数/指数函数
  * 参    数:X 底
  * 参    数:Y 幂
  * 返 回 值:X的Y次方
  * 说    明:辅助取出Score中的某一位上的数字
  */
unsigned int Pow(unsigned char X,unsigned char Y)
{
	unsigned char i;
	unsigned int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * 函    数:显示一条竖线
  * 参    数:无
  * 返 回 值:无
  * 说    明:为了方便看清游戏的区域的边界
  */
void ShowVerticalLine(void)
{
	unsigned char i;
	for(i=0;i<16;i++)
	{
		MatrixLED_ControlPoint(10,i,1);
	}
	MatrixLED_Update();
}

/**
  * 函    数:显示下一个方块
  * 参    数:Next 下一个方块在数组AllBlocks中的索引号
  * 返 回 值:无
  */
void ShowNextBlock(unsigned char Next)
{
	unsigned char i,j;
	for(i=0;i<4;i++)	//清空右上角4X4区域的显示
	{
		for(j=0;j<4;j++)
		{
			MatrixLED_ControlPoint(12+i,j,0);
		}
	}
	for(i=0;i<4;i++)	//更新方块坐标
	{	//参考点坐标固定为(12,3)
		NextBlockX[i]=12+AllBlocks[Next][i][0];	//X坐标,根据参考点坐标和相对参考点的坐标来计算
		NextBlockY[i]=3+AllBlocks[Next][i][1];	//Y坐标,根据参考点坐标和相对参考点的坐标来计算
	}
	for(i=0;i<4;i++)	//根据方块坐标,将下一个方块的位置信息写入屏幕显存数组
	{
		MatrixLED_ControlPoint(NextBlockX[i],NextBlockY[i],1);
	}
	MatrixLED_Update();	//更新到屏幕
}

/**
  * 函    数:方块固定后更新显示游戏信息
  * 参    数:无
  * 返 回 值:无
  * 说    明:更新显示分数、消的行数、方块下落速度,用二进制表示
  */
void UpdateInformation(void)
{
	unsigned char i;
	LinePlus=CheckAndEliminate();	//方块固定后,从下往上遍历每一行,行满则消除,并返回所消的行数
	if(LinePlus)	//如果有消行
	{
		Line=Line+LinePlus;	//更新存储总行数的变量
		Score=Score+LinePlus*LinePlus;	//更新得分,一次所消的行数越多,得分越多
		NowSpeed=Line/16+1;	//每消16行,速度增加1次
		if(NowSpeed>10)	//最多只能加速9次,即NowSpeed最大值为10
		{
			NowSpeed=10;
		}
		if(LastSpeed != NowSpeed)
		{
			Duration=Duration*9/10;	//方块下落的时间间隔减为原来的90%
			if(Duration<400)	//限制最小值为400,即最短400ms下落一个像素
			{
				Duration=400;
			}
			LastSpeed=NowSpeed;
		}
	}
	for(i=0;i<8;i++)	//显示得分,通过屏幕右下角的16个像素用二进制表示
	{
		if( Score & (0x01<<(7-i)) )
		{
			MatrixLED_ControlPoint(15,8+i,1);
		}
		else
		{
			MatrixLED_ControlPoint(15,8+i,0);
		}
		if( Score & (0x01<<(15-i)) )
		{
			MatrixLED_ControlPoint(14,8+i,1);
		}
		else
		{
			MatrixLED_ControlPoint(14,8+i,0);
		}
	}
	for(i=0;i<8;i++)	//显示消的总行数,通过屏幕右下角的16个像素用二进制表示
	{
		if( Line & (0x01<<(7-i)) )
		{
			MatrixLED_ControlPoint(13,8+i,1);
		}
		else
		{
			MatrixLED_ControlPoint(13,8+i,0);
		}
		if( Line & (0x01<<(15-i)) )
		{
			MatrixLED_ControlPoint(12,8+i,1);
		}
		else
		{
			MatrixLED_ControlPoint(12,8+i,0);
		}
	}
	for(i=0;i<4;i++)	//显示速度,通过屏幕右边的4个像素用二进制表示
	{
		if( NowSpeed & (0x01<<i) )
		{
			MatrixLED_ControlPoint(15-i,6,1);
		}
		else
		{
			MatrixLED_ControlPoint(15-i,6,0);
		}
	}
	MatrixLED_Update();	//更新到屏幕显示
}

/**
  * 函    数:检测到需要固定方块后的处理
  * 参    数:无
  * 返 回 值:无
  * 说    明:按S14、按S16、自由下落,这三种情况方块固定后,都要这么处理
  */
void AfterFixBlock(void)
{
	FixBlock();	//则固定方块
	UpdateInformation();	//更新显示屏幕的游戏信息
	ReferencePointX=4;	//方块固定后,参考点坐标重置
	ReferencePointY=-1;	//方块固定后,参考点坐标重置
	GameOverFlag=IsGameOver();	//判断游戏是否结束
	if(GameOverFlag==0)
	{
		BlockSelect=NextBlock;	//固定方块后,更新要下落的方块
		NextBlock=rand()%28;	//确定下一个方块
		ShowNextBlock(NextBlock);	//显示下一个方块
	}
	T0Count_0=0;	//定时器0全局计数变量清零
	FallFlag=0;	//方块下落的标志置0
}

/**
  * 函    数:主函数(有且仅有一个)
  * 参    数:无
  * 返 回 值:无
  * 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌
  */
void main()
{
	unsigned char i,j;	//For循环用到的临时变量

	P2_5=0;	//防止开发板上的蜂鸣器发出声音
	MatrixLED_Init();	//点阵屏初始化
	Timer0_Init();  //定时器0初始化

	OnceFlag=1;
	Key_Clear();	//进主循环前清空所有按键的所有标志位

	while(1)
	{

		/*按键处理*/
		if(Mode != LastMode)	//如果模式发生改变
		{
			Key_Clear();	//每次切换模式都要清空所有按键的所有标志位,防止本模式置1的标志位影响下一模式
			LastMode=Mode;
		}
		if(Mode==0)	//如果是循环滚动显示游戏名称的界面
		{
			for(i=0;i<16;i++)
			{
				if(Key(i,DOWN))	//如果有任意按键按下
				{
					srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
					Mode=1;	//切换到模式1
					OnceFlag=1;	//切换模式前只执行一次的标志置1
					break;	//退出循环
				}
			}
		}
		else if(Mode==1)	//如果是正在游戏的界面
		{
			if(Key(S1,DOWN))	//如果按下S1
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				PauseFlag=!PauseFlag;	//置反暂停的标志
				if(PauseFlag==0)	//从暂停变成继续的时候,显示方块
				{
						Layer1_UpdateBlock();	//图层1中写入正在下落的方块的位置的信息
						UpdateGameArea();	//更新屏幕显示
				}
			}
			if( (Key(S13,DOWN) || Key(S13,REPEAT)) && PauseFlag==0)	//如果S13按下或重复,且不是暂停状态
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				if( CanMoveLeft() )	//如果方块能左移
				{
					ReferencePointX--;	//参考点的X坐标减1
					Layer1_UpdateBlock();	//更新正在下落的方块的位置
					UpdateGameArea();	//更新屏幕显示
				}
			}
			if( (Key(S15,DOWN) || Key(S15,REPEAT)) && PauseFlag==0)	//如果S15按下或重复,且不是暂停状态
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				if( CanMoveRight() )	//如果方块能右移
				{
					ReferencePointX++;	//参考点的X坐标加1
					Layer1_UpdateBlock();	//更新正在下落的方块的位置
					UpdateGameArea();	//更新屏幕显示
				}
			}
			if(Key(S10,DOWN) && PauseFlag==0)	//如果按下S10,且不是暂停状态
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				if( CanRotate(ReferencePointX,ReferencePointY) )
				{	//如果旋转后,没有超出屏幕区域,也没有跟已固定的方块重叠
					RotateBlock();	//旋转并更新屏幕显示
				}
				else if( CanRotate(ReferencePointX-1,ReferencePointY) )
				{	//如果旋转后左移一格,没有超出屏幕区域,也没有跟已固定的方块重叠
					ReferencePointX--;	//更新参考点的X坐标
					RotateBlock();	//旋转并更新屏幕显示
				}
				else if( CanRotate(ReferencePointX-2,ReferencePointY) )
				{	//如果旋转后左移两格,没有超出屏幕区域,也没有跟已固定的方块重叠
					ReferencePointX-=2;	//更新参考点的X坐标
					RotateBlock();	//旋转并更新屏幕显示
				}
				else if( CanRotate(ReferencePointX-3,ReferencePointY) )
				{	//如果旋转后左移三格,没有超出屏幕区域,也没有跟已固定的方块重叠
					ReferencePointX-=3;	//更新参考点的X坐标
					RotateBlock();	//旋转并更新屏幕显示
				}
				//如果以上四种情况都不满足,则不旋转方块
			}
			if((Key(S14,DOWN) || Key(S14,REPEAT)) && PauseFlag==0)	//如果S14按下或重复,且不是暂停状态,则方块加速下落
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				if( IsFix() )	//如果是固定方块的情况
				{
					AfterFixBlock();	//方块固定后的处理
				}
				if(GameOverFlag==0)	//如果游戏还没结束
				{
					ReferencePointY++;	//方块下落一格
					Layer1_UpdateBlock();	//更新正在下落的方块的位置
					UpdateGameArea();	//更新屏幕显示
					T0Count_0=0;	//定时器0全局计数变量清零
					FallFlag=0;	//方块下落的标志置0
				}
			}
			if(Key(S16,DOWN) && PauseFlag==0)	//如果是按下S16,且不是暂停状态
			{
				srand(TL0);	//每次按键按下都用定时器0的低8位做种子,从而产生真随机数
				while( IsFix()==0 )	//瞬间到达底部(不显示下落的过程)
				{
					ReferencePointY++;
					Layer1_UpdateBlock();	//更新正在下落的方块的位置
				}
				AfterFixBlock();	//方块固定后的处理
				UpdateGameArea();	//更新屏幕显示
				if(GameOverFlag==0)
				{
					ReferencePointX=4;
					ReferencePointY=0;
					Layer1_UpdateBlock();	//更新正在下落的方块的位置
					UpdateGameArea();	//更新屏幕显示
				}
			}
		}
		else if(Mode==2)	//如果是游戏结束全屏闪烁的界面
		{
			if(Key(S1,DOWN))	//如果按下S1
			{
				Mode=3;
				OnceFlag=1;
			}
		}
		else if(Mode==3)	//如果是滚动显示汉字“得分”的界面
		{
			if(Key(S1,DOWN))	//如果按下S1
			{
				Mode=4;
				OnceFlag=1;
			}
		}
		else if(Mode==4)	//如果是循环滚动显示得分的界面
		{
			if(Key(S2,DOWN))	//如果按下S2
			{
				Mode=1;	//重新开始游戏
				OnceFlag=1;
			}
		}

		/*游戏处理*/
		if(Mode==0)	//循环滚动显示游戏名称
		{
			if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次
			{
				OnceFlag=0;	//只执行一次的标志置0
				Offset=0;	//滚动显示的偏移量清零
			}
			if(ScrollFlag)	//如果滚动显示的标志ScrollFlag为真(非零即真)
			{
				ScrollFlag=0;	//滚动显示的标志ScrollFlag置0
				MatrixLED_HS(Table1,Offset);	//滚动显示,向左
				Offset++;	//每次向左移动一个像素
				Offset%=128;	//越界清零,循环滚动显示
			}
		}
		else if(Mode==1)	//游戏进行中
		{
			if(OnceFlag)
			{	//游戏初始化
				OnceFlag=0;
				MatrixLED_Clear();	//清屏
				Score=0;	//得分清零
				for(i=0;i<144;i++){ScoreShow[i]=0;}	//清空分数显示的缓存数组的数据
				PauseFlag=0;	//暂停的标志置0
				GameOverFlag=0;	//游戏结束的标志置0
				Line=0;	//所消的总行数清零
				LinePlus=0;	//上一次所消的行数清零
				NowSpeed=1;	//初始速度值为1
				LastSpeed=1;	//初始速度值为1
				Duration=1000;	//初始每隔1000ms下落一次
				BlockSelect=rand()%28;	//开始游戏时随机确定一个方块
				NextBlock=rand()%28;	//开始游戏时随机确定下一个方块
				ShowNextBlock(NextBlock);	//显示下一个方块
				ReferencePointX=4;	//初始化参考点的X坐标
				ReferencePointY=0;	//初始化参考点的Y坐标
				Layer0_Clear();	//图层0清空数据
				Layer1_Clear();	//图层1清空数据
				Layer1_UpdateBlock();	//更新正在下落的方块的位置
				UpdateGameArea();	//更新屏幕显示
				ShowVerticalLine();	//显示一条竖线
				UpdateInformation();	//显示游戏信息:得分、消的行数、速度值
				T0Count_0=0;	//定时器0全局计数变量清零
				FallFlag=0;	//方块下落的标志置0
			}
			if(GameOverFlag==0)	//如果游戏未结束
			{
				if(PauseFlag==0)	//如果不是暂停
				{
					if(FallFlag)	//如果方块下落的标志为真
					{
						FallFlag=0;	//方块下落的标志置0
						if( IsFix() )
						{
							AfterFixBlock();	//方块固定后的处理
						}
						if(GameOverFlag==0)
						{
							ReferencePointY++;
							Layer1_UpdateBlock();	//更新正在下落的方块的位置
							UpdateGameArea();	//更新屏幕显示
						}
					}
				}
				else	//如果是暂停状态
				{
					if(FlashFlag)
					{
						Layer1_Clear();	//清除图层1正在下落的方块的位置信息
						UpdateGameArea();	//更新屏幕游戏区域的显示
					}
					else
					{
						Layer1_UpdateBlock();	//重新向图层1写入正在下落的方块的位置信息
						UpdateGameArea();	//更新屏幕游戏区域的显示
					}
				}
			}
			else	//如果游戏结束
			{
				Mode=2;	//切换到模式2
			}
		}
		else if(Mode==2)	//游戏结束全屏闪烁
		{
			if(FlashFlag)
			{
				MatrixLED_WhetherToDisplay(0);
			}
			else
			{
				MatrixLED_WhetherToDisplay(1);
			}
		}
		else if(Mode==3)	//滚动显示汉字“得分”
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				MatrixLED_WhetherToDisplay(1);	//防止闪烁的时候正好不显示时进入此模式,导致后面不能正常显示
				Offset=0;
			}
			if(ScrollFlag && Offset<=48)	//只滚动显示一次
			{
				ScrollFlag=0;
				MatrixLED_HS(Table2,Offset);	//滚动显示,向左
				Offset++;
			}
			else if(Offset>48) //滚动结束后,自动切换到循环滚动显示得分的模式
			{
				Mode=4;
				OnceFlag=1;
			}	
		}
		else if(Mode==4)	//循环滚动显示得分
		{
			if(OnceFlag)
			{
				OnceFlag=0;
				Offset=0;
				if(Score>=10000){ScoreLength=5;}//判断得分是多少位数
				else if(Score>=1000){ScoreLength=4;}
				else if(Score>=100){ScoreLength=3;}
				else if(Score>=10){ScoreLength=2;}
				else{ScoreLength=1;}
				for(j=0;j<ScoreLength;j++)//将得分的数字的字模数据写入数组ScoreShow中
				{
					for(i=0;i<16;i++)
					{
						ScoreShow[32+16*j+i]=Table3[(Score/Pow(10,ScoreLength-1-j)%10)*16+i];
					}
				}
			}
			if(ScrollFlag)
			{
				ScrollFlag=0;
				MatrixLED_HS(ScoreShow,Offset);	//滚动显示,向左
				Offset++;
				Offset%=16+ScoreLength*8;	//循环滚动显示
			}
		}

	}
}

/**
  * 函    数:定时器0中断函数
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Routine() interrupt 1
{
	static unsigned char T0Count0,T0Count1;	//定义计时器静态计数变量
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
	Key_Tick();	//每隔10ms调用一次按键驱动函数Key_Tick
	T0Count0++;
	T0Count1++;
	if(PauseFlag==0){T0Count_0++;}	//不暂停时才计数
	if(T0Count0>=50)	//每隔500ms置反FlashFlag
	{
		T0Count0=0;
		FlashFlag=!FlashFlag;
	}
	if(T0Count1>=5)	//每隔50ms滚动显示一次字母或数字
	{
		T0Count1=0;
		ScrollFlag=1;
	}
	if(T0Count_0>=Duration/10)	//每隔Duration ms方块下落一个像素
	{
		T0Count_0=0;
		FallFlag=1;
	}
}

总结

在8X8点阵屏的俄罗斯方块的原理是一样的,稍改一下就行了。


网站公告

今日签到

点亮在社区的每一天
去签到