XMOVE3.0手持终端——软件介绍(三):在2KB内存的单片机上实现的的俄罗斯方块 (原创)

一. 综述

  

  这也是我两年前完成的小项目,它基于我开发的XMOVE动作感应系统平台

   与XMOVE手持终端相关的介绍文章列表如下:

  硬件综述: 自制的彩屏手持动作感应终端

  软件综述:手持终端功能介绍

  软件介绍(一):精简型嵌入式系统的菜单实现和任务切换  

  软件介绍(二):在2KB内存单片机上实现的彩屏GUI控件库

  软件介绍(三):在2KB内存单片机上实现的俄罗斯方块

  软件介绍(四):在2KB内存单片机上实现的超精简五子棋算法

  软件介绍(五):在2KB内存的单片机上实现的T9中文输入法

  相对于五子棋,俄罗斯方块算法更是满天飞。我的代码还是相对好移植的,看起来也更清晰,方便学弟学妹们做编程小学期时移植。不过要做就要做的有特色:它运行在软硬件由我们独立开发的平台上。以下是截图:

  

  

  有如下特点:

  •     跑在独立开发的平台上,平台为MSP430F149,内存2KB,频率8MHz
  • 支持体感:可以通过左右倾斜来左右移动方块,还可以上下抖动改变方块形状
  • 代码精简,方便移植
  • 内存占用率极低
  • 支持横屏和竖屏操作
  • 支持等级:用户在达到一定分数后,等级会上升,从而方块下落速度变得更快

二. 系统设计

  我们将问题细化为以下几个方面:

  1. 方块的形状如何存储

  俄罗斯方块总共有19种形状,每种形状都由四个小方块组成。如何高效存储这些方块的形状是个值得思考的问题。

  

  上图介绍了存储方法:

  我们用以下数组保存形状:

const unsigned char  BoxShape[19][9]=
{    

    { 1,0,0,1,1,1,2,1,1  },
    { 1,0,1,1,2,1,1,2,2  },
    { 0,0,1,0,2,0,1,1,3  },
    { 1,0,0,1,1,1,1,2,0  },   

    { 1,0,2,0,1,1,1,2,5  },
    { 0,0,1,0,2,0,2,1,6  },
    { 2,0,2,1,2,2,1,2,7  },
    { 0,0,0,1,1,1,2,1,4  },   

    { 1,0,2,0,2,1,2,2,9  },
    { 2,0,0,1,1,1,2,1,10 },
    { 1,0,1,1,1,2,2,2,11 },
    { 0,0,1,0,2,0,0,1,8  },   

    { 0,0,0,1,1,1,1,2,13 },
    { 1,0,2,0,0,1,1,1,12 },   

    { 2,0,1,1,2,1,1,2,15 },
    { 0,0,1,0,1,1,2,1,14 },   

    { 1,0,1,1,1,2,1,3,17 },
    { 0,1,1,1,2,1,3,1,16 },   

    { 1,0,2,0,1,1,2,1,18 }
};

 2. 如何解决各个方块的互相转换的顺序:

  以上的数组已经解决了这一问题,多维数组的最后一位即该形状发生改变后的下一个形状。

 3. 如何存储当前的画布信息

  

 3. 如何解决碰撞问题:

  这大概是算法当中最值得考虑的了,我们用以下四个函数解决:

  所有的函数形参类似,它们的定义分别是:

  XAxi,YAxi:存储当前形状的左上角的点在画布中的实际偏移量。

  Data[][22]:存储的画布数据

  Type:当前形状的ID(即BoxSharp的数组偏移量)

