MFC 实现 俄罗斯方块

 
  1. 很经典的游戏.简单又让人着迷.
    今天就用mfc来实现个自己的俄罗斯方块.运行截图如下:

看起来还行吧..
网上有很多版本的实现代码.但是看了几个都是用一个很大的数组记录这些下落的物体的形状.然后写一个很大的switch case来实现变形.感觉真麻烦.今天提出一个容易的实现方法.下面切入正题


思路是这样的:

一:随机生成物体

二:控制

    1.变形

        围绕一个方块向右旋转90度.以变形

    2.左右下移动

        1>物体左右移动的时候不要过界.

        2>物体落定后.

            (1).设定它落下的位置.

            (2).看是否满了一行.满了一行消去

            (3).看是否方块垒到顶了.到了game over

三:重复以上步骤


约定:
下落的会变形的那个小东东,我们叫 物体Object (--!!.大家忍受下这个名字.我是想不出其他的名字了.)
左侧那个 物体在里面移动的区域叫 游戏区域 Block


实现这个游戏的思路跟玩这个游戏一样简单...
1,随机生成一个物体.
2,响应键盘的输入,做左右下移动和变形
3,物体落到底部,判断是否满一行,物块垒满一行消除之.垒到顶部game over

但是实现起来貌似又不是那么简单了.

首先看起来随机生成一个不同形状的物体就是挑战
怎么在视图上画一个个的不同形状的物体呢?
我们看下这个游戏.每个物体都是由更小的方块组成.这些基本的小方块没有什么不同,
再看看这个二维的游戏界面,左边的游戏区域,一个个的小方块合在一起是否就是我们所熟悉的二维数组?
假如我们把这个游戏区域映射到一个二维数组,当这个数组元素有物体占据的时候就在上面涂颜色,没有则涂背景色.不就实现了么?.

好了可以定义游戏的区域的数据结构了.这个游戏中由于要画不同的颜色,因此数据结构可以如下定义:

  1. struct tagBlock
  2. {
  3.     bSet;
  4.     COLORREF color;
  5. }block[18][10];  // 我们将这个游戏区域格式化为 18*10 (row*col);

        那么画一个物体又该如何画呢?
        假如我们先用数组定义好物体的形状,然后移动变化,那就太麻烦了.可以看到这些物体都是更小的方块组成的.(我们用四个小方块组成一个大的物体).用四个小方块,通过相对位置的不同可以变化成各种各样的不同形状.
我们最终是要在那个二维数组中画不同形状的物体.那么在画的时候就要直到当前的物体应该画到哪儿.假如我们对二维数组的每个元素再抽象出来一个数据结构.在这个数据结构中保存当前它在二维数组中的位置(行列值).那就容易多了.


 

  1. typedef struct tagPane
  2. {
  3.  int row; // 在block[18][10]的行
  4.  int col;  // 列
  5. }Pane; 

这是一个小方格的定义,那每个物体要四个小方格,于是

  1. struct tagObject
  2. {
  3.  Pane object[4];
  4.  COLORREF color; // 每个物体有自己的颜色
  5.   int t; // 对应于最上面的一个方块的行值row
  6.   int b; // 对应于最下面一个方块行值
  7.   int l; // 对应于最左面一个方块的列值col
  8.   int r; // 对应于最右面一个方块列值
  9. };

由于我们要在画一个当前的下落的物体和一个下次将出现的物体.因此定义两个物体的对象

  1. tagObject Object,NextObject;

每个物体都是有边界的(把四个小方格全部盛下的最小矩形边界),我们把这个物体的四个小方块当前在block中的行列值,记录下来作为边界,放置在 tblr中.下面很多地方是用到这几个值的

我们规定每个小方块的实际大小是 (24*24)像素
那这个游戏区域实际上就是 height = 18*24 ,width = 10*24大小的矩形内(要注意数组的行列和矩形宽高的对应,col 对应的是width, row 对应的是height)

 

  1. // 定义方格大小和游戏区域的矩形
  2. const int PANE_WIDTH = 24; // 每个方格大小时24 * 24 pixel
  3. const CRect BLOCK_RECT = CRect(29,31,271,463); // 游戏区域矩形

