用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++基本只能看。

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

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

   。。。。。。

   。。

posted @ 2012-10-07 20:45  Ghost_zhao  阅读(9849)  评论(4编辑  收藏  举报