u8 Bottom_Anti(u8 XAxi ,u8 YAxi,u8  Data[][22],u8 Type)
{
    u8 i;
    u8 Value=1;
    for(i=0;i<4;i++)
    {
        if(Data[XAxi+BoxShape[Type][2*i]][YAxi+BoxShape[Type][2*i+1]+1]==1)
            Value=0;
    }
    return Value;
}
u8 Right_Anti(u8 XAxi ,u8 YAxi,u8  Data[][22],u8 Type)
{
    u8 i;
    u8 Value=1; 

    for(i=0;i<4;i++)
    {
        if(Data[XAxi+BoxShape[Type][2*i]+1][YAxi+BoxShape[Type][2*i+1]]==1)
            Value=0;
    }
    return Value;     

}
u8 Lift_Anti(u8 XAxi ,u8 YAxi,u8  Data[][22],u8 Type)
{
    u8 i;
    u8 Value=1; 

    for(i=0;i<4;i++)
    {
        if(Data[XAxi+BoxShape[Type][2*i]-1][YAxi+BoxShape[Type][2*i+1]]==1)
            Value=0;
    }
    return Value;    

}
u8 Change_Anti(u8 XAxi ,u8 YAxi,u8  Data[][22],u8 *Type)
{
    u8 i;
    u8 Value=1;
    for(i=0;i<4;i++)
    {
        if(Data[XAxi+BoxShape[BoxShape[*Type][8]][2*i]][YAxi+BoxShape[BoxShape[*Type][8]][2*i+1]]==1)
            Value=0; //???????????
    }
    return Value;   

}

   算法本身很清晰,分别检测四个方块在改变后的实际位置是否已经有墙壁或方块存在。这个属性用临时变量Value存储,默认为1,如发现碰撞,则赋值为0并返回。 

4. 如何判断某一行已经被填充满并计分?

  下面是代码,很简单,检测该行的10个方格是不是都填满了,若填满,则加分,并重新刷新LCD

void CheckMark(u8 *mark ,u8  Data[][22],u8 dir)
{
    u8 m,t,s;
    u8 tMark=0;

    for(m=20;m>2;m--)
    {
        tMark=0;
        for(t=1;t<11;t++)
        {
            if(Data[t][m]==1)
                tMark++;
        }
        if(tMark==10)
        {
            for(t=m;t>2;t--)
            {
                for(s=1;s<11;s++)
                {
                    Data[s][t]= Data[s][t-1];
                }
            }
            m++;
            TotalRefreshLCD(Data,dir);
            delay_ms(200);
            (*mark)++;
        }
    }
}

  5. 如何产生方块?并检查方块已经顶到头?

  代码如下:(值得讨论的是,在单片机中如何产生随机数?)

void GenerateBox(u8 *XAxi ,u8 *YAxi,u8 *Type,u8 Data[][22],u8 *FailFlag)
{
    u8 temp;
    *XAxi=5;
    *YAxi=2;
    *Type=random(0,18);  //产生随机数

    for(temp=2;temp<10;temp++)
    {
        if(Data[temp][2]==1)   //若在画布上的第3行出现方块,则失败
        {
            *FailFlag=2;
            return;
        }
    }    

}

   好了,这些核心问题我们都讨论完了,下面讨论核心流程。

三 . 核心处理流程

  若加上全部处理,包括刷新界面,显示欢迎界面,显示失败和分数界面,那么还是很冗长的。这些对于实现核心算法无益,我们也不需要关心。

  下面是流程图:

  

   主要操作代码如下,使用了状态机,请注意看OS_func_state==1的流程,这是系统在运行时的主要流程。

主要核心操作流程

