zoukankan      html  css  js  c++  java
  • 用MFC作的俄罗斯方块

         没有钱出去玩,也没有女友需要陪,作为一个宅宅的程序员,假期越长显得越无聊,于是拿出电脑,熟练的打开visual studio打发一下无聊的时间。

         一直想学学c++都抽不出时间,借十一小长假自娱自乐一下。凭借本人多年上当受骗经验,靠看书学编程几乎是不可能的,于是乎给自己定了一个小任务做一个俄罗斯方块,边干边学。同学们七嘴八舌的建议我用MFC,基于对话窗的小项目,我就从了他们。

         程序本身并没有什么,简简单单,还有一些这样那样的问题,只不过是本人的第一个c++项目,从一个初学者的角度和大家聊聊。

         代码已经上传:https://files.cnblogs.com/GhostZCH/Box.rar ,还请各位多指教。

        程序界面就是这样了,要多简单有多简单,两个按钮加两个picture control就完成了,通过上下左右四个键控制。

        我个人比较熟悉MVC的结构,考虑到这是个小程序只有一个界面,又考虑到我是个C++菜鸟,为了避免比必要的风险,于是省去了控制器。用CBoxDlg当做view层,用Game做Model辅以Tool类,前后台划分以及各个类的详细情况如图所示。CBoxDlg作为唯一的窗口,主要完成图像显示以及用户操作的收集,本身并不负责逻辑的处理。通过DrawBigNet()和DrawSmallNet()两个方法分别将后台数据显示在两个图片控件中,后台提供的数据由0和1组成的矩阵,1代表有方块,0反之。至于所有的逻辑操作全部放在Game类中完成,Tool类代表积木,Game类比较庞大,不过public的东西却很少,这也是面向对象封装的意义之所在吧,每个类对外只是一个黑盒,认真完成自己的事情不去关心其他类的实现细节,将自己应该做的踏踏实实的做好,此所谓高内聚。Game内的方法大致分为四类:第一、get方法向外提供界面需要的数据;第二、类似Input方法用来获得界面传来的操作,第三、canXXX和isXXX方法判断某个操作是否可行或者判断当前状态;第四、MoveXXX等方法执行这个操作。前两种方法都是public的后两种都是private的,前两种调用后两种,复杂的功能被逐渐分拆成细小的碎块,每个只要几行至十几行代码就够了。长期以来,本人一直呼吁不要将一个函数的长度超过30行,将复杂的功能逐渐细化,即容易完成也提高了可维护性。

    代码已经上传,列几个细节:

    1)如我所述,一切逻辑都交给后台,前台不过问后台的事情。

    void CBoxDlg::OnKeyDown(UINT nChar)
    {
        game->Input(nChar);
        Invalidate(true);// 重绘画面
    }

    2)绘制主网格,我一贯飘逸玄幻的代码风格。。

    void CBoxDlg::DrawBigNet()
    {
        CRect rect;
        CWnd *wnd = GetDlgItem(IDC_PIC_MAIN);
        CPaintDC dc(wnd);
    
        wnd->GetClientRect(&rect);
    
        if(game->GetBigNet())
            for(int i=0;i<game->NET_HEIGHT;i++)
                for(int j=0;j<game->NET_WIDTH;j++)
                    if(game->GetBigNet()[i*(game->NET_WIDTH)+j]==1)
                        dc.Rectangle(
                            j*rect.Width()/game->NET_WIDTH,
                            i*rect.Height()/game->NET_HEIGHT,
                            (j+1)*rect.Width()/game->NET_WIDTH,
                            (i+1)*rect.Height()/game->NET_HEIGHT);
    
        wnd->RedrawWindow();
    }

    3)MFC的事件响应有点奇特,必须覆盖一下这个方法:

    BOOL CBoxDlg::PreTranslateMessage(MSG* pMsg)
    {
        if(pMsg->message==WM_KEYDOWN)
            OnKeyDown((UINT)pMsg->wParam);
        return false;
    }

    4)这个方法我觉得比较巧妙,用假设移动统计移动前后方块数目的方法判断是否可以运动,省去一堆令人作呕的if、else,呵呵,分享一下:

    bool Game::CanMoveDown()
    {
        int cnt1 = 4,cnt2=0,x = Loc_X,y = Loc_Y+1;
        int *tmp = (int *)malloc(sizeof(int)*Height*Width);
    
        // 复制一个副本,统计原有方块数+tool中的块数
        for(int i=0;i<Height;i++)
            for(int j=0;j<Width;j++)
            {
                tmp[i*Width+j] = BigNet[i*Width+j];
                cnt1 += tmp[i*Width+j];
            }
    
        // 假设发生变换
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
            {
                if( i+y>=0      &&
                    i+y<Height  &&
                    j+x>=0      &&
                    j+x<Width   &&
                    tool->GetData()[i*4+j])
                    tmp[(i+y)*Width+j+x] = 1;
            }
    
        // 统计变换后方块数
        for(int i=0;i<Height;i++)
            for(int j=0;j<Width;j++)
                cnt2 += tmp[i*Width+j];
    
        delete(tmp);
    
        return cnt2==cnt1;
    }

    5)Remove 三兄弟,本来以为这里会有点烦,情况比较多无从下手,分解成三个函数以后还是很简单的:

    void Game::RemoveLines()
    {
        for(int i=Height-1;i>0;i--)
            while(CanRemoveLine(i))
                RemoveLine(i);
    }
    
    void Game::RemoveLine(int index)
    {
        for(int i=index;i>0;i--)
            for(int j=0;j<Width;j++)
                BigNet[i*Width+j] = BigNet[(i-1)*Width+j];
    
        for(int j=0;j<Width;j++)
            BigNet[j] = 0;
    }
    
    bool Game::CanRemoveLine(int index)
    {
        int count = 0;
    
        for(int i=0;i<Width;i++)
            if(BigNet[index*Width+i]==1)
                count ++;
    
        return count==Width;
    }

        数数看自己写的代码也有七八百行,历时三日,总算是完成,小有成就感。

        最后吐槽一下C++,用了很久的C#再用C++还真不习惯。

        连个像样的智能提示都没有,好不容易提示了个方法名却连个注释也看不到,你让我自己猜啊!

        光是字符和字符串就有如此之多的类型,真心记不住,更别提互相之间的转化了,放弃ASCII能死吗?

       编译器绝对是个弱智,你踩他一脚他告诉你头疼,类后面还要加;文件结束还得多加几个回车,不然还编译通不过。

       C#可以轻松的通过ClassDiagram操作代码,C++基本只能看。

        当然还有我最讨厌的指针和手动内存处理。。

       。。。。。。。。。。。。

       。。。。。。

       。。

  • 相关阅读:
    linux三剑客之sed
    线程与循环的区别?
    Notify和NotifyAll的区别?
    no system images installed for this target这个问题如何解决?
    Intent里ACTION的CALL和DIAL的区别?
    onConfigurationChanged方法的使用
    String和StringBuffer的区别?
    Activity的状态保存
    C#将datatable数据转换成JSON数据的方法
    SQL语句:关于复制表结构和内容到另一张表中的SQL语句
  • 原文地址:https://www.cnblogs.com/GhostZCH/p/2714381.html
Copyright © 2011-2022 走看看