在上俩篇博客的基础上,我们实现接下来的算法,卡片消除!整体思路是这样的,在卡片移动动作完成之后,我们来判断相邻卡片是否可以消除,这个时候得根据不同的手势进行不同的判断,以向左的手势为例吧,我们需要从左到右,从上到下的去判断相邻的卡片的数字是否相同,如果第一张卡片和右边相邻的第二张卡片的数字相同就可以消除,接下来继续判断。而判断这些卡片我们就要从卡片管理器中取出这些添加到棋盘中的卡片,他们添加的顺序当时是随机的,是不确定的,要完成手势向左的判断,先要做的就是对卡片管理器中的卡片进行排序,按照他们的位置,从左到右,从上到下的排序,然后对卡片管理器中已经排好序的卡片进行判断,判断是否可以消除,可以的话就调用消除函数。这个排序的算法使用的是c++中的sort函数,需要包含头文件algorithm,接下来就看看源代码中如何使用的吧!

void CubeManager::sortCubeArray()
{
		std::sort(m_cubeArray->data->arr,m_cubeArray->data->arr+m_cubeArray->data->num,compare);
}

//如果手指滑动的方向是左,则对cube进行排序的时候应该是从上往下,从左往右
bool CubeManager::compare(CCObject * obj_1,CCObject * obj_2)
{
	CCSprite * cube1 = (CCSprite *)obj_1;
	CCSprite * cube2 = (CCSprite *)obj_2;
	switch(m_type)
	{
	case 0:
		//从上到下,从左往右的排序
		if(cube1->getPosition().x < cube2->getPosition().x)
		{
			return true;
		}
		else if(cube1->getPosition().x == cube2->getPosition().x &&cube1->getPosition().y > cube2->getPosition().y)
		{
			return true;
		}
		break;
	case 1:
		if(cube1->getPosition().x < cube2->getPosition().x)
		{
			return true;
		}
		else if(cube1->getPosition().x == cube2->getPosition().x &&cube1->getPosition().y < cube2->getPosition().y)
		{
			return true;
		}
		break;
	case 2:
		if(cube1->getPosition().y > cube2->getPosition().y)
		{
			return true;
		}
		else if(cube1->getPosition().y == cube2->getPosition().y &&cube1->getPosition().x < cube2->getPosition().x)
		{
			return true;
		}
		break;
	case 3:
		//从上到下,从右往左进行排序
		if(cube1->getPosition().y > cube2->getPosition().y)
		{
			return true;
		}
		else if(cube1->getPosition().y == cube2->getPosition().y &&cube1->getPosition().x > cube2->getPosition().x)
		{
			return true;
		}
		break;
	}
	return false;
}

以上就是排序算法的实现,sort函数中需要传入的是数组的起始地址和和结束的地址,调用m_cubeArray->data返回的是一个ccArray的结构体,它有三个变量,arr代表的就是元素的起始地址,num代表元素的个数。sort中的第三个参数就是排序使用的函数,这个函数写在类中的时候应该声明为static,而static函数中的变量也应该是static,所以m_type也应该是static类型的。接下来看看消除函数怎么写吧。

