今天学习一下俄罗斯方块项目的基本功能,首先对俄罗斯方块程序进行用例分析。
由用例分析,首先对下落的功能具体分析,并画出对应的逻辑图,俄罗斯方块分为下落的方块与下落停下的方块,这里我们将两者分为落块与实块;落块下落时,如果不被其他方块阻挡并且不出界,会一直下落直到结束;如果下落时被其他方块阻挡或者出界时,就会变成实块;实块包括合并、消除行、计算得分功能,最后判断是否结束,若结束则game over,若未结束则出新的落块。
功能逻辑图如下所示:
然后对左移动进行具体功能分析,判断左是否出界,若出界游戏结束,若不出界则判断是否碰到左侧实块,若碰到则结束,若未碰到继续左移动,具体如图所示:
右移动判断跟左移动一样,这里就用再画了。
接下来我们进行编程,首先打开V-S软件,建立一个RussianBlock的空项目,在头文件建立6个.h文件,分别为all.h文件用于存放全部的头文件、datatype.h文件用于进行类型的变换、gloablvar.h文件用于存放全局变量、luokuai.h文件用于描述落块的基本功能、shikuai.h用于描述实块的基本功能、game.h用于描述游戏界面得分等级等功能。
all.h文件存放一些头文件,主要为了下面程序执行时更方便,只用调用all.h文件即可,不用调用每一个头文件。
datatype.h文件宏定义TRUE为1FALSE为0,将int类型用_BOOL_类型替换,枚举一下方块的形状,用TYPE替换。
gloablvar.h文件定义一些程序要用到的全局变量。
luokuai.h文件用于描述落块的基本功能。
shikuai.h用于描述实块的基本功能。
game.h用于描述游戏界面得分等级等功能。
下面我首先具体编写一下落块的功能,在luokuai.h文件中鼠标放在函数名上点击快速创建luokuai.cpp文件,luokuai.cpp文件头要调用“all.h”文件。
第一步:编写显示落块函数,在showLuoKuai函数中利用双循环遍历落块,若落块不为0则显示落块,代码如下:
/*显示落块*/
void showLuoKuai()
{
for (int h = 0; h < 20; h++)
{
for (int l = 0; l < 10; l++)
{
if (luoKuai[h][l] != 0)
{
drawImage(30 + l * 30, 5 + h * 30, &imgfk[luoKuai[h][l]]);
}
}
}
}
第二步:编写出新的落块函数功能,在createNewLuoKuai函数中编写方块的种类、颜色都是随机的,然后用switch选择函数进行赋色,这里利用二维数组让同一类型的方块颜色相同,具体实现代码如下所示:
void createNewLuoKuai()
{
//1方块种类:随机
TYPE type = (TYPE)(rand() % 5);
//2方块颜色:随机
color = rand() % 10 + 1;//随机颜色
switch (type)
{
case Z:
luoKuai[0][3] = luoKuai[0][4] = color;
luoKuai[1][4] = luoKuai[1][5] = color;
break;
case T:
luoKuai[0][3] = luoKuai[0][4] = luoKuai[0][5] = color;
luoKuai[1][4] = color;
break;
case TIAN:
luoKuai[0][3] = luoKuai[0][4] = color;
luoKuai[1][3] = luoKuai[1][4] = color;
break;
case L:
luoKuai[0][3] = color;
luoKuai[1][3] = color;
luoKuai[2][3] = luoKuai[2][4] = color;
break;
case ONE:
luoKuai[0][3] = color;
luoKuai[1][3] = color;
luoKuai[2][3] = color;
luoKuai[3][3] = color;
break;
}
}
第三步:编写向下移动函数功能,在downMove函数中编写向下移动函数,本质就是在行和列的双循环内将数组上面一行的数据复制给下面一行,然后把上面一行的数据清零,代码如下所示:
/*下移动*/
void downMove()
{
for (int h=18 ;h>=0 ;h--)
{
for (int k=0 ;k<10 ;k++)
{
luoKuai[h + 1][k] = luoKuai[h][k];
luoKuai[h][k] = 0;
}
}
}
第四步:编写下不出界函数,在downNotOut函数中利用for循环遍历数组的10列,判断第20行的每一列是否为0,若为不为0则返回FALSE,若为0则返回TRUE;代码如下所示:
/*下不出界 */
_BOOL_ downNotOut()
{
for (int k=0 ;k<10 ;k++)
{
if (luoKuai[19][k]!=0)
{
return FALSE;
}
}
return TRUE;
}
第五步:编写下不阻挡函数,在downNotStop函数中利用循环套循环遍历落块与实块每一行每一列是否相同,若相同则返回FALSE,若不同则返回TRUE,代码如下所示:
/*下不阻挡 */
_BOOL_ downNotStop()
{
for (int h=18 ;h>=0 ;h--)
{
for (int k=0 ;k<10 ;k++)
{
if (luoKuai[h][k] && shiKuai[h+1][k])
{
return FALSE;
}
}
}
return TRUE;
}
第六步:编写左移动函数,在leftMove函数中使用双循环遍历落块的行与列,让落块同行的右一列复制给左一列,并把右列清零,代码如下:
/*左移动*/
void leftMove()
{
for (int k=1 ;k<10 ;k++)
{
for (int h=0 ;h<20 ;h++)
{
luoKuai[h][k - 1] = luoKuai[h][k];
luoKuai[h][k] = 0;
}
}
}
第七步:编写左不出界函数,在leftNotOut函数中利用循环遍历每一行的第0列是否存在,若存在则返回FALSE,若不存在则返回TRUE,代码如下:
/*左不出界 */
_BOOL_ leftNotOut()
{
for (int h=0 ;h<20 ;h++)
{
if (luoKuai[h][0])
{
return FALSE;
}
}
return TRUE;
}
第八步:编写左不阻挡函数,在leftNotStop函数中利用双循环遍历每行与列,判断落块所在的行和列与实块所在的行和左一列是否同时存在,若存在则返回FALSE,不同时存在返回TRUE,代码如下:
/*左不阻挡 */
_BOOL_ leftNotStop()
{
for (int k = 1; k < 10; k++)
{
for (int h = 0; h < 20; h++)
{
if (luoKuai[h][k] && shiKuai[h][k-1])
{
return FALSE;
}
}
}
return TRUE;
}
第九步:分别编写右移动、右不出界、右不阻挡函数,方法与左侧一样,这里不具体分析,代码如下:
/*右不出界 */
_BOOL_ rightNotOut()
{
for (int h = 0; h < 20; h++)
{
if (luoKuai[h][9])
{
return FALSE;
}
}
return TRUE;
}
/*右不阻挡 */
_BOOL_ rightNotStop()
{
for (int k = 8; k >= 0; k--)
{
for (int h = 0; h < 20; h++)
{
if (luoKuai[h][k] && shiKuai[h][k+1])
{
return FALSE;
}
}
}
return TRUE;
}
/*右移动*/
void rightMove()
{
for (int k = 8; k >=0; k--)
{
for (int h = 0; h < 20; h++)
{
luoKuai[h][k + 1] = luoKuai[h][k];
luoKuai[h][k] = 0;
}
}
}
其次编写一下实块的功能,在shikuai.h文件中鼠标放在函数名上点击快速创建shikuai.cpp文件,shikuai.cpp文件头要调用“all.h”文件。
第一步:编写显示实块函数,代码如下:
/*显示实块*/
void showShiKuai()
{
for (int h = 0; h < 20; h++)
{
for (int l = 0; l < 10; l++)
{
if (shiKuai[h][l] != 0)
{
drawImage(30 + l * 30, 5 + h * 30, &imgfk[shiKuai[h][l]]);
}
}
}
}
第二步:编写合并函数,在merge函数中循环遍历每一行与列,如果落块存在,就把落块复制给实块,然后清空落块,代码如下:
/*合并*/
void merge()
{
for (int h=0 ;h<20 ;h++)
{
for (int k = 0; k < 10; k++)
{
if (luoKuai[h][k])
{
shiKuai[h][k] = luoKuai[h][k];
luoKuai[h][k] = 0;
}
}
}
}
第三步:编写清空行函数,在cleanLines函数中首先遍历所有行,其次看哪行可以清除,若能清除然后清除,最后记录行数,代码如下:
/* 清除行
返回:行数
*/
int cleanLines()
{
//1 遍历所有行
int count = 0;//记录消除的行数
for (int h=1; h<20; h++)
{
//2 看哪行能消除
_BOOL_ neng = TRUE;
for (int k = 0; k < 10; k++)
{
if (shiKuai[h][k]==0)
{
neng = FALSE; //推翻假设
break;
}
}
if (neng)
{
//3 消除
for (int xh = h - 1; xh >= 0; xh--)
{
for (int xk = 0; xk < 10; xk++)
{
shiKuai[xh + 1][xk] = shiKuai[xh][xk];
shiKuai[xh][xk] = 0;
}
}
//4 记录行数
count++;
}
}
return count;
}
第四步:编写计算得分函数, 在setScore函数中使用switch函数进行选择,1行得20分,2行得50分,3行得100分,4行得200分,代码如下:
/*
根据消除的行数 计算得分
*/
void setScore(int lines)
{
switch (lines)
{
case 1:
score += 20;
break;
case 2:
score += 50;
break;
case 3:
score += 100;
break;
case 4:
score += 200;
break;
}
}
第五步:编写能否结束函数,在enabledOver函数中在0行3~6列这个范围是否有方块,若存在能结束,不存在继续游戏,代码如下:
/*能否结束*/
_BOOL_ enabledOver()
{
for (int k=3 ;k<7 ;k++)
{
if (shiKuai[0][k])
{
return TRUE;
}
}
return FALSE;
}
然后编写游戏界面函数,在game.h文件中鼠标放在函数名上点击快速创建game.cpp文件,game.cpp文件头要调用“all.h”文件。
第一步:编写游戏结束函数,在gameover函数中编写stop=1代表游戏结束,代码如下:
void gameover()
{
stop = 1;
}
第二步:编写背景初始化函数。
/*初始化背景*/
void initBg()
{
loadimage(&imgbg, L".\\images\\2\\bg-1.png");
}
第三步:编写显示背景函数。
/*显示背景*/
void showBg()
{
putimage(0, 0, &imgbg);
}
第四步:编写加载方块图函数。
/*加载方块图*/
void loadFKImage()
{
for (int c = 0; c < 11; c++)
{
_stprintf(imgPath, L".\\images\\2\\%d.png", c);
loadimage(&imgfk[c], imgPath);
}
}
第五步:编写显示得分函数。
void showScore()
{
settextstyle(18, 0, _T("黑体"));
settextcolor(0xffffff);
TCHAR str[100];
_stprintf(str, L"%d分", score);
outtextxy(390, 228, str);
}
第六步:编写显示等级函数。
void showLevel()
{
settextstyle(18, 0, _T("黑体"));
TCHAR str[100];
_stprintf(str, L"Level %d", 1);
outtextxy(390, 294, str);
}
最后在主函数ELSFKGame.cpp文件中调用上述函数功能就可以实现俄罗斯方块基本功能。
在头调用“all.h”文件。
第一步:游戏初始化函数,包括对背景初始化,加载方块图,创建新的落块函数的调用,代码如下:
void gameInit()
{
initBg(); /*初始化背景*/
loadFKImage();/*加载方块图*/
createNewLuoKuai();
}
第二步:绘图处理函数,包括对显示背景,显示落块,显示实块,显示得分和等级函数的调用,代码如下:
void gamePaint()
{
showBg();/*显示背景*/
//---------显示下落方块-------------//
showLuoKuai();
//---------显示落实方块-------------//
showShiKuai();
//---------显示 得分 等级-------------//
showScore();
showLevel();
}
第三步:定时处理函数,代码如下:
void gameInterval()
{
if (downNotOut() && downNotStop())
{
downMove();
}
else
{
merge();
setScore(cleanLines());
if (enabledOver())
{
gameover();
}
else
{
createNewLuoKuai();
}
}
}
第四步:键盘处理函数,代码如下:
void gameKeypress(int key)
{
switch (key)
{
case VK_LEFT:
if (leftNotOut() && leftNotStop())
{
leftMove();
}
break;
case VK_RIGHT:
if (rightNotOut() && rightNotStop())
{
rightMove();
}
break;
case VK_UP:
break;
case VK_DOWN:
if (downNotOut() && downNotStop())
{
downMove();
}
break;
}
}
以上就是俄罗斯方块的基本功能实现。