摘 要:在俄罗斯方块游戏编程过程中,二维数组起到了至关重要的作用,我们通过控制二维数组元素的值来达到控制方块的目的,在游戏中的障碍判断、行满判断及其各种形状方块的左右移动,实际上都需要借助于它。本文借俄罗斯方块游戏编程来指导大家对二维数组的学习和应用。
关键词:二维数组;坐标;位图(DIB)资源
俄罗斯方块是大家都很熟悉的游戏,在其Windows应用程序的开发过程中,总的来说需要解决几个关键性的问题:其一是如何实现各个方块图案形状之间的转换;其二是在方块图案下落时如何判断下方有无障碍;其三就是如何判断一行是否填满及其消除的办法。这些功能都需要采用一定的算法来实现。
在程序的开发过程中,我们采用了二维数组的办法来控制整个方框的状态,从而比较容易理解的解决了上述的三个主要问题。此应用程序是采用Microsoft Visual C++程序语言在Windows 98及Windows XP上运行成功,下面就详细介绍二维数组在程序中的应用方法。
首先,我们需要分析方块图案的种类及其状态的问题。典型的也是最常见的俄罗斯方块游戏中共有7类图案,每一类图案又有若干变形的状态。为了让大家直观了解,所有图案和变形状态如图一所示。
图一
在某一类图案下落的过程中,我们可以通过键盘控制此类图案各状态间的转换。由于方块是在Windows的“可视”窗口中显示出来的,因此我们还需要调用位图(DIB)资源。实际上在Windows操作系统中每一个位图资源都是以矩形的形式定义的(即定义位图资源的宽和高)。如果对每一类图案的每一个变形状态都用不同的位图来表示,则由图一可知总共要定义19幅位图,这样不但增加了绘制位图的工作量,而且在变形状态间转换时容易引起闪烁和位置抖动。而更为麻烦的是它非常不利于下落时障碍判断和行是否填满状态的判断。考虑到每一种图案实际上是由四个大小完全相同的小方块所组成。四个这样的小方块通过不同的位置组合就可以形成上述的七大类19种状态的图案。因此实际上我们只需要绘制一幅小方块的DIB位图。由于连续调用四次位图的时间很短,不会超过人眼睛的视觉停留时间,因此我们看上去是一幅完整的位图。
我们把各个小方块的左上角坐标分别用(cx1,cy1),(cx2,cy2),(cx3,cy3),(cx4,cy4)来代表,程序中控制这些坐标之间的关系就相当于控制了小方块组合的形状,图二就表明了第三类图案的变形状态转换情况。
图二
下面来定义游戏窗口,我们假定游戏的左上角坐标点为(ulPosx,ulPosy),边框宽度定义为frmwidth,边框内的宽和高分别是16mWidth和20mHeight。其中mWidth和mHeight分别是小方块的宽和高,我们通常设定两者相等。我们可以理解,在功能实现上,可以看做是先擦除图案,往下移动一个mHeight距离后再将图案显示出来的过程。若移动一个mHeight距离后,任一个小方块的左上角纵坐标都没有超出方框底线,并且任一小方块当前所处位置所对应的数组元素都不等于1,就说明当前图案没有到底,也没有遇到障碍物,这是我们可以直接将其显示出来;否则,在显示之前应当向上移动一个mHeight距离,即取消刚才所做的距离移动。在这里擦除图案和显示图案分别用函数EraseDesign()和PaintDesign()来实现;函数CalcStatus()的作用是计算public变量i1-i4、j1-j4的值,这里擦除图案和显示图案分别用函数EraseDesign()和PaintDesign()来实现。函数Calcstatus()的作用是计算public变量i1到i4、j1到j4的值,程序代码如下:
Void CBlocks View::OnTimer(UINT nIDEvent)
{EraseDesign();
cy1+=mHeight; cy2+=mHeight; cy3+=mHeight; cy4+=mHeight;
CalcStatus();
if (cy1>(ulPosy+frmwidth+19*mHeight)︱︱cy2>(ulPosy+frmwidth+19*mHeight)︱︱
cy3>(ulPosy+frmwidth+19*mHeight)︱︱cy4>(ulPosy+frmwidth+19*mHeight)︱︱
(array=1;}
else
PaintDesign();}
void CBlocksView:: EraseDesign()//擦除方块
{CClientDC dc(this);
CBrush whiteBrush(RGB(255,255,255)),
CRect rect1(cx1,cy1,cx1+mWidth,cy1+mHeight); CRect rect2(cx2,cy2,cx2+mWidth,cy2+mHeight);
CRect rect3(cx3,cy3,cx3+mWidth,cy3+mHeight); CRect rect4(cx4,cy4,cx4+mWidth,cy4+mHeight);
dc.FillRect(&rect1,&whiteBrush); dc.FillRect(&rect2,&whiteBrush); dc.FillRect(&rect3,&whiteBrush); dc.FillRect(&rect4,&whiteBrush);}
void CBlocksView:: PaintDesign()//绘制方块
{ CClientDC dc(this);CDC ppDC;CBitmap Bmp;Bmp.LoadBitmap(ID-101);ppDC,CreateCompatibleDC(&dc);
CBitmap*m-OldBmp=ppDC.SelectObject(&Bmp);dc.BitBlt(cx1,cy1,mWidth,mHeight,&ppDC,0,0,SRCCOPY);
dc.BitBlt(cx2,cy2,mWidth,mHeight,&ppDC,0,0,SRCCOPY); dc.BitBlt(cx3,cy3,mWidth,mHeight,&ppDC,0,0,SRCCOPY); dc.BitBlt(cx4,cy4,mWidth,mHeight,&ppDC,0,0,SRCCOPY);
ppDC.SelectObject(m-OldBap);}
void CBlocksView::CalcStatus()//方块坐标->行列数
{i1=(cy1-ulPosy-frmwidth)/mHeight;j1=( cx1-ulPosx-frmwidth)/mWidth;同样可得i2-i4,j2-j4。
有了二维数组的概念以后,我们很容易进行一行填满与否的判断,只要扫描二维数组的各元素,如果一行的所有元素都等于1,则此行填满,予以消除,然后将此行以上各行状态依次下移,注意:如果相邻两行都填满时,要多处理一次(程序中的i++很重要)代码如下:
void CBlocksView::DetectFill()//判断此行是否填满
{ CClientDC dc(this); CBrush whiteBrush(RGB(255,255,255)), ;CDC ppDC; CBitmap Bmp; Bmp.LoadBitmap
(ID-101);ppDC,CreateCompatibleDC(&dc); CBitmap*m-OldBmp=ppDC.SelectObject(&Bmp);
for(int i=19;i>=0;i--){
BOOL IsFilled=TURE;for(int j=0;j<=15;j++)if(array 《C语言教程》,谭浩强 主编,清华大学出版社