//卡片消除
void CubeManager::flipOverCube()
{
	//先对精灵进行排序
	this->sortCubeArray();
	//保存卡片的数量
	int count = (int)m_cubeArray->count();
	//使用for循环遍历数组
	for(int i=0;i<count;i++)
	{
		//先取出第一个精灵对象
		CCSprite * cube_1 = (CCSprite *)m_cubeArray->objectAtIndex(i);
		CCPoint logicalPoint_1 = m_dealPoint.convertToLogicalPosition(cube_1->getPosition());
		//取出它后边的精灵对象,如果后边没有了精灵则返回
		if(i+1 >= (int)m_cubeArray->count())
			return;
		CCSprite * cube_2 = (CCSprite *)m_cubeArray->objectAtIndex(i+1);
		CCPoint logicalPoint_2 = m_dealPoint.convertToLogicalPosition(cube_2->getPosition());
		switch(m_type)
		{
		case 0:
			if(logicalPoint_1.x == logicalPoint_2.x && logicalPoint_1.y-1 == logicalPoint_2.y && cube_1->getTag() == cube_2->getTag())
			{
				this->clear(cube_1,logicalPoint_1,cube_2,logicalPoint_2);
				i--;
				count-=2;
			}
			break;
		case 1:
			if(logicalPoint_1.x == logicalPoint_2.x && logicalPoint_1.y+1 == logicalPoint_2.y && cube_1->getTag() == cube_2->getTag())
			{
				this->clear(cube_1,logicalPoint_1,cube_2,logicalPoint_2);
				i--;
				count-=2;
			}
			break;
		case 2:
			//如果满足以下的条件,则可以消除
			if(logicalPoint_1.y == logicalPoint_2.y && logicalPoint_1.x+1 == logicalPoint_2.x && cube_1->getTag() == cube_2->getTag())
			{
				this->clear(cube_1,logicalPoint_1,cube_2,logicalPoint_2);
				//由于动态的往卡片数组中添加和删除元素,导致count数量在改变,元素在数组中的下标也发生了改变
				//所以i--,一会再i++才代表下一个元素
				i--;
				//count减去消去的一个元素,减去新添加在末尾的元素,所以减2
				count-=2;
			}
			break;
		case 3:
			if(logicalPoint_1.y == logicalPoint_2.y && logicalPoint_1.x-1 == logicalPoint_2.x && cube_1->getTag() == cube_2->getTag())
			{
				this->clear(cube_1,logicalPoint_1,cube_2,logicalPoint_2);
				i--;
				count-=2;
			}
			break;
		}
	}
}
void CubeManager::clear(CCSprite * cube_1,CCPoint logicalPoint_1,CCSprite * cube_2,CCPoint logicalPoint_2)
{
	//保存tag
	int tag = cube_1->getTag();
	//清除cube_1、cube_2
	m_cubeArray->removeObject(cube_1);
	cube_1->removeFromParentAndCleanup(true);
	m_cubeArray->removeObject(cube_2);
	cube_2->removeFromParentAndCleanup(true);
	//在坐标管理器中回收cube_2的坐标
	GridPoint * gridPoint = GridPoint::create();
	gridPoint->m_point = logicalPoint_2;
	m_pointManager->getPointArray()->addObject(gridPoint);
	//添加一个新的卡片
	CCSprite * cube = this->addCubeToChess(2*tag,logicalPoint_1);
	//添加翻牌的动作
	CCOrbitCamera * orbit = CCOrbitCamera::create(0.2f, 1, 0, -90, 90, 0, 0);
	cube->runAction(orbit);
	//向chess类发送添加卡片到棋盘的消息
	CCNotificationCenter::sharedNotificationCenter()->postNotification(MESSAGE_TYPE_ADD_CUBE,cube);

}
//传入的point为逻辑坐标
CCSprite * CubeManager::addCubeToChess(int tag,CCPoint point)
{
	//(int)(log((float)tag)/log((float)2))是计算以2为底的对数
	CCString * string = CCString::createWithFormat("cube_%d.png",(int)(log((float)tag)/log((float)2)));
	//获得精灵对象
	CCSprite * sprite = CCSprite::createWithSpriteFrameName(string->getCString());
	//设置标签
	sprite->setTag(tag);
	//设置坐标
	sprite->setPosition(m_dealPoint.convertToActualPosition(point));
	//添加到卡片数组中
	m_cubeArray->addObject(sprite);

	return sprite;
}

这里重载了addCubeToChess函数,用来添加指定tag,指定位置的卡片。这里边涉及到的一些逻辑我的注释写的很清楚了,相信大家不难看懂吧。这样就完成了消除的动作,然后我们需要在call函数中做如下的修改。

