首页 > 代码库 > MFC实现2048游戏(二)

MFC实现2048游戏(二)

上一篇,主要介绍了UI部分,其实根本没有UI,自己做这个游戏也是就是实现一下逻辑功能,其实游戏的逻辑是最难的,UI谁都可以学会,逻辑却是需要理解的!

主要的逻辑:

选择了二维数组 与 双端队列(deque);因为双端队列(queue)可以操作[]下标,用起来比较方便:

int tempArray[Count][Count];

memcpy(tempArray,m_nArray,sizeof(int)*Count*Count);

deque<int> lst[Count];//声明一个deque<int>类型的数组 数组长度为4,也就是4个deque<int>

首先界面的初始化:

//重置,重新开始界面初始化void CMy2048Dlg::Reset(void){	for(int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			m_nArray[i][j] = 0;		}	}	RandonNum();	RandonNum();}

这个没有什么,RandNum()是随机生成数字的函数

 

向下为例子:我们要用 双端队列数组lst 对二维数组进行分组:
 原先的数组  00 01 02 03
                       10 11 12 13
                       20 21 22 23
                       30 31 32 33
 分组后   第一个队列:lst[0] : 00 10 20 30
                第二个队列:lst[1] : 01 11 21 31
                第三个队列:lst[2] : 02 21 22 32
                第四个队列:lst[3] : 03 13 23 33

 所以分组后:
 lst[0][0] = tempArray[0][0];
 lst[0][1] = tempArray[1][0];
 lst[0][2] = tempArray[2][0];

 上面的分析只是相对假设的情况,实际上不是如此 因为分组有个条件:m_nArray[i][j] != 0
 所以它只是会对不是0的数据进行分组


 现在分析一个情况: 原先数组的第一列为   4
                                                                              2
                                                                              0
                                                                              0
 那么分组后 其实 lst[0] 只要两个数据: lst[0][0]: 4; lst[0][1]:2;

我们看分组的代码实现:

int tempArray[Count][Count];	memcpy(tempArray,m_nArray,sizeof(int)*Count*Count);	deque<int> lst[Count];//声明一个deque<int>类型的数组 数组长度为4,也就是4个deque<int>	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] != 0)			{				lst[j].push_front(m_nArray[i][j]);			}		}	}

所以分组后:lst[0] 只要两个数据: lst[0][0]: 4; lst[0][1]:2;

原先数组是 4 2 0 0 ,那我们按向下的按钮它应该变为 0 0 4 2:

在这之间我们要对 lst里面的数据进行 加的处理 和 反转的处理:

//实现累加void CMy2048Dlg::Add( deque<int> &list){	for(int i = 0; i < list.size(); i++)	{		if (list[i] == 0)			continue;		for (int j = i+1; j < list.size(); j++)		{			if (list[i] == list[j])			{				list[i] *= 2;				list[j]  = 0;				break;			}			//如果当前的值不为0  跳出内循环,没必要再比较例如:  4 2 2 ; 当 list[0]=4,list[1]=2			//list[1] = 2 != 0,直接跳出循环, 没必要再比较 list[2]与 list[0]			if (list[j] != 0) 			{				break;			}					}		}	//重新排列顺序,不足补0;	//例如 4 2  经过上面的代码其实没有任何变化累加,但是累加最后的结果要变为  4 2 0 0  	deque<int> tempLst;	for (int i = 0; i < list.size(); i++)	{		//双端队列支持对元素的下标访问		if (list[i] != 0)		{			tempLst.push_back(list[i]);		}	}	list = tempLst; //这个时候 list的元素为: 4 2 ,所以下面要补0	for (int i = list.size(); i < Count; i++ )	{		list.push_back(0); //list 的元素为: 4 2 0 0 	}}
//反转数据void CMy2048Dlg::ReservedLst(deque<int> &lst){	deque<int> tempLst;	while (lst.size() != 0)	{		tempLst.push_back(lst.back());		lst.pop_back();	}	lst = tempLst; //反转后的list元素为 0 0 2 4}


再看向下的代码:

