鉴于有人给我发消息推荐一些能对string进行操作的函数,本章将刻意使用string相关函数
14.1 多路径的连续遍历
路径的分割
对于大型区域,可以分解成一个个的小型区域,每个小型区域单独制作路径,任务需要遍历大型区域,这就要求能把一连串的路径衔接起来。如果能实现这种要求,会缩短遍历时间,从而更加效率。
为了使衔接更加容易,在制作路径的时候,对大型区域的分割要合理,否则不光不利于衔接,也不利于返回。
分割大型区域的方法有很多,这里我介绍我用的一种分割方法供大家参考。
拿神龙岛举例,整个大型区域的名字定为shenlong,整个区域有坐船有神龙弟子挡路,有个地方能爬悬崖有比较长的busy,全部遍历比较费时间。
将它分割成5个小型区域shenlong1,shenlong2,shenlong3,shenlong4,shenlong5
考虑到神龙岛的地形,在峰顶练武场那里开始出现岔路,往左是通往洪教主,往右是通往五龙堂,另外半山腰那里客厅,客厅后面山泉那里能往下爬。
shenlong1:从ct到峰顶练武场的路径。ct到塘沽口直接最短路线遍历过去,从塘沽口开始路径中要包含路上每个岔路,一直到峰顶练武场停下。此路径中不包含半山腰客厅已经往下爬悬崖的那部分房间。
shenlong2:从练武场往五龙堂方向遍历,然后返回练武场的路径。
shenlong3:从练武场往洪教主方向遍历,然后返回练武场的路径。
shenlong4:从练武场往半山腰客厅方向遍历,然后返回练武场的路径,包括山泉那里爬悬崖。
shenlong5:从练武场最短路线返回ct的路径。
这种划分的好处在于,不论在哪个小区域找到了盗宝人,直接跳至最后一段路径shenlong5,从而最短路线返回。
注一:有人也许会问,为什么不能找到盗宝人停下,然后原路返回?主要原因在于原路返回需要用到路径的逆转和简化,而逆转和简化的运用是有限制的。有的地方是不能原路返回的,比如无量爬上去了就不能往回爬下来。另外比如慕容茶花林是个迷宫,进去和出来的路径不一样,路径的逆转简化都不能用。再者,逆转和简化的制作难度不比多路径的连续遍历简单,即使无量那里能往回爬下来,也要对climb stiff,climb yafeng这些方向定义逆方向。另外简化路径的时候路径缩短,有busy的方向所在序号都会发生改变,计算起来也比较复杂。
再在拿慕容来举例,整个区域可以这样划分:
murong1:从ct到琴韵的码头的最短路线
murong2:从琴韵码头cai yanziwu过去,遍历整个燕子坞cai qinyun回到码头
murong3:从琴韵码头cai tingxiang过去,遍历整个听香水榭cai qinyun回到码头
murong4:从琴韵码头遍历琴韵然后tan qin,row mantuo在遍历整个曼陀最后enter boat返回码头
murong5:从码头最短路线回到ct
拿洛阳来举例,整个区域可以这样划分:
luoyang1:从ct走暗道到洛阳中心广场,不需要遍历的部分直接最短路线走过去,凡是盗宝人可能会出现的房间都遍历到
luoyang2:从洛阳中心广场遍历洛阳北街附近所有房间,回到洛阳中心广场。
luoyang3:从洛阳中心广场往西遍历包括万安寺然后返回洛阳中心广场
luoyang4:从洛阳中心广场往东南遍历包括天地会里面然后返回中心广场
luoyang5:从洛阳中心广场经由渡口方向cross river返回ct
注二:洛阳这地方着实大了点,盗宝人会出现在渡口附近甚至包括汉水南岸,也会出现在万安寺天地会这些地方,也会出现在由暗道去洛阳的路上,洛阳城本身都够大。整个区域如果做成1条路径会包含300多个方向,整个走完即使是快速行走也要1分钟左右。如果你采用先搜索一次定位,再遍历一次击杀返回这样的方法来做机器人,仅仅是走路的时间差不多都要1分半了,这还不算你杀盗宝人的时间。
采用多路径的连续遍历,平均节约至少60%的遍历时间,如果你的遍历做的好,你会发现机器人速度比传音搜魂和求助vast还快。
注三:天龙寺这个区域,盗宝人可能会出现在无量山的悬崖峭壁上。本人做胡一刀任务以来,遇到过几十次吧。如果采用1条路径来遍历整个天龙寺,这个悬崖峭壁就是个鸡肋,路径中包含它,意味着每次都要爬悬崖过善人渡,遍历时间延长3倍不止,如果不包含它,本次胡一刀任务直接失败。
这里分割区域就很好的解决了这个问题,路径分割成4个小区域,去天龙寺,遍历天龙寺,爬悬崖无量山走一圈,返回中央广场。
如果在第2个区域找到盗宝人,直接跳至第4个区域返回。如果没有找到盗宝人则需要在无量走一圈.并且主id从大米那里知道盗宝人在峭壁那里之后,可以不用走第2个区域,从ct直奔峭壁杀盗宝人然后返回ct。整个遍历效果相当令人非常满意。
路径分割的原则
原则一:去和回的路径尽量简单,尽量都只包含最短路线。因为这2条路径在整个遍历过程中都是必不可少的,无论怎么划分,也无论盗宝人在哪里,总必须得有去有回。这种必不可少的路径,尽量简单能提高遍历效率。
原则二:有busy的小区域,花费时间较多的小区域靠后。因为遍历的顺序是从小号开始,一旦找到了盗宝人可以直接跳到最后一条路径返回,从而避免遍历耗时较多的区域。
原则三:每个小区域的终点和后面任意一个小区域的起点相同。这点很重要,如此,可以从当前小区域的终点跳至后面任意一条小区域。这也是保证省略部分小区域和路径连续遍历的前提。
原则四:所有小型区域加起来确实遍历了整个大型区域的所有房间。
原则五:在保证上面4条原则的前提下,如果你能做到所有小区域组合起来无重复或者少重复,每个小区域大小基本一致,那就更完美了。
路径的使用(以神龙为例)
shenlong下面分为shenlong1-shenlong5五个小区域。@shenlong的值预先设置为5并且固定,表示shenlong一共有5个区域。
首先制作trigger,抓取盗宝人所在地址@dbraddress(比如神龙岛),根据@dbraddress的值,令@address的值为大型区域的名字(比如shenlong),这个应该很容易做到吧,2个list类型变量将中文名字和英文名字对应起来转化就可以了。
每个小区域路径保存在@shenlong1-@shenlong5中,对单独的小区域比如shenlong1,遍历方法跟前面几章介绍的一样,快速行走嵌套alias为bianli
另外制作alias bianlishenlong1,在遍历之前对步数@step,有busy的方向@busy,需要等待的时间@time,@wait等等一系列变量的初始化并且开始执行alias bianli,这里bianli为嵌套alias,嵌套结束之后执行一个alias next,再次大概给出bianli和bianlishenlong1的定义,并且next定义如下:
#alias bianli {#if (...) {...;%item(@path,@step);bianli} {next}}
//满足嵌套条件则依次取出@path中每个方向执行,并且嵌套一个bianli,嵌套条件结束执行next
#alias bianlishenlong1 {#var path @shenlong1;#var step ...;#var steps %numitems(@path);#var busy ...;#var time ...;#var wait ...;bianli}
//初始化各个变量,并且开始执行嵌套alias bianli
#alias next {tell @zhanghao @pathnumber完毕,下一个[@pathnumber+1]}
//每条小区域路径走完,都会tell自己一句话,内容中包括了当前小区域编号和下一个小区域的编号
#trigger {*~(@zhanghao~)告诉你:%d完毕,下一个(%d)} {#if (%1>[@@address]) {tell @zhanghao 整个路径遍历结束} {#if (@finddbr=0) {#var pathnumber %1;#var pathname %concat(@address,"%1");#exe bianli[@pathname]} {#var pathnumber [@@address];#var pathname %concat(@address,@pathnumber);#exe bianli[@pathname]}}} {finddbr}
//这2个trigger要结合起来看,从表面上看非常复杂,我详细解释下
//[@@address]用到了2个@符号,@address的值为shenlong,[@@address]的值就为5,注意这里必须加上[ ].如果不加[ ],@@address的值为字符串@shenlong,而不是数字5.
//遍历shenlong1之前,令pathnumber为1,那么依次遍历各个小区域结束之后就会得到自己tell自己的信息,比如"1完毕,下一个2"或者"3完毕下一个4"
//根据这句话来判断下一个要遍历的小区域,首先判断%1>[@@address],如果为真,则tell @zhanghao 整个路径遍历结束,否则遍历下一个区域
//遍历下一个区域的整个语句为#if (@finddbr=0) {#var pathnumber %1;#var pathname %concat(@address,"%1");#exe bianli[@pathname]} {#var pathnumber [@@address];#var pathname %concat(@address,@pathnumber);#exe bianli[@pathname]},第一个括号里的语句表示顺序遍历接下来的一个小区域,第二个括号里的语句表示跳至最后一个小区域返回。
其中第一个括号里的语句为#var pathnumber %1;#var pathname %concat(@address,"%1");#exe bianli[@pathname]
@pathnumber的值更改为%1,要遍历的小区域的名字保存在@pathname中,#exe bianli[@pathname]开始遍历。
这里#var pathname %concat(@address,"%1")和#exe bianli[@pathname]大家可能都感到陌生
%concat作用为将2个字符串连接起来,举例来说,如果%1为3,@address为shenlong,那么@pathname为shenlong3
#exe bianli[@pathname]的作用等同于执行alias bianlishenlong3,前面讲过,bianlishenlong3为对小区域shenlong3的遍历,其中包括对@step,@busy,@wait,@time等一系列变量的初始化并且执行嵌套alias bianli
如果第一个括号的语句都看懂了,那么第2个括号里的内容就不难理解,表示直接跳至最后一个区域比如shenlong5,以此来返回。
至于到底是顺序遍历下一个区域还是直接跳至最后一个区域,由变量@finddbr来控制,因此你需要在遍历之前在trigger中加入#var finddbr 0,找到盗宝人的时候#var finddbr 1.
再来整理下神龙岛区域都用到了哪些变量和alias,来更好的理解上面的遍历方法
@dbraddress值为神龙岛,@address值为shenlong,@shenlong值为5
@pathnumber表示当前正在遍历的路径编号,值只可能是1-5.@pathname表示小区域的路径名字,值只可能是shenlong1-shenlong5
这些变量中除了@shenlong是神龙岛独有的,另外的变量都是公用变量。简单点,比方说慕容区域独有的变量为@murong,至于@dbraddress,@address,@pathnumber,@pathname则属于公用变量,对整个慕容区域的遍历也是用这4个变量
用到的alias有next,bianlishenlong1,bianlishenlong2,...,bianlishenlong5,以及bianli
其中next,bianli都属于公用alias,慕容区域的遍历也是用这2个alias
bianlishenlong1-5为神龙岛独有的,慕容区域的遍历用到的alias为bianlimurong1-5
#alias lianxubianli {#var pathnumber 1;#var pathname %concat(@address,@pathnumber);#exe bianli[@pathname]}
//从第一条小区域路径开始,遍历整个大型区域@address
至此,如果@address值为shenlong,命令行输入lianxubianli,则按顺序依次遍历神龙岛每个小区域,找到盗宝人之后继续遍历完当前小区域,直接跳至最后一个小区域返回。同样如果@address的值为慕容,也是在命令行输入lianxubianli
注四:可以看到这个遍历方法非常非常强大,仅仅通过胡一刀告诉你的地址,获得地址的英文名字,然后输入lianxubianli就可以了,而且遍历的速度非常快。不需要使用第十三章提到的简化和逆转,因此也省去了很多是否可逆的判断,而这个判断通常非常复杂。而且当路径不可逆时,不需要老老实实遍历完整个路径,速度快很多;当路径可逆时,也仅仅只需要多走完当前没有走完的小区域,这个时间通常不到3秒。这个方法本人大力推荐。
#trigger {盗 宝 人*~(@dbr~)} {#t- find;#var dbrnum @pathnumber;#var dbrnumber @n;#var finddbr 1} {find}
//搜索盗宝人的trigger,执行一次关闭,@dbrnum用来保存盗宝人所在小区域的区域编号,@dbrnumber用来保存盗宝人所在房间的房间编号,@finddbr用来指示是否已经找到盗宝人
//对于@n的值,需要在遍历过程中数房间数目,以此来确定盗宝人在当前小区域路径中的位置
#trigger {*~(@zhanghao~)告诉你:%d完毕,下一个(%d)} {#if (%1>[@@address]) {tell @zhanghao 整个路径遍历结束} {#if (%1<=@dbrnum) {#var pathnumber @dbrnum} {#var pathnumber [@@address]}};#var pathname %concat(@address,@pathnumber);#exe bianli[@pathname]} {killdbr}
//前面那个trigger设置为finddbr class,这个trigger设置为killdbr class,目的很明显,就是走到盗宝人那里停下,然后杀掉,再返回。
//每次选择小区域时判断%1的值,如果%1>[@@address]表示整个路径遍历结束;如果%1<=@dbrnum,直接跳至第@dbrnum个小区域,如果%1>@dbrnum,直接跳至最后一个小区域返回,因此走到盗宝人那里和从盗宝人那里返回都是用这个trigger
注五:这里用到的遍历alias还是诸如bianlishenlong1,bianlishenlong2之类的alias,用这个alias原来的定义方法来遍历盗宝人所在小区域的时候,是停不下来的,因此需要修改这个alias.这个修改不难,在bianlishenlong1中判断(@pathnumber=@dbrnum),如果为真则@steps的值设置为@dbrnumber就可以了,如果不真,则@steps的值设置为%numitems(@shenlong1).
注六:修改后的bianlishenlong1同样可以用于搜索盗宝人,因为搜索前肯定将@dbrnumber,@dbrnum都初始化为0,因此搜索盗宝人的过程中@pathnumber肯定不会等于@dbrnum,可以用这个修改后的bianlishenlong1代替上面提到的bianlishenlong1,前后统一起来,没必要另外定义新的alias,减少机器人制作工作量。
注七:至此,只要获知盗宝人的中文地址,执行#t+ finddbr;#t+ find;#var dbrnumber 0;#var dbrnum 0;lianxubianli就可以按小区域顺序搜索盗宝人了,搜索到盗宝人之后直接遍历最后一个小区域返回,然后执行#t+ killdbr;lianxubianli就可以只选择第1个,盗宝人所在那一个以及最后1个小区域来杀掉盗宝人以及返回。前面说了,第1个和最后1个小区域都很短,一般为最短路径,整个过程速度非常快。
14.2 解决乱入的2种方法
抓取房间出口方向
比如"east、north、west 和 south",整个字符串作为一个房间的出口信息,如果能将这个字符串转化为list类型变量,那么对房间的出口进行操作就变的很简单。
房间的出口信息中一般包含:出口方向、"、"、"和"、"。"以及空格
#tri {这里*的出口是(*)$} {#var exit "%1";#var exit %replace(@exit,"、","|");#var exit %replace(@exit,"和","|");#var exit %replace(@exit,"。","|。");#while (%item(@exit,1)!="。") {#var exit %additem(%trim(%item(@exit,1)),@exit);#delnitem exit 1};#delnitem exit 1} {exit}
//思路很简单,将"、"以及"和"都替换成"|",将空格去掉,最后去掉"。"
//比如房间出口是east、north、west 和 south。那么#var exit %replace(@exit,"、","|");#var exit %replace(@exit,"和","|");#var exit %replace(@exit,"。","|。")这3个命令的结果是@exit的值为" east|north|west | south|。"
//#while命令,判断@exit的第一个item,如果不是句号,就取出第一个item消除空格之后添加到@exit的末尾,然后删除第一个item。简单点说就是把第一个item消除空格之后移动到最后面去,一直循环判断知道第一个item为句号。实际上整个#while语句的作用就是消除空格。
//最后删除句号就可以了
#tri {这里没有任何明显的出路。} {#var exit {}} {exit}
//没有出口,@exit赋值为空
有了这2个trigger,房间的出口信息都保存在@exit中,@exit为list变量,可以轻易知道该房间的任意一个方向以及房间出口方向的数目
解决乱入方法一
通过大米跟随护镖,乱入之后look各个方向,看到大米后选择正确的方向回去。这个方法只能解决1步乱入,护镖任务改动之后这个方法已经没什么用了,仅仅靠这个是无法全自动的,我简单说下。
#tri {劫匪趁你不注意,推着镖车就跑,你赶紧追了上去。} {#t+ exit;tell @zhanghao 乱入}
//镖车移位置,开启抓取房间出口信息的trigger
#tri {@myname~(@zhanghao~)告诉你:乱入} {#t-exit;#t+ dummy;#var dummynum 0;#forall @exit {look %i}}
//房间出口信息抓取完毕,关闭exit class,然后打开观察大米的trigger,并且look房间的所有出口
#tri {^(*)%s-%s$} {#add dummynum 1} {dummy}
//对房间名计数
#tri {*@dummyname~(*~)$} {#t- dummy;gan che to %item(@exit,@dummynum)} {dummy}
//注意exit class在触发之后关闭了,所以look各个方向的时候不会更新@exit,因此@exit的值为当前房间的出口信息
//看到大米了,关闭dummy class,把镖车赶过去.大米的名字预先保存在变量@dummyname中
解决乱入方法二
其实是方法一的推广,方法一只是这个方法的一个特例,此方法可回答本文开头提出的第3个问题
实例:3已知你的大米或者npc在你附近不超过n步的某个房间,通过抓取房间出口方向来遍历n步以内所有房间,并且在找到大米或者npc时能够停下来。
通过房间的出口信息,遍历当前位置任意预先指定步数以内的所有房间并且返回。遍历结束后,能得到从当前位置到大米所在房间的路径,沿着路径把镖车赶过去就可以了。下面给出遍历并且得到路径的代码
普遍采用的遍历方法有2种,即广度优先和深度优先。这2种方法都可以实现,下面采用广度优先写出遍历方法。从理论上来说广度优先要快,但是当我在mud中实际试验2两种方法,比较之下发现,当乱入步数较多时,广度优先走了很多重复路,遍历速度不如深度优先,而当乱入步数较少时,由于遍历时间较短,广度优先体现不出优势,所以实际上深度优先更好一些。对于深度优先方法有兴趣可以自己尝试写一下,深度优先要简单一些。
小知识:广度优先的顺序是,1,2,...,max,(度数加1),1-1,1-2,1-3,...,1-max,2-1,2-2,...,2-max,3-1,...,3-max,,,,,max-max,(度数+1),1-1-1,1-1-2,...,1-1-max,1-2-1,1-2-2,...
度数是广度优先遍历的一个专业术语,在这里度数表示到达某个房间需要走的步数
比如某个房间需要先走第a个方向,再走第b个方向,再走第c个方向,最后再走第d个方向才能到达,那么该房间的度数为4,用a-b-c-d来表示房间的编号
可以看到,只有当全部为max的时候,度数+1。采用广度优先遍历,度数小的房间将被优先遍历到。
实现思路:
先遍历所有的1度房间,然后遍历所有的2度房间一直进行下去
举例来模拟一下遍历过程,以便更好的看懂下面给出的代码。
正在遍历4度房间,如果房间的编号为a-b-c-d,那么先退回一步成为a-b-c,然后判断d是否为最后一个出口,如果是则继续退回一步,判断c;否则前进一步,进入a-b-c-(d+1)号房间
如果连续退了2步,房间编号为a-b,判断c不为max,此时按上面的判断需要前进一步,到达a-b-(c+1)号房间.这时房间度数为3,不足4度,用1补齐,进入a-b-(c+1)-1号房间
上面的过程将一直持续到到达编号为max-max-max-max的房间,此时继续上面的过程将连续退4步,房间编号为空,表示所有4度房间都遍历完毕,此时度数+1,开始遍历所有5度房间。
具体实现方法:
#tri {这里*的出口是(*)$} {#var exit "%1";#var exit %replace(@exit,"、","|");#var exit %replace(@exit,"和","|");#var exit %replace(@exit,"。","|。");#while (%item(@exit,1)!="。") {#var exit %additem(%trim(%item(@exit,1)),@exit);#delnitem exit 1};#delnitem exit 1} {exit}
#tri {这里没有任何明显的出路。} {#var exit {}} {exit}
//跟上面一样,仅仅抓取房间出口信息
#tri {劫匪趁你不注意,推着镖车就跑,你赶紧追了上去。} {#t+ exit;判断劫匪是否被打跑?}
//开始抓取房间出口信息的trigger,开启判断劫匪是否已经被打跑的方法,此方法自己补充完整
#tri <劫匪已经被打跑> {#var dummynum 0;#var degree 0;#var searchpath {};#var pathnum {};tell @zhanghao 开始遍历}
//劫匪已经被打跑,可以开始遍历了,先初始化遍历需要用到的变量
//@degree设置成0度,表示开始遍历0度房间,即当前房间
//@searchpath表示从镖车所在位置到当前所在位置的路径,当前位置和镖车所在位置相同,所以赋值为空.
//@pathnum表示当前房间的编号,编号为a-b-c-d的房间赋值为a|b|c|d。当前房间为0度房间,所以赋值为空.
#tri {@myname~(@zhanghao~)告诉你:开始遍历} {#if (%numitems(@pathnum)<@degree) {#var searchpath %additem(%item(@exit,1),@searchpath);#var pathnum %additem(1,@pathnum);%item(@exit,1);tell @zhanghao 开始遍历} {#t- exit;#var roomnum 0;#var dummynum 0;#t+ dummy;#forall @exit {look %i};tell @zhanghao 是否找到大米}}
//房间度数不足,用1补齐,否则关闭exit class,仍然采用look当前房间所有出口来寻找大米,@roomnum用于房间名计数,@dummynum用来保存大米所在的房间名计数,look之前这2个变量赋值为0
#tri {^(*)%s-%s$} {#add roomnum 1} {dummy}
//对房间名计数
#tri {*@dummyname~(*~)$} {#var dummynum @roomnum} {dummy}
//如果找到大米,得到@dummynum的值
#tri {@myname~(@zhanghao~)告诉你:是否找到大米?} {#t- dummy;#if @dummynum {tell @zhanghao 成功找到大米} {tell @zhanghao 没有找到大米}}
//先关闭dummy class,如果@dummynum的值不为0,则表示找到大米,否则表示没有找到大米
#tri {@myname~(@zhanghao~)告诉你:成功找到大米} {#var searchpath %additem(%item(@exit,@dummynum),@searchpath);tell @zhanghao 路径已经得到,沿着路径赶回去}
//将第@dummynum个方向添加到@searchpath中,得到从镖车所在位置到当前房间再到大米所在位置的路径,其实就是从镖车所在位置到大米所在位置的路径,接下来要做的就是沿着@searchpath返回镖车那里,然后沿着@searchpath把镖车推到大米那里乱入就解决了
//如果沿着@searchpath推车的时候再次乱入也没有关系,大米没有改变过位置,只需要重新解决乱入即可
#tri {@myname~(@zhanghao~)告诉你:没有找到大米} {#t+ exit;#if (%numitems(@pathnum)=0) {#add degree 1;tell @zhanghao 开始遍历} {goback}}
//没有找到大米,开启exit class,如果当前房间度数为0,则@degree加1,开始遍历所有1度房间;否则退回1步,用alias goback返回
#var fangxiang {north|east|northwest|northeast|northup|northdown|eastup|eastdown|up|enter|south|west|southeast|southwest|southdown|southup|westdown|westup|down|out}
#var fangxiangb {south|west|southeast|southwest|southdown|southup|westdown|westup|down|out|north|east|northwest|northeast|northup|northdown|eastup|eastdown|up|enter}
#alias goback {#var temp %item(@pathnum,%numitems(@pathnum));#var temp2 %item(@searchpath,%numitems(@searchpath));#delnitem pathnum %numitems(@pathnum);#delnitem searchpath %numitems(@searchpath);%item(@fangxiangb,%ismember(@temp2,@fangxiang));tell @zhanghao 已经退回一步}
//将@pathnum,@searchpath的最后一个item分别保存在临时变量@temp和@temp2中,然后从@pathnum和@searchpath中删除最后一个item,并且往回走一步,用"tell @zhanghao 已经退回一步"作为接下来的触发
#tri {@myname~(@zhanghao~)告诉你:已经退回一步} {#if (%numitems(@pathnum)=0|&@temp<%numitems(@exit)) {bianlinextdegree} {#if (@temp<%numitems(@exit)) {#add temp 1;#var temp2 %item(@exit,@temp);#var pathnum %additem(@temp,@pathnum);#var searchpath %additem(@temp2,@searchpath);@temp2;tell @zhanghao 开始遍历} {goback}}}
//第一个#if判断是否退到头了,如果是则度数加1,开始遍历更高度数的所有房间;否则进入第二个#if判断
//第二个#if判断刚刚后退的那一步是否为房间的最后一个出口,如果不是则进入下一个出口继续遍历;否则再退一步
//此代码完全按照前面给出的遍历思路来写
#alias bianlinextdegree {#if (@degree<@degrees) {#add degree 1;tell @zhanghao 开始遍历} {tell @zhanghao 遍历结束,乱入解决失败;quit}}
//如果@degree<@degrees,度数+1,开始遍历更高度数的所有房间;否则遍历结束,乱入解决失败,退出游戏,护镖失败
//@degrees用来控制遍历范围,比如事先设定@degrees为4,则将遍历所有4度以内的房间
代码结束
注七:此方法仍然采用look房间出口的作法来寻找大米,因此如果@degrees为4,实际5步以内的所有房间都将被搜索到
注八:此方法已通过实际检验,zmud555的同学可将代码复制到mud中进行试验。我将大米放在襄阳城的帅府大门,然后从帅府大门走n;e;e;e;e,命令行输入#t- dummy;#t+ exit;#var degrees 9;#var dummynum 0;#var degree 0;#var searchpath {};#var pathnum {};look;tell [@zhanghao] 开始遍历,遍历就开始了。最后得出@searchpath的值为east|west|west|west|west|west|south,度数@degrees设置为9,搜索到6度房间时搜索停止。可以看到@searchpath的前2个item互相可逆,这是由于@exit中包含不仅包含去后面房间的出口,还包含返回前面房间的出口,可以对@searchpath使用路径的简化,也可以对上面的方法进行修改,只需要修改抓取房间出口信息的trigger就可以了,修改如下:
#tri {这里*的出口是(*)$} {#var exit "%1";#var exit %replace(@exit,"、","|");#var exit %replace(@exit,"和","|");#var exit %replace(@exit,"。","|。");#while (%item(@exit,1)!="。") {#var exit %additem(%trim(%item(@exit,1)),@exit);#delnitem exit 1};#delnitem exit 1;#var temp3 %item(@searchpath,%numitems(@searchpath));#var temp3 %ismember(@temp3,@fangxiang);#var temp3 %item(@fangxiangb,@temp3);#delitem exit @temp3} {exit}
//抓取出口信息的时候删除掉返回前面房间的出口
修改后再试验上面的例子,得到@searchpath的值为west|west|west|west|south
注九:当通过房间出口方向进入下一个房间有阻碍时,比如店小二要求交钱才允许up,衙门不让进,此时遍历搜索会出问题,上面的方法需要再做修改,这个不难。修改如下:
#tri {这里*的出口是(*)$} {#var exit "%1";#var exit %replace(@exit,"、","|");#var exit %replace(@exit,"和","|");#var exit %replace(@exit,"。","|。");#while (%item(@exit,1)!="。") {#var exit %additem(%trim(%item(@exit,1)),@exit);#delnitem exit 1};#delnitem exit 1;#var temp3 %item(@searchpath,%numitems(@searchpath));#var temp3 %ismember(@temp3,@fangxiang);#var temp3 %item(@fangxiangb,@temp3);#delitem exit @temp3;#if (@where=客店|@where=中央广场) {#delitem exit up};#if (@where=帅府大门) {#delitem exit south}} {exit}
//这里,通过房间的名称,将@exit中不能走的方向删除,比如客店和中央广场删除up方向,帅府大门删除south方向,这样遍历搜索时这些不能行走的方向相当于屏蔽掉了。当乱入到客店二楼或者赏月台时候,先通过房间名称将镖车往down方向赶一步,再开始搜索就可以了
//@where的值通过下面的trigger获取
#tri {^(*)%s-%s$} {#var where "%1";#add roomnum 1}
注十:至于深度优先遍历,实现起来比这个要简单,遍历度数较小时,实际上2种方法在速度上差别不大,有兴趣的同学可以自己尝试写写深度优先遍历
注十一:当乱入步数小于3时,无论是广度优先还是深度优先,速度都很快,特别当仅乱入1步时,搜索在1秒内完成。乱入步数再多一些的话,遍历花费时间就比较长了,因此建议搜索时@degrees设置为2,当找不到大米时采用上一章介绍的办法。事实上不用这个方法,只用上一章介绍的方法也完全可以。
注十二:这个方法不仅可以用来解决乱入,也可以用来小区域搜索店铺伙计。我在自己的机器人中没有采用这个方法,这个方法只是为了本章专门写出来的,此方法改进的余地应该还很大。
高级篇总结:至此,高级篇围绕list变量遍历,已经介绍了多种遍历方法,其中胡一刀和护镖机器人的遍历方法都不止介绍了一种。事实上不仅仅只有文中介绍到的方法,并且文中介绍到的方法也肯定不会是最佳方法,相信肯动脑筋的同学一定能创造出更好的方法。在写整篇文章包括本章的过程中,重新对zmud的各种方法技巧回顾归纳整理,并且从大家的回帖中,都学到了不少东西。