南京邮电大学电子设计竞赛 选拔赛试题 A题俄罗斯方块.docx
《南京邮电大学电子设计竞赛 选拔赛试题 A题俄罗斯方块.docx》由会员分享,可在线阅读,更多相关《南京邮电大学电子设计竞赛 选拔赛试题 A题俄罗斯方块.docx(37页珍藏版)》请在冰豆网上搜索。
南京邮电大学电子设计竞赛选拔赛试题A题俄罗斯方块
2012年南京邮电大学电子设计竞赛
选拔赛试题
(A题)
小组成员:
B10011305刘薇
B10011420吕健
B10010235梁燕青
二O一二年五月七日
2012年南京邮电大学电子设计竞赛
选拔赛试题
无线遥控游戏机(A题)(通信类)
摘要
俄罗斯方块是一款风靡全球的电视游戏机和掌上游戏机游戏,它由俄罗斯人AlexPajitnov发明,故得此名。
俄罗斯方块的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。
由于上手简单、老少皆宜,从而家喻户晓,风靡世界。
这次设计使用STC89C54RD单片机和LCD12864液晶显示,由红外收发模块以及红外遥控器控制组合,来实现比较简单的俄罗斯方块设计。
此次设计初期是在keil和protues联合仿真中进行,编程语言为51C语言,后期是进行实物连接。
论文描述了俄罗斯方块的主要功能函数的实现,算法分析和实物焊接过程中遇到的问题。
【关键词】:
俄罗斯方块STC89C54RD红外收发模块LCD12864
一、系统方案
1.1系统方案描述
按照系统的设计功能要求,本无线遥控游戏机系统的设计采用单片机软件系统实现,用单片机的自动控制功能配合按键控制,来控制俄罗斯方块游戏的正常进行。
达到游戏图形的开始/结速、左移、右移、翻转、旋转、加速下降。
并且达到旋转90度使用而功能不改变。
我们使用了红外收发模块达到用红外无线遥控器控制游戏进行。
以上都满足了题目所有要求。
此外,我们还进行了创新提高,将题目中的3种图形增加到了19种图形,增加了插空功能,使带字模本来只能水平显示的12864利用画图方法进行水平显示,美化了开始、游戏中和结束的界面,显示屏右上方显示下一个即将出现的图形,显示屏下方显示游戏玩家的成绩和等级,优化了人机关系,使玩家更轻松愉悦。
1.2系统方案比较
1.2.1程序整体思路
单片机上的程序设计一般是一个大循环结构,对于俄罗斯方块的程序设计,首先通过随机数发生器产生一个伪随机数,然后程序根据此数值所对应的图形模块装入ram的固定区域内,紧接着将此图像写入LCD所对应的显示缓冲区中,显示程序将缓冲区内的内容显示在LCD上,如果没有控制键按下,图形将自动向下移动。
如果有键按下,程序将根据按下的键来改变图形存储区的值,同时程序将判断图形是否已到达边界,先判断图形是否能向左、向右、旋转,再判断是否能继续下落一格,当图形最上层到达显示区顶部,则游戏结束,此时将清楚显示缓冲的内容,游戏重新开始。
整个游戏期间屏幕右上方显示下一个即将出现的图形,屏幕下方显示玩家的级别和分数。
消层增加分数1分,50分晋级,级别越高,图形自动下降的速度越快,10级为通关。
显示结束界面。
1.2.2显示方案选择
方案一:
分数和级别可以使用数码管,但数码管只能显示简单的数字,其电路复杂,占用资源较多,字母和文字显示几乎不能实现,显示信息少,不宜显示大量信息。
固放弃次方案。
方案二:
1602有明显的有点:
微功耗,尺寸校,超薄轻巧,显示信息量大,而且容易控制。
但对于次图形绘制移动游戏显示需求不能满足,固放弃次方案。
方案三:
我们设计的俄罗斯方块游戏系统要显示的信息多,所以应选用显示功能更好的液晶显示,要求能显示更多的数据,增加显示信息的可读性,看起来方便。
而12864液晶屏显示信息量更大,字迹美观,视觉舒适,而且容易控制。
它可以显示8*4行16*16点阵的汉字,也可以完成图形显示,低电压功耗是其又一显著特点。
由该模块构成的液晶显示方案与同类型的图形点阵液晶显示模块相比,不论硬件电路结构或显示程序都要简洁得多,切该模块的价格也略低于相同点阵的图形液晶模块。
通过以上三种方案的比较,我们选择方案三。
1.2..3遥控方案选择
方案一:
有线遥控:
采用有线遥控抗干扰能力强,由于它与控制对象直接相连,所以他的控制距离有限。
由于他的这个局限性,这种技术一般只能用于短距离控制。
方案二:
无线遥控:
无线电遥控最主要的特点是,遥控距离远。
一般不受遥控方向或角度的制约,无线电波接收器能接收数十米至数百米外由操作者操纵无线电波发射器送来的电波信号。
通过以上两种方案的比较,我们选择方案二。
二、理论分析与计算
2.1显示方案描述
12864液晶屏显示原理如图
图表112864液晶显示原理图
带中文字库的128X64是一种具有4位/8位并行、2线或3线串口多种接口方式,内部含有国际一级、二级简体中文字库的点阵图形液晶显示模块;其显示分辨率为128*64,内置8192个16*16点汉字,和128个16*8点ASCII字符集。
利用该模块灵活的接口方式和简单、方便的操作指令,可构成全中文人机交互图形界面。
通过对LCD初始化,向LCD指定起始位置写入一个字符串,写入指令代码,从LCD中读出数据。
设置有显示字符串子函数、起点坐标、字符串起始地址,向LCD指定位置画一条长度一定指定颜色的垂直线和水平线,坐标未变化向LCD指定坐标写入一个象素,象素颜色有两种,0代表白(无显示),1代表黑(有显示)。
首先测试LCD是否处于忙状态,清除Lcd全屏,如果清除模式Mode为0,则为全屏清除为颜色0则无任何显示,否则为全屏清除为颜色1,全屏填充显示,其中部分清屏函数对应的是垂直坐标,游戏中加速模块为加速逻辑运算而设置的掩码表,这是以牺牲空间而换取时间的办法。
2.2控制算法表述
消层算法
设置循环变量数,每一次循环过程中检测此行是否已满,也就是检测这一行所对应的存储单元是否全为一,如果全为一则将消去此行,即将此行对应的ram清零。
同时将此行上面ram的内容按每列依次下移,下移完之后重新将R2=0,又从第一行开始检测。
此行不全为零时只需将R2=R2+1。
直到R2=9消层过程结束。
旋转算法
首先要确定一个旋转中心,假如旋转中心的编号是34,将34除以8,商是4存入R1余数是2存入R2,及时用商和余数建立坐标系。
此时若以此为旋转中心的图形需要旋转,则将每一个小方块对应编号除以8,商减去R1,余数减去R2,而每一个方块一次将旋转90度,所以有公式x2=-y1,y2=x1其中x1,y1为旋转之前的坐标,x2,y2为旋转之后的坐标。
旋转完之后将x2加上R1将y2加上R2,然后将x2乘以8再加上y2。
执行四次此过程旋转结束。
参数计算方法
游戏界面:
大的方面来说一共12列20行(x=0_12;y=0_20)第2行到第20行共19行。
第1行,最下面。
第2行,最上面。
定义共21行,其中num[0]为下墙壁行,num[20]为上墙壁行,每行12格,最左一格为左墙壁列,最右一格为右墙壁列。
得到前一个状态的翻转后的形式(i=type*4+change)
无线方案设计
红外遥控系统:
通用红外遥控系统由发射和接收两大部分组成。
应用编/解码专用集成电路芯片来进行控制操作,如图1所示。
发射部分包括键盘矩阵、编码调制、LED红外发送器;接收部分包括光、电转换放大器、解调、解码电路。
图表2红外线摇杆系统框图
接收器对外只有3个引脚:
Out、GND、VCC与单片机接口非常方便,如图7所示。
三、电路与程序设计
电路设计
图表312864液晶屏原理如图
图表454单片机原理如图
图表5红外收发模块原理如图
程序设计
程序流程图:
图表6程序流程图1
图表7程序流程图2
图表8程序流程图3
附录I部分程序代码
//初始化MPU包括开启定时器0和定时器1红外线用到的外部中断口P3.2
voidInitCpu(void)
{
wela=0;
dula=0;
TMOD=0x02;//工作在方式0定时器0和1都开启
TH0=0x00;//重载值
TL0=0x00;//初始化值
ET0=1;//开中断
TR0=1;
TH1=0;//初值为0定时2^13=
TL1=0;
TR1=1;
//外部中断
ET1=1;
IT0=1;//指定外部中断0下降沿触发,INT0(P3.2)
EX0=1;//使能外部中断
//EA=1;//开总中断
EA=1;
}
//定时中断服务子程序来获取键值
voidTimer1Int(void)interrupt3//定时器0中断(中断的形式读取键值)
{
if(irok)//如果接收好了进行红外处理
{
Ircordpro();//键值的获取
irok=0;
if(irpro_ok)
{
switch(IRcord[2])//如果处理好后进行工作处理
{
case0x0c:
KeyBuffer=11;
break;
case0x09:
KeyBuffer=PAUSE;//键值用到只有813172125
break;
case0x18:
KeyBuffer=CHANGE;
break;
case0x1c:
KeyBuffer=DOWN;
break;
case0x5a:
KeyBuffer=RIGHT;
break;
case0x08:
KeyBuffer=LEFT;
break;
case0x46:
KeyBuffer=RESTAR;
break;
default:
break;
}
}
}
}
//红外线模块的处理函数(键值的获取)
voidIrcordpro(void)//红外码值处理函数
{
unsignedchari,j,k;
unsignedcharcord,value;
k=1;
for(i=0;i<4;i++)//处理4个字节
{
for(j=1;j<=8;j++)//处理1个字节8位
{
cord=irdata[k];
if(cord>7)//大于某值为1,这个和晶振有绝对关系,这里使用12M计算,此值可以有一定误差
value|=0x80;
if(j<8)
{
value>>=1;
}
k++;
}
IRcord[i]=value;
value=0;
}
irpro_ok=1;//处理完毕标志位置1
}
//根据积木图标左下坐标X,Y来画出积木图标(通过控制DrawMode,可用于擦除)
//其中横屏和竖屏用两个函数分别实现
voidDrawSign(structJimuTemp,unsignedcharDrawMode)
{
unsignedcharm,n;
for(m=0;m<4;m++)//在一个4*4的空间中绘制积木
for(n=0;n<4;n++)
{
if((Temp.dat&MaskTab[4*m+n])!
=0)
Lcd_Rectangle(Temp.x+n*unit,Temp.y-3-unit*m,Temp.x+n*unit+3,Temp.y-unit*m,DrawMode);
}
}
voidDrawSign_original(structJimuTemp,unsignedcharDrawMode)
{
unsignedcharm,n;
for(m=0;m<4;m++)//在一个4*4的空间中绘制积木
for(n=0;n<4;n++)
{
if((Temp.dat&MaskTab[4*m+n])!
=0)
Lcd_Rectangle_original(Temp.x+n*3,Temp.y-2-3*m,Temp.x+n*3+2,Temp.y-3*m,DrawMode);
}
}
voidDrawSign_tishi(structJimuTemp,unsignedcharDrawMode)
{
unsignedcharm,n;
for(m=0;m<4;m++)//在一个4*4的空间中绘制积木
for(n=0;n<4;n++)
{
if((Temp.dat&MaskTab[4*m+n])!
=0)
Lcd_Rectangle(Temp.x+n*(unit-1),Temp.y-2-(unit-1)*m,Temp.x+n*(unit-1)+2,Temp.y-(unit-1)*m,DrawMode);
}
}
//将方块固定函数实现
voidFixSign(void)//重点理解
{
unsignedcharm,n;
for(m=0;m<4;m++)//行循环
for(n=0;n<4;n++)//列循环
{
if((Sign[0].dat&MaskTab[4*m+n])!
=0)//将不能下落的积木固定
{
num[20-(Sign[0].y-2)/unit+m]|=MaskTab[11-Sign[0].x/unit-n];
}
}
return;
}
//判断是否可以再移动
unsignedcharCheckIf(void)
{
unsignedcharm,n;
for(m=0;m<4;m++)//行循环
for(n=0;n<4;n++)//列循环
{
if((Sign[1].dat&MaskTab[4*m+n])!
=0)//MaskTab是讲每行的固定查找格式Sign[1]是积木数组中的一个
{//下一个状态不为0的部分
if((num[20-(Sign[1].y-3)/unit+m]&MaskTab[11-Sign[1].x/unit-n])!
=0)//以每一个最下方得方块作为坐标,并且作为判断标志
return0;
}
}
return1;
}
//判断积木图标是否可以继续下降一格
unsignedcharCheckIfDown(void)
{
Sign[1]=Sign[0];//原来sign1使用语存储上一个状态
Sign[1].y+=unit;//每次下降一行(也就是坐标加3)
returnCheckIf();//是否还能继续下落
}
//判断积木图标是否可以向左移动
unsignedcharCheckIfLeft(void)
{
Sign[1]=Sign[0];//先判读那得到标志位1可以0不可以
Sign[1].x-=unit;
returnCheckIf_original();
}
//判断积木图标是否可以向右移动
unsignedcharCheckIfRight(void)
{
Sign[1]=Sign[0];
Sign[1].x+=unit;
returnCheckIf();
}
//判断是否可以旋转
unsignedcharCheckIfRoll(void)
{
unsignedchari;
unsignedintTemp;
Sign[1]=Sign[0];
if(++Sign[1].change>3)//控制翻转数载0到2之间
Sign[1].change=0;
i=Sign[1].type*4+Sign[1].change;//得到前一个状态的翻转后的形式(i=type*4+change)总共有28种
Temp=(unsignedint)Block[i][0]<<8;//BLOCK是积木的形态控制数组
Temp=Temp|Block[i][1];//高低八位的重组
Sign[1].dat=Temp;//形态发生了变化
returnCheckIf();
}
//寻找满格的行并做消除处理
//最多寻找4个满行并做消除
voidDelFull(void)
{
unsignedcharm,n;//循环控制变量
unsignedcharTemp;//行数观察
unsignedcharFlag=0;//标志位
Temp=(Sign[0].y-3)/unit;//先算出当前行数
if(Temp>=20)//防止越过了下边界
Temp=1;
else
Temp=20-Temp;
for(n=Temp+3;n>=Temp;n--)//积木图标的最顶行开始寻找满行比较有利于运算
{
if(num[n]==0xfff)
{
Flag=1;//消行标志
for(m=n+1;m<=19;m++)//把上一行的数赋值给下一行积木
{//第19行始终不动0x801
num[m-1]=num[m];
}
num[m]=0x801;//改为绘制界面初始值
Score++;//每找到一个满行,则分数加1
}
}
if(Flag)//为加速而设置并判断的标志,有已固定的积木有满格消行变化则重画积木界面
{
for(m=Temp;m<=19;m++)//为加速,不必要重第一行重画起,只需要从积木图标最下行开始往上的重画
for(n=1;n<=10;n++)//
{
if((num[m]&MaskTab[n])==0)
{
if(Lcd_ReadPixel(40-(n-1)*unit,76-(m-1)*unit)!
=0)//为加速而做的读象素操作
{
Lcd_Rectangle(40-(n-1)*unit,76-(m-1)*unit,40-(n-1)*unit+3,76-(m-1)*unit+3,0);//清除操作
}
}
else
{
if(Lcd_ReadPixel(40-(n-1)*unit,76-(m-1)*unit)==0)//为加速而做的读象素操作
{
Lcd_Rectangle(40-(n-1)*unit,76-(m-1)*unit,40-(n-1)*unit+3,76-(m-1)*unit+3,1);//画图操作
}
}
}
}
}
//随机产生一个积木图标放到预产生区域并显示出来
voidCreatSign(void)
{
unsignedcharn;
unsignedintTemp;
DrawSign_tishi(Sign[2],0);//先清除
n=Random()*28;
Temp=(unsignedint)Block[n][0]<<8;
Temp=Temp|Block[n][1];
Sign[2].dat=Temp;
Sign[2].x=50;//首坐标为45
Sign[2].y=4*unit+2;//14
Sign[2].type=n/4;//形状是随机的,但是类型只能有一种
Sign[2].change=n%4;//类型的变化
DrawSign_tishi(Sign[2],1);//后画出
}
/********************************
游戏的具体过程,也是俄罗斯游戏的关键部分
*********************************/
unsignedcharGamePlay(void)
{
unsignedcharm,n;
unsignedintTemp;
SysFlag|=1<InitRandom(TL0);//用定时器作为种子
Print_state
(1);
//PrintScore();
LCD_ShowString(84,63,"SC:
00",1);
//PrintLevel();
LCD_ShowString(100,63,"LV:
01",1);
CreatSign();//界面的绘制
while
(1)
{
if((SysFlag&(1<{
SysFlag&=~(1<Sign[0]=Sign[2];//把sign[0]=接收
CreatSign();
Sign[0].x=16;//初始坐标设定
Sign[0].y=19;
for(m=0;m<4;m++)//行循环
{
for(n=0;n<4;n++)//列循环
{
if((Sign[0].dat&MaskTab[15-m*4-n])==0)//主要用于检测最上面一行为了是从最后一行开始查询
break;
}
if(n==4)
Sign[0].y-=unit;
}//将积木图标出现置顶
for(m=0;m<4;m++)//行循环
for(n=0;n<4;n++)//列循环
{
if((Sign[0].dat&MaskTab[4*m+n])!
=0)
{
if((num[20-(Sign[0].y-2)/unit+m]&MaskTab[11-Sign[0].x/unit-n])!
=0)
SysFlag|=1<}
}
if((SysFlag&(1<=0)
returnDEADFLAG;//如果产生新的积木图标中的方块与已固定好的方块重合,则死亡。
游戏结束
DrawSign(Sign[0],1);
}
switch(KeyBuffer)
{
caseLEFT:
KeyBuffer=0;//清除键盘缓存区
if((SysFlag&(1<{
if(CheckIfLeft())
{
DrawSign(Sign[0],0);//清除原图形,画出心图形
Sign[0].x-=unit;
DrawSign(Sign[0],1);
}
}
else
{
if(++Level>=10)//分数与时
Level=1;
PrintLevel();
}