u8 TerisBrick()
{
    u8 grade=0,mark=0;
    u8 GameGUIData[12][22];
    u8 KeyTemp;
    u8 XAxi,YAxi,Type,OriGrade=0;

        TickControlEN=1;
    u8* temp[2];
    u8 dir=0;

    temp[0]="竖版游戏模式";
    temp[1]="横版游戏模式";
         if(AccControlEN==1)
                        {

                            ADXL345Init(0);
                        }

    while(OS_func_state<10)
    {
        switch(OS_func_state)
        {
        case 0:
            GUI_GameOpen("俄罗斯方块",&grade);

            OriGrade=grade;
            dir=ListGUI(temp,"选择游戏方向",2);  //dir 表征运动方向,0:横版,1竖版
            dir--;
            Clear_Screen();
            OS_func_state=1;

            break;
        case 1:

            TerisBrickInit(GameGUIData,12,22);
            _EINT();
            SetPaintMode(1,COLOR_Black);
            if(dir==0)
            {
                Rectangle(30,22,110,190,1);

            }
            else
                Rectangle(22,32,232,132,1);
            PutString(275,70,"grade");
            PutString(275,110,"mark");
            Lcd_disp(2,200,"左右方向键移动,下键加速,上改变形状");
            while(OS_func_state==1)
            {
                          back_light=back_light_set;
                          FontSetTotal(COLOR_Black);

            NumberDis(275,90,grade,3,1);
            NumberDis(275,130,mark,3,1);
            GenerateBox(&XAxi,&YAxi,&Type,GameGUIData,&OS_func_state);
            while(Bottom_Anti(XAxi,YAxi,GameGUIData,Type))
            {       key_data=KEYNULL;
            ShowBoxGUI(XAxi,YAxi,Type,1,dir);
                        if(AccControlEN==1)
                        {
                          ADXL345ReadData();
                          ADXL345ShowData(0);
                          L3G4200DReadData();
                          L3G4200DShowData();
                        }
            if(KeyTemp==KEYDOWN_DOWN)
                delay_ms(150);
            else
                delay_ms(500-70*grade);

            ShowBoxGUI(XAxi,YAxi,Type,0,dir);
            YAxi++;
            if(AccControlEN==1)
                        {
                              if(dir==0)  //横版
                          {
                            if(AccX>3)
                              key_data=KEYLEFT_UP    ;
                             else if(AccX<-3)
                              key_data=KEYRIGHT_UP  ;
                             if(GyroY>400||GyroY<-400)
                             {
                               key_data=KEYUP_UP  ;
                                 delay_ms(200);
                             }

                          }
                          else
                          {
                             if(AccY>3)
                              key_data=KEYLEFT_UP    ;
                             else if(AccY<-3)
                              key_data=KEYRIGHT_UP  ;
                               if(GyroX>400||GyroX<-400)
                               {
                               key_data=KEYUP_UP  ;
                               delay_ms(200);
                               }

                          }
                        }
            switch(key_data)
            {
            case KEYLEFT_UP    :
                if(Lift_Anti(XAxi,YAxi,GameGUIData,Type)!=0)
                    XAxi--;
                break;
            case KEYRIGHT_UP  :

                if(Right_Anti(XAxi,YAxi,GameGUIData,Type)!=0)
                    XAxi++;

                break;
            case KEYUP_UP  :
                if(Change_Anti(XAxi,YAxi,GameGUIData,&Type)!=0)
                    Type=BoxShape[Type][8];
                break;
            case KEYDOWN_UP  :

                if(Bottom_Anti(XAxi,YAxi,GameGUIData,Type))
                    YAxi++;
                break;

            case KEYCANCEL_UP    :
                if(MessageGui("提示信息","是否跳出?",1)==1)
                {
                                         TickControlEN=1;
                    OS_func_state=2;
                    return 1;
                }
                else
                    TotalRefreshLCD(GameGUIData,dir);
                break;

            }
            KeyTemp=key_data;
                        key_data=KEYNULL;
            }

            for(u8 t=0;t<4;t++)
            {
                GameGUIData[XAxi+BoxShape[Type][2*t]][YAxi+BoxShape[Type][2*t+1]]=1;
            }
            ShowBoxGUI(XAxi,YAxi,Type,1,dir);
            CheckMark(&mark,GameGUIData,dir);
            grade=OriGrade+mark/10;

            } 

            break;
        case 2:
            if(mark==0)
                MessageGui("超级菜鸟","学习下游戏规则",0);
            else if(mark>0&&mark<10)
                MessageGui("初学者","你还要加油哦",0);
            else if(mark>9&&mark<20)
                MessageGui("中级水平","哥们你很牛逼",0);
            else if(mark>19&&mark<30)
                MessageGui("高级水平","无敌哥!膜拜",0);
            else
                MessageGui("超级无敌","不是一般人!!",0);
            if( MessageGui("提示信息","是否继续",1)==1)
                OS_func_state=0;
            else
            {
                          TickControlEN=1;
                OSTaskClose();

            }
            break;

        }
        }

        return 1;

}

四. 其他模块

  其他模块主要包括界面显示,分数显示,方块显示和键盘输入等。考虑到和平台相关,因此不具备移植性,贴在下面仅供参考。

  1. 初始化界面绘图画布数组,主要是建立“围栏”

void TerisBrickInit(u8  Data[][22],u8 i,u8 j)
{

    u8  a,b;
    for(b=0;b<i;b++)
    {
        for(a=0;a<j;a++)
            Data[b][a]=0;
    }
    for(b=0;b<j;b++)
    {
        Data[0][b]=1;
        Data[11][b]=1;
    }
    for(a=0;a<i;a++)
    {
        Data[a][0]=1;
        Data[a][21]=1;
    }

}

   2. 绘制方块和刷新界面

绘制界面和方块