现在的问题是,你怎么知道具体应该在视图的哪个地方画这些小方块呢?
太简单了.我们可以由行列值(row,col)立即得到它在视图中所对应的矩形区域
CRect(col*PANE_WIDTH,row*PANE_WIDTH,(col+1)*PANE_WIDTH,(row+1)*PANE_WIDTH);
假如我们 要画一个 物体,物体的四个方块的行列值都知道的.比如是在二维数组中
(<0,0> <0,1> <1,0> <1,1>) 那
<0,0>对应的视图的座标矩形是(0,0,24,24)
<0,1> -----(24,0,48,24)
....
自己那么对比几下就明白了.
可以写一个函数专门获取小方块对应的实际视图中的矩形区域(实际上,我们的游戏区域不是从视图的0,0开始画的,而是上和左都有空白,左边空白LEFT_MARGIN个像素,上边空白是TOP_MARGIN个像素,因此是以(LEFT_MARGIN,TOP_MARGIN)点处作为游戏区域的左上角 .方块的长和宽相等,定义为PANE_WIDTH = 24)

 

  1. // 根据座标取得客户区矩形
  2. CRect CRBlock::GetPaneRect(int row, int col)
  3. {
  4.     if (row < 0 || col < 0)
  5.     {
  6.         return CRect(0,0,0,0);
  7.     }
  8.     return CRect(col*PANE_WIDTH+LEFT_MARGIN,row*PANE_WIDTH+TOP_MARGIN,
  9.         (col+1)*PANE_WIDTH+LEFT_MARGIN,(row+1)*PANE_WIDTH+TOP_MARGIN);
  10. }
  11. //为方便.给出另一个重载的版本
  12. // 由Pane的座标取得对应的客户区的矩形
  13. CRect CRBlock::GetPaneRect(Pane *p)
  14. {
  15.     return GetPaneRect(p->row,p->col);
  16. }

现在再绘制一个物体还难么???张飞吃豆芽.!

  1. #define RANDOM_COLOR RGB(rand()%256,rand()%256,rand()%256)
  2. // 绘制当前下落的物体
  3. void CRBlock::DrawFallingObject(CDC *pDC)
  4. {
  5.     CBrush brush(Object.color);
  6.     CBrush brBorder(RANDOM_COLOR);
  7.     CBrush *pOld = pDC->SelectObject(&brush);
  8.     for (int i = 0; i < 4; i++)
  9.     {
  10.         CRect rc = GetPaneRect(&(Object.object[i]));
  11.         if (!rc.IsRectNull())  //这个组成物体的小方块 还未出现在游戏区域内
  12.         {
  13.             pDC->Rectangle(&rc);
  14.             pDC->FrameRect(&rc,&brBorder); // 绘制边界,看起来会漂亮点哦.
  15.         }
  16.     }
  17.     pDC->SelectObject(pOld);
  18. }

咦.郁闷了...吃豆芽的时候遇到点problem.....

需要绘制下一个将出现的物体.这个物体不是绘制在游戏区域内的.而是在游戏区域的右侧.

没关系.假如我们在游戏区域和这下个物体绘制的区域之间隔一个 方块的宽度.所有的问题都又解决了,原来的函数还照样可以用... 

 ...继续吃豆芽

 

  1. const CRect RECT_NEXTOBJECT = CRect(294,30,438,174); // 在这个矩形局域画下一个物体 4*6 *PANE_WIDTH 大小
  2.                             //294 = LEFT_MARGIN + 10 * PANE_WIDTH + 1*PANE_WIDTH; 
  3. 现在这个4*6的区域的 左上角,即是第一个方格的列座标就是  10+1 = 11 了.至于行座标,由于这个区域是和原来的游戏区域上对齐的,所以依然是0;
  4. // 绘制下一个将出现的物体
  5. void CRBlock::DrawNextObject(CDC *pDC)
  6. {
  7.     int l = NextObject.l;
  8.     int t = NextObject.t;
  9.     int r = NextObject.r;
  10.     int b = NextObject.b;
  11.     
  12.     // 把 NextObject 的行列映射到 要画在的矩形中
  13.     int offsetRow = (0+(6-(b-t+1))/2)-t;     /// 这只是要把物体画到这个区域的中心
  14.     int offsetCol = (11+(6-(r-l+1))/2)-l; // 
  15.     
  16.     // 这一段只是美化
  17.     CBrush brBkgnd(COLOR_BKGND);
  18.     CBrush *pOld = pDC->SelectObject(&brBkgnd);
  19.     CPen pen;
  20.     pen.CreatePen(PS_SOLID,1,RANDOM_COLOR);
  21.     CPen *pOldPen = pDC->SelectObject(&pen);
  22.     pDC->Rectangle(&RECT_NEXTOBJECT);
  23.     pDC->SelectObject(pOldPen);
  24.     pDC->SelectObject(pOld);
  25.     
  26.     CBrush brush(NextObject.color);
  27.     CBrush brBorder(RANDOM_COLOR);
  28.     pDC->SelectObject(&brush);
  29.     // 画下一个物体
  30.     for (int i = 0; i < 4; i++)
  31.     {
  32.         int row = NextObject.object[i].row;
  33.         int col = NextObject.object[i].col;
  34.         CRect rc = GetPaneRect(row+offsetRow,col+offsetCol);
  35.         if (!rc.IsRectNull())
  36.         {
  37.             pDC->Rectangle(&rc);
  38.             pDC->FrameRect(&rc,&brBorder);
  39.         }
  40.     }
  41. }

好了.万事具备了.下次讲如何生成不同形状的物体物体

编程群C,C++,MFC,Java 58698324.正招人.欢迎加入

发布了39 篇原创文章 · 获赞 6 · 访问量 13万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章