基于MFC实现的俄罗斯方块游戏

发布于:2024-11-29 ⋅ 阅读:(26) ⋅ 点赞:(0)

基于MFC实现的俄罗斯方块游戏

1.需求分析

需要实现的功能:

  • 使用键盘方向键实现方块的控制和移动
  • 程序能够对方块的位置进行判断,防止方块移动出界
  • 当满足消行条件时,能够及时消去满足条件的行数,其他保持不变
  • 实现加分和等级晋级,以及游戏难度增加等附加功能

2.设计分析

2.1 方块显示

可以采用二维数组来对方块进行控制变化和显示。1-代表有方块,0-无方块。如:

Now[4][4]=1110				
	0100
	0000
	0000
};

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.游戏测试

经多次测试,游戏可以按照设计文档的功能需求运行;