void  ControlBox(u8 x,u8 y,u8 mood,u8 dir)
{
    //dir指出方向,0,为横版,1为竖版
    if(mood==1)
    {       SetPaintMode(0,COLOR_Black);
    if(dir==1)
        Rectangle(22+10*y,22+10*x,32+10*y,32+10*x,1);
    else 

        Rectangle(22+8*x,22+8*y,30+8*x,30+8*y,1);
    SetPaintMode(0,COLOR_Red);
    if(dir==1)
        Rectangle(23+10*y,23+10*x,31+10*y,31+10*x,1);
    else
        Rectangle(23+8*x,23+8*y,29+8*x,29+8*y,1);
    }

    else
    {
        SetPaintMode(1,COLOR_Black);
        if(dir==1) 

            Rectangle(22+10*y,22+10*x,32+10*y,32+10*x,1);
        else
            Rectangle(22+8*x,22+8*y,30+8*x,30+8*y,1);

    }

}
void TotalRefreshLCD(u8  Data[][22],u8 dir)
{
    u8 t,s;
    SetPaintMode(1,COLOR_Black);
    if(dir==0)

        Rectangle(30,22,110,190,1);

    else
        Rectangle(22,32,232,132,1);  

    for(t=1;t<11;t++)
    {
        for(s=2;s<21;s++)
        {
            if(Data[t][s]==1)
                ControlBox(t,s,1,dir);
        }
    }

}

五. 总结

  本科一年级时候写过一个俄罗斯方块,代码冗长可读性极差,各种判断和奇怪的变量,最后还有一堆BUG。 这套代码算是对当时愚蠢的我的补偿吧,其实写的也不怎么地。比如,画布大小必须是22*12么?为什么不可变?

  蛮想把我开发的这套系统拿出来给大家分享的,可惜自制硬件就是这样:给自己带来了快乐,给别人带来了开发和移植的麻烦。

  说多了,呵呵

  有任何问题,欢迎随时交流。

时间: 2024-11-01 19:25:58

XMOVE3.0手持终端——软件介绍(三):在2KB内存的单片机上实现的的俄罗斯方块 (原创)的相关文章

XMOVE3.0手持终端——软件介绍(二):在2KB内存的单片机上实现的彩屏GUI控件库

一. 综述 嵌入式系统发展日新月异,安卓和ios已然战胜了当年雄霸天下的塞班,界面是我们特别看重的因素之一.不过你考虑过自己做一套系统,写一个界面库么?在单片机上自制系统,可以很好的锻炼编程能力和架构设计能力. 这些界面库都是在底层画点画线的驱动程序上实现的,基于我的XMOVE动作感应系统.基本具有硬件无关性.支持彩屏320*240的分辨率,由于考虑不同分辨率的开发过分复杂(想想看你需要计算每个点布局在哪个位置,这对安卓等系统都是大问题),因此我并没有太过完善的考虑过其他分辨率. 我已经写过一篇

XMOVE3.0手持终端——软件介绍(五):在2KB内存的单片机上实现的T9中文输入法

编者注: X-MOVE是作者在业余时间于2010年6月份启动的以运动传感开发,算法和应用的平台,目前已经发展了三个版本,第四版的开发接近尾声.发布在博客园仅为交流技术,不存在商业目的,作者保留一切权利.   一. 综述      所谓T9,指的是在手机上广为流传的九宫格输入法.中文输入法大家每天都在使用,那么多大的空间才能承载一个输入法呢?搜狗安装包已经20M了,手机版本的也有2M.但我会告诉你,实现中文输入法仅需要14KB的存储空间和不到100byte的内存.虽然没有联想,并只支持拼音,但已经

XMOVE3.0手持终端——软件介绍(四):在2KB内存的单片机上实现的超精简五子棋对战算法(原创)

一. 综述 这是我两年前完成的一个小项目,它基于我开发的XMOVE动作感应系统平台.五子棋算法网上随便一搜到处都是,不过值得自豪的是,我在2KB内存的单片机上不仅跑上了我自制的嵌入式OS,还能同时跑五子棋.这是界面截图:  以下是它的功能和特性: 内存占用极低,约600byte 执行一次迭代过程,算法在初级水平(同学,这是单片机,不是电脑!) 在8MHz的MSP430上算法执行时间不超过0.3s 支持人机对战,双人对战和无线对战(通过NRF24L01实现) 代码精简 嵌入式彩屏GUI实现 支持陀

