基于MFC实现的俄罗斯方块游戏
基于MFC实现的俄罗斯方块游戏
1.需求分析
需要实现的功能:
- 使用键盘方向键实现方块的控制和移动
- 程序能够对方块的位置进行判断,防止方块移动出界
- 当满足消行条件时,能够及时消去满足条件的行数,其他保持不变
- 实现加分和等级晋级,以及游戏难度增加等附加功能
2.设计分析
2.1 方块显示
可以采用二维数组来对方块进行控制变化和显示。1-代表有方块,0-无方块。如:
Now[4][4]={
1,1,1,0
0,1,0,0
0,0,0,0
0,0,0,0
};
2.2 背景数组
ArryBK[18][12]={0,0,…,0};
屏幕绘制采用双缓冲,现在内存绘制,最后一次性送到显存;
2.3 数据结构设计
定义结构体
struct shape
{
int ary[4][4]; //方块数组
CPoint pt; //起点(方块左上角),X-所在行,Y-所在列
};
定义类
class CRussia
{
public:
//构造函数,用来初始化数据成员
CRussia();
virtual ~CRussia();
//重新开始游戏
void GameAgain();
//判断游戏是否结束
bool IsOver();
//将当前形状数组附加到背景数组
void Attch();
//获得当前速度,用开控制游戏难度
int GetSpeed()const;
//在内存dc中画出当前图形
void DrawNow(CDC *);
//在内存dc中画出右边分数,等级,和下一图形
void DrawScores(CDC *);
//在内存dc画出背景
void DrawRussia(CDC *);
//消行
void DeleteLines();
//当用户按下方向键上时调用,用来变换矩阵
void InvertShape();
//用来产生随机矩阵图形
void RandShape();
//用来判断方向,当用户按下方向键,左(2),右(3),下(1)时调用
void Judge(int i=1);//默认判断方向为下
private:
bool m_bOver; //判断是否结束
int m_scores; //玩家得分
int m_speed; //时间间隔,用来设置定时器控制难度
int m_level; //玩家级数
shape Now; //当前矩阵图形
shape Will; //下一矩阵图形
int Russia[18][12]; //背景矩阵
CBitmap m_fangkuai; //方块位图
};
3.设计实现
游戏数据初始化
方块初始起点设置成第0行,第6列。
CRussia::CRussia()
{
m_level=1;
m_scores=0;
m_speed=600; //600ms间隔
m_bOver=false;
m_fangkuai.LoadBitmap(IDB_FANGKUAI);
int i,j;
for(i=0;i<18;i++)
for(j=0;j<12;j++)
{
Russia[i][j]=0; //背景数组清空
}
for (i=0;i<4;i++)
for(j=0;j<4;j++)
{
Now.ary[i][j]=0; //当前矩阵数组清空
Will.ary[i][j]=0; //下一矩阵数组清空
}
//当前矩阵图形初始化
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[1][0]=1;
Now.ary[2][0]=1;
Now.pt.x=0;
Now.pt.y=6;
//下一矩阵图形初始化
Will.ary[0][0]=1;
Will.ary[0][1]=1;
Will.ary[1][0]=1;
Will.ary[2][0]=1;
Will.pt.x=6;
Will.pt.y=0;
}
方块,背景及玩家信息屏幕绘制实现
当前方块图形绘制,由于Now.pt.x表示所在行数,Now.pt.y表示所在列数。正好与默认坐标映射模式相反,所以在BitBlt()函数的前2个参数的X,Y坐标时,调换了行和列的坐标值。
void CRussia::DrawNow(CDC *pDc)
{
// 创建内存dc
CDC memDc;
memDc.CreateCompatibleDC(pDc);
int nDC=memDc.SaveDC();
//初始化内存dc
memDc.SelectObject(m_fangkuai);
for (int i=0;i<4;i++)
for(int j=0;j<4;j++)
{
if (1==Now.ary[i][j])
{
//将方块图案绘制到缓冲内存dc中
pDc->BitBlt(30*(j+Now.pt.y),30*(i+Now.pt.x),30,30,&memDc,0,0,SRCCOPY);
}
}
memDc.RestoreDC(nDC);
}
背景数组绘制。
void CRussia::DrawRussia(CDC *pDc)
{
CDC memDc;
memDc.CreateCompatibleDC(pDc);
int nDC=memDc.SaveDC();
memDc.SelectObject(m_fangkuai);
for (int i=0;i<18;i++)
for(int j=0;j<12;j++)
{
if (1==Russia[i][j])
{
pDc->BitBlt(30*j,30*i,30,30,&memDc,0,0,SRCCOPY);
}
}
memDc.RestoreDC(nDC);
}
游戏附加信息绘制。
void CRussia::DrawScores(CDC *pDC)
{
CDC memDc;
memDc.CreateCompatibleDC(pDC);
int nDC=memDc.SaveDC();
memDc.SelectObject(m_fangkuai);
for (int i=0;i<4;i++)
for(int j=0;j<4;j++)
{
if (1==Will.ary[i][j])
{
pDC->BitBlt(30*(j+13),30*(i+8),30,30,&memDc,0,0,SRCCOPY);
}
}
memDc.RestoreDC(nDC);
int nOldDC=pDC->SaveDC();
CFont font;
font.CreatePointFont(300,"华文楷体");
pDC->SelectObject(&font);
CString str;
pDC->SetTextColor(RGB(20,255,0));
pDC->SetBkColor(RGB(255,255,0));
str.Format("%d",m_level);
pDC->TextOut(430,120,str);
str.Format("%d",m_scores);
pDC->TextOut(430,2,str);
pDC->RestoreDC(nOldDC);
}
键盘方向控制实现
默认方向向下,当方块向下移动时,遍历方块数组依次判断每个值为1的方块的正下方方块是否有方块阻挡,以及判断是否碰到底界,如果遇到阻挡或碰到底,则将当前方块数组附加到背景数组,并返回结束本次移动,否则Now.pt.x++方块起点的X值加1。
void CRussia::Judge(int i)
{
//1-下; 2-左 3-右
int j=0,k=0;
switch(i)
{
case 1:
for ( j=0;j<4;j++)
for( k=0;k<4;k++)
{
if (1==Now.ary[j][k])
{
//判断是否遇阻,以及是否遇到底界
if (1==Russia[Now.pt.x+j+1][Now.pt.y+k]||17==Now.pt.x+j)
{
if (0==Now.pt.x)
{
m_bOver=true;
MessageBeep(1);
}
Attch();
return;
}
}
}
Now.pt.x++;
break;
case 2:
for (j=0;j<4;j++)
for(k=0;k<4;k++)
{
if (1==Now.ary[j][k])
{
//判断是否遇阻,以及是否遇到左边界
if (1==Russia[Now.pt.x+j][Now.pt.y+k-1]||0==Now.pt.y)
{
MessageBeep(1);
return;
}
}
}
Now.pt.y--;
break;
case 3:
for (j=0;j<4;j++)
for(k=0;k<4;k++)
{
if (1==Now.ary[j][k])
{
//判断是否遇阻,以及是否遇到右边界
if (1==Russia[Now.pt.x+j][Now.pt.y+k+1]||11==Now.pt.y+k)
{
MessageBeep(1);
return;
}
}
}
Now.pt.y++;
break;
default:
break;
}
}
Attch()函数的实现,遍历当前方块数组,值为1的坐标赋值给背景数组对应的点。并判断是否可以消行。
void CRussia::Attch()
{
for (int i=0;i<4;i++)
for(int j=0;j<4;j++)
{
if (1==Now.ary[i][j])
{
Russia[Now.pt.x+i][Now.pt.y+j]=1;
}
}
DeleteLines();
RandShape();
}
DeleteLines()的实现,遍历背景数组,从第一行开始判断。并用nRet记录可消行的行数值。如果nRet不为0,则消行;
void CRussia::DeleteLines()
{
static int nflag=1;
int nRet=0;
bool bRet=true;
for (int i=0;i<18;i++)
{
for (int j=0;j<12;j++)
{
if(0==Russia[i][j])
{
bRet=false;
break;
}
}
//判断是否有一整行全部为1,即达到消行状态
if (bRet)
{
nRet++;
}
bRet=true;
}
//可消行数为1行
if (1==nRet)
{
m_scores+=1;
}
//可消行数为2行
if (2==nRet)
{
m_scores+=3;
}
//可消行数为3行
if (3==nRet)
{
m_scores+=5;
}
//可消行数为4行
if (4==nRet)
{
m_scores+=10;
}
if (m_scores>50*nflag) //加分,晋级,及难度控制
{
m_level+=1;
m_speed-=50;
nflag++;
}
if (nRet!=0)
{
bool bFlag=true;
for (int i=0;i<18;i++)
{
for (int j=0;j<12;j++)
{
if(0==Russia[i][j])
{
bFlag=false;
break;
}
}
//为了记录所在行数值i,采用一行一行消去
if (bFlag)
{
//可消行,i值记录了可消的行所在的行数
for(int m=0;m<i;m++)
for (int k=0;k<12;k++)
{
//第i行以上整体下移一行
Russia[i-m][k]=Russia[i-m-1][k];
}
}
bFlag=true;
}
}
}
方向上键控制,用来转换方块矩阵图形InvertShape()。
void CRussia::InvertShape()
{
int i=0,j=0,MaxV=0;
for ( i=0;i<4;i++)
for( j=0;j<4;j++)
{
Now.ary[i][j]=0; //清空
}
static int k=2;
switch(k)
{
case 0:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
break;
case 1:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[1][0]=1;
Now.ary[2][0]=1;
break;
case 2:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[1][1]=1;
Now.ary[2][1]=1;
break;
case 3:
Now.ary[0][0]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
Now.ary[1][2]=1;
break;
case 4:
Now.ary[0][2]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
Now.ary[1][2]=1;
break;
case 5:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[0][2]=1;
Now.ary[1][0]=1;
break;
case 6:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[0][2]=1;
Now.ary[1][2]=1;
break;
case 7:
Now.ary[0][1]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
Now.ary[2][0]=1;
break;
case 8:
Now.ary[0][1]=1;
Now.ary[0][2]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
break;
case 9:
Now.ary[0][1]=1;
Now.ary[1][0]=1;
Now.ary[1][1]=1;
Now.ary[1][2]=1;
break;
case 10:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[0][2]=1;
Now.ary[1][1]=1;
break;
case 11:
Now.ary[0][0]=1;
Now.ary[1][0]=1;
Now.ary[2][0]=1;
Now.ary[3][0]=1;
break;
case 12:
Now.ary[0][0]=1;
Now.ary[0][1]=1;
Now.ary[0][2]=1;
Now.ary[0][3]=1;
break;
}
k=++k%13;
//判断是否出右边界,出界则向左移动方块起点
for ( i=0;i<4;i++)
for( j=0;j<4;j++)
{
if (1==Now.ary[i][j])
{
if (12==Now.pt.y+j)
Now.pt.y-=1;
if (13==Now.pt.y+j)
Now.pt.y-=2;
if (14==Now.pt.y+j)
Now.pt.y-=3;
}
}
}
4.游戏测试
经多次测试,游戏可以按照设计文档的功能需求运行;