bool CMy2048Dlg::Down(void){	int tempArray[Count][Count];	memcpy(tempArray,m_nArray,sizeof(int)*Count*Count);	deque<int> lst[Count];//声明一个deque<int>类型的数组 数组长度为4,也就是4个deque<int>	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] != 0)			{				lst[j].push_front(m_nArray[i][j]);			}		}	}	//累加	for (int i = 0; i < Count; i++)	{		Add(lst[i]); //这里假设对于 lst[0];相加后为: lst[0][0]: 4; lst[0][1]:2;没有变化这种情况		//因为相加的时候是 以lst[0][0], lst[1][0]为基准,所以这里向下相加后,就要反转一下		ReservedLst(lst[i]);	}	//重新赋值	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			m_nArray[i][j] = lst[j][i]; //注意这里是 lst[j][i] 赋值给 m_nArray[i][j]		}	}	//检测变动	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] != tempArray[i][j])			{				return true;			}		}	}	return false;}


这个过程用实例演示:

比如现在 第一列是   2

                                     2

                                     0

                                     0

那么它经过分组后  lst[0]:  有两个数据: lst[0][0]为 2  lst[0][1]  为2

Add(lst[0]);  经过Add后  lst 首先变为  4 0,  再变为 4 0 0 0

ReservedLst(lst[i]);经过ReservedLst后 lst 变为  0 0 0 4;

达到我们的效果!

 

向上,向左,向右的代码基本一样:

有两个地方需要注意:

向下 与 向右  分组的时候  是前端插入队列:

for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] != 0)			{				lst[j].push_front(m_nArray[i][j]);			}		}	}

向上 与 向左 分组的时候 是后端插入队列:

for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] != 0)			{				lst[i].push_back(m_nArray[i][j]);			}		}	}

至于为什么两者不一样,主要是因为反转那里的原因!

 

最后就是结束的逻辑,
只要两两都不相同,游戏就结束了:

//处理游戏结束的逻辑:bool CMy2048Dlg::CalcEnd(void){	//首先处理横轴:	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] == 0)			{				return false;			}			if (j+1 == Count)			{				break;			}			if (m_nArray[i][j] == m_nArray[i][j+1])			{				return false;			}		}	}	//处理纵轴	for (int i = 0; i < Count; i++)	{		for (int j = 0; j < Count; j++)		{			if (m_nArray[i][j] == 0)			{				return false;			}			if (i+1 == Count)			{				break;			}			if (m_nArray[i][j] == m_nArray[i+1][j])			{				return false;			}		}	}	return true;}


游戏面盘的设计:就是把一个客户区划分为16个小块,没个小块对应每一个数组元素的数字显示:

else //初始化游戏面盘	{		CRect rect;		GetClientRect(rect);//获取窗口客户区的坐标 客户区坐标是相对窗口客户区的左上角而言的,因此左上角坐标为(0,0)。		rect.left   += 5;		rect.right  -= 5;		rect.top    += 5;		rect.bottom -= 5;		CPaintDC dc(this);		int nWidth  = rect.Width()/Count; //每个单元格的宽度		int nHeight = rect.Height()/Count;//每个单元格的高度		CFont font;		font.CreatePointFont(nHeight*5, _T("Arial"),&dc); //创建字体和大小		CFont* oldFont = dc.SelectObject(&font);//保存旧的字体		dc.SetBkMode(TRANSPARENT);//设置背景颜色为透明		for(int i = 0; i < Count; i++)		{			for (int j = 0; j < Count; j++)			{				CRect rectChild;				rectChild.left   = rect.left + j*nWidth;				rectChild.top    = rect.top  + i*nHeight;				rectChild.right  = rectChild.left + nWidth;				rectChild.bottom = rectChild.top  + nHeight;				dc.Draw3dRect(rectChild,RGB(255,0,0),RGB(255,0,0));				if (m_nArray[i][j] != 0)				{					CString  strText;					strText.Format(_T("%d"),m_nArray[i][j]);					//该函数的功能是在指定的矩形里写入格式化文本,根据指定的方法对文本格式化					//(扩展的制表符,字符对齐、折行等)					dc.DrawText(strText,rectChild,DT_CENTER|DT_VCENTER);				}			}		}		CDialogEx::OnPaint();		dc.SelectObject(oldFont);//选则回原来的字体		font.DeleteObject();//删除字体	}


然后处理键盘消息;键盘消息的截获要重载PreTranslateMessage()函数:

//对键盘消息进行截获BOOL CMy2048Dlg::PreTranslateMessage(MSG* pMsg){	bool bRet = false;	if (pMsg->message == WM_KEYDOWN)	{		switch (pMsg->wParam)		{		case VK_DOWN:			bRet = Down();			break;		case VK_UP:			bRet = Up();			break;		case VK_LEFT:			bRet = Left();			break;		case VK_RIGHT:			bRet = Right();			break;		default:			break;		}		if (CalcEnd())		{			AfxMessageBox(_T("GameOver!"));			Reset();		}		else		{			if (bRet)			{				RandonNum();				Invalidate(TRUE);				if (CalcEnd())				{					AfxMessageBox(_T("GameOver!"));					Reset();				}			}		}		Invalidate(TRUE);	}		return CDialogEx::PreTranslateMessage(pMsg);}


 

感觉用文字解释还是真的比较难,就是为自己梳理了下思路:

源码下载地址:点击打开链接;大神勿看,写的差技术分享 ;新手可以看看一起学习!文字解释比较难解释,还是看代码慢慢理解!



 


 

MFC实现2048游戏(二)