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

说了这么多,这一节是本文最后一节啦,就是程序的进一步优化。

这一节呢,还分那么几个小意思,- -!

1.程序逻辑和机制的优化

2.源码级代码的优化

3.针对CPU和操作系统的编译优化

问:大侠,我是过来人,排序哈希我渐渐习惯了,不痛了,还有哪些地方可以更刺激的

答:前面我们提到检测局面重复,不要让后面的局面有跟走过的局面一样,导致无限的堕落,无法自拔,还有一样是可以算作重复的

那就是失败的局面,即没有一个箱子可以有效推的局面,再出现这个局面就不要分析了,直接删掉吧,那么我们就要再创建一个失败

局面队列,把失败过的局面箱子坐标和哈希值以及搬运工的位置保存下来,哈希值是为了哈希计算,而坐标则是为了万一的万一的万

一的万一有重复,局面解不出来,那么就原汁原味的使用坐标比较,当然使用纯哈希比较求解的时候坐标既不保存也不分配内存。

问:那么就只有这个吗?

答:同样,失败的局面向父级递归,tagStage.Slaves递减,如果降到零,那么父级也是失败局面,跟着连坐,也加入失败局面队列。

问:爽!我想马上看源码,我要看,我要看……

答:

// 记录失败场景快照(暂时未完成测试)
int fnStageSnap(PQUEUE pQueue, PSTAGE pStage)
{
	union {
		PSNAP pSnap;	// 快照指针
		BYTE *pDummy;
	};
	PSHOT pShot;
	int dwRet;

	pShot = pQueue->Shots;
	if(pShot->Count)
	{
		pSnap = (PSNAP)realloc(pShot->Snaps, pShot->Size * (pShot->Count + 1));
	}else
	{
		pSnap = (PSNAP)malloc(pShot->Size);
	}
	if(pSnap)
	{
		pShot->Snaps = pSnap;	// 更新指针
		//pSnap = &pSnap[pNext->Shots->Count];	// 结构体不定大小
		pDummy += pShot->Size * pShot->Count;
		pSnap->Position = pStage->Position;
		pSnap->Hash = pStage->Hash;		// 布局指纹
		if((pStage->Flags & SGF_CRC32) == 0)
		{
			// 排序存储坐标详情(只要不归位, 都会撤销回到最初, 不必排序)
			//fnStageSort(pNext->Stars, NULL, pSnap->Stars, pNext->Count);
			V32Copy(pSnap->Stars, pStage->Series, sizeof(STAR) * pStage->Count);
		}
		pShot->Count++;			// 递增失败局面数量
		// 向上递归父级场景, 所有待分析子场景数量为零的都将记录到失败列表并弹出队列
		pStage = pStage->Host;
		if(pStage != NULL)
		{
#ifdef _DEBUG
			fnPrint("父级场景=0x%08X, 剩余子级场景数量=%d.\r\n", pStage, pStage->Slaves);
			dwRet = fnQueueRange(pQueue, pStage);
			if(dwRet <= 0)
			{
				fnPrint("[警告]场景0x%08X不在有效队列中!\r\n", pStage);
				return 1;
			}
			if(pStage->Slaves == 0)
			{
				fnPrint("[警告]场景0x%08X已经没有子场景!\r\n", pStage);
				return 1;
			}
#endif
			pStage->Slaves--;	// 调试完成可以确保计数为正
			if(pStage->Slaves == 0)
			{
#ifdef _DEBUG
				fnPrint("记录父级场景=0x%08X到失败集合.\r\n", pStage);
#endif
				dwRet = fnStageSnap(pQueue, pStage);
				if(dwRet <= 0) return dwRet - 1;
#ifdef _DEBUG
				fnPrint("移除父级场景=0x%08X.\r\n", pStage);
#endif
				fnQueueRemove(pQueue, pStage);
				return dwRet + 1;	// 返回级数
			}
		}
		return 1;
	}
	fnStageCode(SEC_CACHE_NULL);
	return 0;	// 内存不足, 数据不变
}

问:那么,可有运行效果比较?

答:

没有失败队列,在前面已有,这里再贴一次:

加入失败队列后,注意队列使用峰值,剩余值和步数比较:

问:太TM神了,还有没有?我还要,我还要!

