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);
}

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

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

时间: 2024-08-30 09:14:17

MFC实现2048游戏(二)的相关文章

MFC实现2048游戏(一)

MFC先实现2048游戏: 总共有两个场景,欢迎场景与游戏场景,在MFC下面,一个场景也就是一个对话框: 第一步: 实现欢迎场景: 很简单的插入一个对话框:防止两个按钮,一个进入游戏,一个退出游戏,其他界面上自己可以加载图片好看一点: 这个类为:CStartDlg 因为我们运行程序首先出来的是欢迎界面,所以在2048App当中要把主对话框改为 CStartDlg 第二步:实现游戏场景: 这里界面几乎没有设置,界面部分没有发时间去弄,就是简单的数组数字显示:主要是弄游戏逻辑,下一篇讲游戏的逻辑!

基于MFC对话框的2048游戏

在之前一篇<简单数字拼板游戏学习>基础上修改,地址:http://www.cnblogs.com/fwst/p/3706483.html 运行效果: (4 X 4) (7 X 7) (1)已完成 2048 游戏基本功能,需要解决的几个关键问题是 a. 首先是数据结构.先定义矩形类,然后定义矩形类对象的二维数组,长度由宏定义,可修改,即可自定义成N*N的游戏.这样游戏就是由N*N个矩形对象组成. b. 然后是游戏逻辑处理,也是最重要的一部分.方向键的响应.键盘上下左右四个方向键的逻辑一样,代码部

2048游戏完整源代码揭秘和下载 (二)

5.游戏滑动上下左右相加的逻辑 下面我们将来分析一下游戏2048滑动上下左右相加逻辑的实现.要实现这一功能,首先我们得监听触碰事件.本游戏中,我们是在 GameGroup 实现滑动事件的处理. (1) public GameGroup(GameScene pGameScene) { super(0, 0, 435, 435, pGameScene); // 设置可以监听触碰事件 this.setIgnoreTouch(false); this.mGameScene = pGameScene; i

2048游戏回顾二:算法总结(移动、合并、动画等)

如果只是单纯的写一个2048游戏,让这个游戏可以玩的话,工作量还是蛮小的.不过,在这写工作中,你可能花时间最多的就是数字的移动与合并的算法了,如果没有做过,可能确实要花点时间来构思,所以,写完2048游戏以后,我希望能把它做个记录. 移动与合并的算法 比如说我们有如下一个界面: 现在,玩家向左划,这个导致所有的数字向左移动,并且移动的过程中如果发生碰撞,会检查数字是不是可以合并. 我们的算法应该是通用的,不仅对于4*4模式,即便是针对3*3模式,n*n模式,它都应该是一样的. 那么怎么做呢?其实

2048游戏回想二:算法总结(移动、合并、动画等)

假设仅仅是单纯的写一个2048游戏.让这个游戏能够玩的话,工作量还是蛮小的.只是,在这写工作中,你可能花时间最多的就是数字的移动与合并的算法了,假设没有做过,可能确实要花点时间来构思,所以.写完2048游戏以后,我希望能把它做个记录. 移动与合并的算法 比方说我们有例如以下一个界面: 如今.玩家向左划.这个导致全部的数字向左移动,而且移动的过程中假设发生碰撞.会检查数字是不是能够合并. 我们的算法应该是通用的.不仅对于4*4模式,即便是针对3*3模式.n*n模式,它都应该是一样的. 那么怎么做呢

用javascript制作2048游戏的思路(原创若 转载请附上本链接)

一.项目已上传至github,地址:https://github.com/forjuan/2048game 二.学习了javascript基础后,想要捣鼓点东西做,做了一个自己以前很爱玩的2048游戏.经过初期的思路设计手工画了设计思路图.手工图有空在用图画出来. 实现2048的功能:1.核心:上下左右移动. 2.游戏开始,游戏结束. 3.外观实现. 4.积分(暂时没实现) 把问题分解:1.首要难点移动,将移动进行分解,分为上下左右移动: 2.用什么记录位置,怎样记录每个方块的值,边界,相同值相

2048游戏回顾一:使用SurfaceView创建游戏启动动画

SurfaceView有个很大的好处,就是可以在子线程中绘制UI,其他的View只能在主线程中更新UI,这或多或少给编程增加了些不便.而SurfaceVIew在子线程中可以绘制UI的特性,再加上其可以直接从内存或者DMA等硬件接口取得图像数据,这使得它适合2d游戏的开发. SurfaceView使用步骤 SurfaceView的使用比较简单,可以总结为如下几个步骤: 1.继承SurfaceView并实现 SurfaceHolder.Callback方法 譬如: public class Star

是男人就下100层【第五层】——2048游戏

前言: 在"阳光小强"的实战系列博文<是男人就下100层>的上一层我们一起从零开始完成了我们自己的贪吃蛇游戏--CrazySnake,可能很多朋友还不过瘾,那么我们今天就来玩一玩最近一直比较火的2048游戏,让大家再过一把瘾.由于"阳光小强"目前并没有从事Android的游戏开发工作,所以这些游戏的实现并不需要很专业的游戏开发知识,如果你有Android的基础就可以一起来参与进来共同完成这个游戏.有些朋友可能就会说"这些小游戏,会不会有点简单,

网页版《2048游戏》教程 - 游戏初始化

3.1.     初始化棋盘格 我们在main.js文件中,创建newgame()方法用于开始新的游戏.而开始新游戏需要完成两件事情,一是初始化棋盘格,一是在随机两个格子生成两个数字. $(function () { newgame(); }); function newgame() { // 初始化棋盘格 init(); // 在随机两个格子生成数字 generateOneNumber(); generateOneNumber(); } 我们通过编写init()方法来完成棋盘格的初始化工作.棋