//当棋盘格子中的卡片完成了自己的动作以后调用该函数,当num和卡片数量想同的时候代表可以接受下一次用户的触摸
//如果在卡片没有完成自己的动作的时候就接受触摸消息,会产生bug,导致坐标混乱
void CubeManager::call()
{
	static int num = 0;
	num++;
	//当所有的卡片移动完毕以后以下条件才会满足
	if(num == this->m_cubeArray->count())
	{
		//当所有的卡片都移动完毕以后设置卡片的移动状态为false
		this->m_bRun = false;
		num = 0;
		//所有的卡片移动完毕对相邻卡片进行消除
		this->flipOverCube();
		this->moveCube();
		//向chess发送添加卡片的消息
		CCNotificationCenter::sharedNotificationCenter()->postNotification(MESSAGE_TYPE_ADD_CUBE,
			this->addCubeToChess());
	}
}

大家会发现我在call中还调用了moveCube()这个函数,这个函数是做什么的呢?当然大家可以注释掉这句话,然后运行程序看下效果,会发现这样的现象,假如棋盘的第一排有三个2,我们给它一个向左的手势,最左边的俩个2会消除,第三个2移动到了(2,0)的位置,也就是他和消除获得的新的卡片4中间隔了一个格子,因为我们的逻辑是先移动所有的卡片,到卡片移动完成之后再判断相邻的卡片是否可以消除,然后随机的添加一个卡片到棋盘中。这样的话当然卡片是移动结束了,所以不会再次移动了,也就出现了上述的现象,所以我写了一个moveCube函数,再次移动一下卡片到手势的方向,让他们之间不出现空格,函数如下。

//卡片消除完毕以后对没有移动完成的卡片继续移动
void CubeManager::moveCube()
{
	//数组遍历的时候用到
	CCObject * obj;

	//重置坐标管理器中的坐标
	this->m_pointManager->initPointArray();

	//遍历数组,对数组中的卡片执行移动的动作
	CCARRAY_FOREACH(this->m_cubeArray,obj)
	{
		CCSprite * cube = (CCSprite *)obj;
		//getPosition获得的是节点坐标,这里是相对于棋盘图片的坐标
		CCPoint cubePoint = cube->getPosition();
		//转化为自己设置的逻辑坐标
		CCPoint logicalPoint = m_dealPoint.convertToLogicalPosition(cubePoint);
		//获得卡片左边的卡片数量,为移动做准备
		int numOfCube = this->getNumOfCube(logicalPoint,m_type);

		//根据不同的手势产生不同的移动动作
		CCMoveTo * move = NULL;
		//保存卡片将要移动到的逻辑位置
		CCPoint movePoint;

		//根据手势的类型,做不同的处理,关于处理代码的说明请参照case 2的注释
		switch(m_type)
		{
		case 0:
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,3-numOfCube)));
			movePoint = ccp(logicalPoint.x,3-numOfCube);
			break;
		case 1:
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,numOfCube)));
			movePoint = ccp(logicalPoint.x,numOfCube);
			break;
		case 2:
			//如果左边的卡片数为0,就移动到(0,原y坐标),也就是ccp(numOfCube,logicalPoint.y)
			//注意是移动到,其他的情况请类推
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(numOfCube,logicalPoint.y)));
			movePoint = ccp(numOfCube,logicalPoint.y);
			break;
		case 3:
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(3-numOfCube,logicalPoint.y)));
			movePoint = ccp(3-numOfCube,logicalPoint.y);
			break;
		}
		this->removePoint(movePoint);
		//执行动作
		cube->runAction(move);
	}
}

也许大家会觉得我这样的实现方式太麻烦了,我本人也认为消除这块写的不太好,至少是麻烦了一点,这和我刚开始的思路有关系吧,不过大家可以采用自己的实现方式,对于我的代码可以学习一下其中的数组排序等知识,关于其他实现的方式你有好的想法给我留言哈!最后上一下主要逻辑实现以后的效果图吧!

小塔1024——卡片的翻转和消除