答:当我们针对一个箱子进行分析的时候,无论上下左右,总有个固定次序,比如就是上下左右,有可能最好的走法是后面的方向

得到走法列表后,按照归位数量从大到小依次生成场景,如图,如果不优化这个机制,上下左三个方向会先被生成分析,而加入这

个机制,向右的归位数量最大,先被分析,其实就一步完成了,也就是立刻到达最优解。

问:暴走了。。还有吗?

答:留点空间给后来者发挥吧, 下面是源码级别的优化,比如内部指针

tagStage有几个内部指针,在申请内存时一并计算分配,而不是后面再分配,动态分配在32位Windows中以4K为

单位,你申请一个字节也是4K,浪费内存,反复申请释放,过程复杂容易写错,同时容易产生碎片手榴弹。。。

另外,是一维数组代替若干个一维或多维数组,比如Stars,简化内存管理逻辑,详见资源包代码

再者,使用内联汇编的裸函数代替库函数,可以大幅提升效率,比如内存复制,在V32.dll中:

// 复制两个内存块, 不检测指针
void __declspec(naked) __stdcall fnCopy(void *dst, const void *src, int len)
{
	// 调试时mov esi, esp, 调用完成cmp esi, esp检查堆栈
	//__asm{
	//	mov edi, dst		; [esp + 4]
	//	mov esi, src		; [esp + 8]
	//	mov ecx, len		; [esp + c]
	//	rep movsb
	//}
	__asm{
		push esi			; 保护寄存器esi和edi, 第一个变量由[esp+4h]推移至[esp+0ch], 第二个为+10h
		push edi
		mov esi, [esp+10h]	; 串操作原始地址
		mov edi, [esp+0ch]	; 串操作目标地址
		mov ecx, [esp+14h]	; 串操作计数
		and ecx, 3			; 先复制余数
		rep movsb			; 逐字节复制
		mov ecx, [esp+14h]
		shr ecx, 2			; 求四的商(双字个数)
		rep movsd			; 逐双字复制
		pop edi
		pop esi
		ret 0ch				; 裸函数返回(三个参数)
	}
}

当你赋值两个结构体时,编译出来其实差不多就是类似的指令,而不会调用memcpy之类的函数。

就像把比较放在循环外,有程序来控制指针,我们干嘛每次都去检测这个指针呢?

最后针对CPU和操作系统的优化,时间差不多了,就不说了,Intel有专门的编译优化工具。

其实,我只是写个小礼物给莲花妹妹开心一下,写到这里,也算尽心尽力了,但愿编程之美,能带给世人更多的欢笑,释放更多的压力和痛苦

我的计算机说,反反复复的事情就TM交给我吧!!!

致佳茵 全文毕

虎胆游侠

2015-03-15 00:34:56

时间: 2024-10-25 15:05:26

致佳音: 推箱子游戏自动求解算法设计(五)的相关文章

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

这一节是本文的核心内容,即推箱子游戏求解算法的设计思路过程 前面已经说过过,判断局面重复的最好标准不是局面完全一致,而是坐标排序相同且角色坐标通行 如下图,角色无论怎么移动,不推动箱子的时候,都能回到原来的位置,算作同一个局面: 再如下图,两个箱子互换位置,结果与没有移动箱子是一样的,所以排序箱子坐标以后一致,还是相同局面 问:有必要判断局面重复吗?是不是只是提升一下效率? 答:不是为了提升效率,而是为了能解出来,如果使用递归,重复的局面反复耗尽堆栈,而队列则耗尽内存 正如上图,反复推这两个箱子

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

本来酷爱音乐, 老衲也想谱一曲<献给爱丽丝>之类, 通俗又有境界的曲子, 奈何没有那个水平, 也不是一个程序员做的勾当,于是就有了本文. 希望莲花妹妹跟着思路走,能遗忘那些太多的忧伤-- 本文分以下四个小节: 一.平面寻路算法(Alpha Star) 二.闭合图形填充算法(扫描线种子填充) 三.推箱子求解 四.执行效率的优化 日本人有个程序叫Sokuban Automatic Solver,文件名是sokoban722.exe我附带在资源里面 不过日本人的东西没有开源,我们也不知道它里面的花花

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

