致佳音: 推箱子游戏自动求解算法设计(三)

这一节我们说说闭合曲线的填充,为什么会有这个东西呢

当我们递归一个场景时,我们以推动箱子为标志,如果不推动箱子,那么跑到哪里都白跑,而出现重复的判别最好就是所有坐标相同

包括这些坐标互换位置(排序结果相同),而后一个场景搬运工坐标能移动到另一个场景搬运工的位置(求解算法部分再详细说)

由于场景有多个箱子,每个箱子可以有几个方向移动,反复的寻路效率不高,起初我想删除路径部分,只检测能否移动到目标

来提升执行效率,就是偷懒一下,,然后想想既然是礼物,偷懒也不是分时候,也有脸献给别人于是废弃了A×算法

目的就很明显了,标定所有能到达的位置,检测的时候就不用寻他妹的路了,直接检测是否被填充即可

那么如何填充一个闭合的曲线呢?最简单的逻辑是:

1.往周围4个或8个方向,记录所有不是边界,没被填充的点并填充

2.递归这些点,直到没有新的点被检测到

递归,又是递归,这是自交么?罪过啊!万恶的递归,可怜的堆栈……

上面的方法实现很简单,不过有很多点会被反复检测若干次,效率并不太高

另外一种方法就是我们要说的:扫描线种子填充算法

主要逻辑思想是:

1.把坐标换成线段,记录最左和最右断点,填充线段,加入队列(代替递归)

2.填充最先加入队列的线段,检查上一行和下一行,把相邻的线段都加进来,从队列中删除

3.重复1-2直到队列没有任何线段

示例源代码,详情见资源

// 扫描线填充(用循环代替递归, 玩家必须在边界封闭的曲线内)int fnStageScan(PQUEUE pQueue, PSTAGE pStage){UINT x0, xl, xr, y0, xid;UINT flag;//, cPSTACK s;PSTAR p;//UINT sNum;union {UINT *pData;BYTE *pNum;};UINT X, Y;int i;// 首先清零非类型位Y = pStage->SizeX * pStage->SizeY;X = Y % 4;pNum = pStage->Matrix;while(X–){*pNum++ &= SMT_FILTER;// 清零非类型信息}Y /= 4;while(Y–){*pData++ &= SMT_MATRIX;// 清零非类型信息}// 清空堆栈, 种子入栈s = pQueue->Stacks;p = s->Stars;p->X = pStage->PosX;p->Y = pStage->PosY;s->Count = 1;while(s->Count){X = p->X;Y = p->Y;p–;s->Count–;pNum = &pStage->Matrix[Y * pStage->SizeX + X];*pNum |= SMT_OPENED;// Me.PSet (x0, Y), newvaluex0 = X + 1;pNum++;// 填充右边不是箱子也不是边界的单元while((*pNum & SMT_MASKED) == 0)// Me.Point(x0, Y) <> boundaryvalue{//if(x0 >= pStage->SizeX) break;// 到最右边(地图控制)*pNum |= SMT_OPENED;pNum++;x0++;}xr = x0 – 1;// 最右坐标x0 = X – 1;pNum = &pStage->Matrix[Y * pStage->SizeX + x0];// 填充左边不是箱子也不是边界的单元while((*pNum & SMT_MASKED) == 0)// Me.Point(x0, Y) <> boundaryvalue{//if(x0 < 0) break;// 到最左边(地图控制)*pNum |= SMT_OPENED;pNum–;x0–;}xl = x0 + 1;// 最左象素// 检查上一条扫描线和下一条扫描线,若存在非边界且未填充的象素,则选取代表各连续区间的种子象素入栈。y0 = Y;for(i = 1; i >= -1; i -= 2){x0 = xr;Y = y0 + i;while(x0 >= xl){flag = 0;// 向左传递未填充的点直到边界, 记录最后一个点的X坐标pNum = &pStage->Matrix[Y * pStage->SizeX + x0];// c = Me.Point(x0, Y)//while(((*pNum & SMT_MASKED) == 0) && ((*pNum & SMT_OPENED) == 0) && (x0 >= xl))while(((*pNum & SMT_OPNMSK) == 0) && (x0 >= xl)){// (c <> boundaryvalue) And (c <> newvalue) And (x0 >= xl)if(flag == 0){flag = 1;xid = x0;}pNum–;// c = Me.Point(x0, Y)x0–;}// 将最右侧可填充象素压入栈中if(flag == 1){p++;p->X = xid;p->Y = Y;s->Count++;// s.push(Point(xid,y));flag = 0;}// 检查当前填充行是否被中断,若被中断,寻找左方第一个可填充象素pNum = &pStage->Matrix[Y * pStage->SizeX + x0];// c = Me.Point(x0, Y)while(*pNum & SMT_OPNMSK){// (c = boundaryvalue) Or (c = newvalue) '判断当前点是否为边界或箱子 或 判断当前点是否为已填充点if(x0 == 0) break;// 到最左边(…)pNum–;x0–;// 若当前点为边界点或已填充点,根据前面的判断,当前点必然未超出左边界,则当前点向左移动}}// loop while(x0 >= xl)}// next for(i = 1; i >= -1; i -= 2)}// loop while(!s.isempty())return 1;}为了存储空间,我只填充特定标志位,队列固定大小,结构更加紧凑,测试执行效果:

左键绘制线段端点,右键点击闭合曲线内任意一个点即填充完成,详见资源包。

长江后浪催前浪,一辈新人换旧人。

致佳音: 推箱子游戏自动求解算法设计(三)

相关文章:

你感兴趣的文章:

标签云: