zoukankan      html  css  js  c++  java
  • 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么?为什么不可变?

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

      说多了,呵呵

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

  • 相关阅读:
    594 One Little, Two Little, Three Little Endians
    提出js框
    从4个细节做好查询语句优化
    Windows Sever2008 R2 iis部署
    收集 常用CSS样式的笔记
    html常用标签介绍
    加密URL
    JQuery UI选项卡插件及图片轮播插件
    推荐两款富文本编辑器:NicEdit和Kindeditor
    合并一条SQL语句 根据不同条件
  • 原文地址:https://www.cnblogs.com/buptzym/p/2556918.html
Copyright © 2011-2022 走看看