这一个小节我们说一说传说中的A×算法,其实之前也上传过类似的小件件,这里我们就去剖析一下它 毕竟在游戏程序,我们要从一点移动到另一点,并得到最短路程的轨迹,类似这种算法还有好几种,执行效率都差不多,不过大多不能得到轨迹 首先,从一点移动到另一点,最快就是直接走过去了,就像小男生爱上小女生,最好的办法就是直接走到她面前说:我爱你 不过理想状态,几乎是没有的,弯路那是必然的经过,有曲线,其实更美-- 那么弯路该怎么走呢,是不是先去背景看下毛主席,再去三亚晒个太阳,再回来告诉她外面的世界好美,不,不,

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

这一节我们说说闭合曲线的填充,为什么会有这个东西呢 当我们递归一个场景时,我们以推动箱子为标志,如果不推动箱子,那么跑到哪里都白跑,而出现重复的判别最好就是所有坐标相同 包括这些坐标互换位置(排序结果相同),而后一个场景搬运工坐标能移动到另一个场景搬运工的位置(求解算法部分再详细说) 由于场景有多个箱子,每个箱子可以有几个方向移动,反复的寻路效率不高,起初我想删除路径部分,只检测能否移动到目标 来提升执行效率,就是偷懒一下,然后想想既然是礼物,偷懒也不是分时候,也有脸献给别人于是废弃了A×算法

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

这一节是本文的核心内容,即推箱子游戏求解算法的设计思路过程 前面已经说过过,推断局面反复的最好标准不是局面全然一致,而是坐标排序同样且角色坐标通行 例如以下图.角色不管怎么移动,不推动箱子的时候.都能回到原来的位置.算作同一个局面: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJzbmlwZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"

JavaScript写一个小乌龟推箱子游戏

推箱子游戏是老游戏了, 网上有各种各样的版本, 说下推箱子游戏的简单实现,以及我找到的一些参考视频和实例: 推箱子游戏的在线DEMO : 打开 如下是效果图: 这个拖箱子游戏做了移动端的适配, 我使用了zepto的touch模块, 通过手指滑动屏幕就可以控制乌龟走不同的方向: 因为推箱子这个游戏比较简单, 直接用了过程式的方式写代码, 模块也就是两个View 和 Model, 剩下就是用户的事件Controller, 用户每一次按下键盘的方向键都会改变数据模型的数据,然后重新生成游戏的静态htm

推箱子游戏

本游戏为推箱子游戏,即通过移动方向键来控制小人去推动箱子,直到把所有的箱子都推动到各个目标中.游戏需要在人物行走过程中无法穿越墙和箱子,并在有限的范围中放好各箱子,且每次只能搬运一个箱子.所基于的语言是8086汇编,使用的编译环境是唐都的集中开发环境TD-PIT. 本次设计的基本思想是通过将不同的元素(墙.路.箱子.人.目标位)抽象为不同的矩阵,然后将所设计的地图描抽象成一个控制矩阵来控制图像的显示,每一个控制矩阵块代表一个元素,我们通过不断刷新控制矩阵来达到显示人物移动及推箱子的效果. 1.1

jQuery版推箱子游戏详解和源码

前言 偶然间看到很多用js写游戏的感觉很炫酷的样子,所以就想试试,就看了一些资料和某前端站点的视屏.于是乎就自己动手实践了一下,上推箱子截图 感觉很丑陋,但是功能是实现了.再说貌似大多都是这样的吧,这一关其实还是有点难度的,我做完之后想检测一下下一关正确么,居然还玩了以后才通关. 如果你看到这张图让你想起了你童年的回忆,说明你老了,这里可以试玩一下(很遗憾没有链接地址,最后又源码可以下载). css布局 主要考虑的是地图是怎么动态生成的,地图中有灰色的,还有墙,箱子,蓝色,红色背景,人物.先看c

用C写一个简单的推箱子游戏(一)

我现在在读大二,我们有一门课程叫<操作系统>,课程考查要求我们可以写一段程序或者写Windows.iOS.Mac的发展历程.后面我结合网上的资料参考,就想用自己之前简单学过的C写一关的推箱子小程序. 这一程序主要用到了C语言中的二维数组,头文件#include<conio.h>(因为要调用getch()函数以记录输入内容),switch函数等. 一.     功能概述 a)   游戏规则概述 玩家通过键盘输入W.S.A.D四键或者“↑”.“↓”.“←”.“→”四个方向键推动箱子,而