XMOVE3.0手持终端——软件介绍(一):精简型嵌入式管理系统的菜单实现和任务切换

编者注: X-MOVE是作者在业余时间于2010年6月份启动的以运动传感开发,算法和应用的平台,目前已经发展了三个版本,第四版的开发接近尾声.发布在博客园仅为交流技术,不存在商业目的,作者保留一切权利.        一. 综述和废话 本系统是我的XMOVE动作感应系统框架的嵌入式实现部分. 一提到OS一般都会被人喷.OS是何等庞大的东西,区区小辈凭什么敢把自己的几百行代码称之为OS?叫做框架都不行!  有句话叫简单就是美.方便移植,使用简单的c语言框架,在单片机上再合适不过了. 想象一下,一个

XMOVE3.0手持终端——综述:软硬件全部自行开发的彩屏体感控制器

编者注: X-MOVE是作者在业余时间于2010年6月份启动的以运动传感开发,算法和应用的平台,目前已经发展了三个版本,第四版的开发接近尾声.发布在博客园仅为交流技术,不存在商业目的,作者保留一切权利.   一. 综述 乔布斯曾经说过:做软件的人应该也制造属于自己的硬件. 不觉得每天给电脑,手机开发程序很不爽么? 为什么总是要"给别人打工",用别人的SDK? 小时候特别羡慕有文曲星的同学,也特别梦想自己做一个. 在大四这个理想也成为了现实. 我启动了我自己的体感项目XMOVE的2.0版

[XMOVE自主设计的体感方案] 历代版本系统介绍(三)X-MOVE3.0

编者注: X-MOVE是作者在业余时间于2010年6月份启动的以运动传感开发,算法和应用的平台,目前已经发展了三个版本,第四版的开发接近尾声.发布在博客园仅为交流技术,不存在商业目的,作者保留一切权利. 一 . 前言 XMOVE3.0是2.0版本上的升级版本,硬件改动不大,改动主要集中在软件和算法的升级.它的开发时间是2011年1月--2011年6月,开端于大四寒假,结束于北邮创新展. 它的发展,也是我转变思维的过程.曾经的我,认为硬件是王道,软件只是装饰硬件的皮.甚至,我会觉得开发软件的人都太

旋风备忘录0.10软件介绍

软件名称:旋风备忘录0.10 英文名称:CquMemo支持机型:Java通用版本,支持所有Java机型旋风备忘录介绍 软件名称:旋风备忘录英文名称:CquMemo当前版本:0.10Beta 1.软件介绍该软件可以实现在手机中记录各种信息,可以保存您的日程安排.会议记录和重要事件备忘等信息.目前该软件只是最初的版本,如果您有什么好的意见和建议,可以和我联系. 2.功能介绍:该软件主要实现备忘录功能,现在具备的功能如下:A.为该软件设置进入密码.通过这个功能,可以在一定程度上保护您的个人隐私.B.修

《鸟哥的Linux 私房菜 基础学习篇(第三版)》——0.4 软件程序运行

0.4 软件程序运行 鸟哥的Linux 私房菜 基础学习篇(第三版) 鸟哥在上课时经常会开玩笑地问:"我们知道没有插电的计算机是一堆废铁,那么插了电的计算机是什么?"答案是"一堆会电人的废铁."这是因为没有软件的运行,计算机的功能就无从发挥了.就好像没有了灵魂的躯体也不过就是行尸走肉,重点在于软件/灵魂.所以下面咱们就得要了解一下"软件"是什么. 一般来说,目前的计算机系统将软件分为两大类,一个是系统软件,一个是应用程序.但鸟哥认为我们还是得要了

《FLUENT 14.0超级学习手册》——第2章 FLUENT软件介绍2.1 FLUENT软件特点简介

第2章 FLUENT软件介绍 FLUENT 14.0超级学习手册 CFD商业软件FLUENT是通用CFD软件包,用来模拟从不可压缩到高度可压缩范围内的复杂流动.由于采用了多种求解方法和多重网格加速收敛技术,因而FLUENT能达到最佳的收敛速度和求解精度.灵活的非结构化网格和基于解的自适应网格技术及成熟的物理模型,使FLUENT在转换与湍流.传热与相变.化学反应与燃烧.多相流.旋转机械.动/变形网格.噪音.材料加工.燃料电池等方面有广泛的应用. 学习目标: 学习FLUENT软件的主要特点: 了解F