(4.4 17:21更新)zmud721区域遍历、错位后重定向及room to room的一点心得
本帖最后由 digeyes 于 2013-4-4 05:22 PM 编辑4月4号更新在4楼
在下灵鹫宫新手刚500k,换服务器这几天都在折腾机器人。现在还有不少地方的秘密地点不知道怎么去,路径也还没录完,不过基本框架倒是完工大半了。这个机器人旨在制作一个通用的行走、搜索工具以辅助任务机器人的实现。本文包含4部分:1、路径的录制和遍历及指令的衔接2、通过遍历自动记录和使用房间信息3、遍历错位后如何重新定位4、在1、2、3的基础上实现全球定位
路径的录制和遍历及指令的衔接
这方面的内容论坛里有不少文章,不过还是老生常谈下吧。在进入正题之前介绍3个重要变量,后面经常会用到。@nesw : n|e|s|w|ne|se|sw|nw|nu|eu|su|wu|nd|ed|sd|wd|u|d|enter|out@reversenesw:s|w|n|e|sw|nw|ne|se|sd|wd|nd|ed|su|wu|nu|eu|d|u|out|enter@fullnesw: north|east|south|west|northeast|southeast|southwest|northwest|northup|eastup|southup|westup|northdown|eastdown|southdown|westdown|up|down|enter|out分别是基本方向、逆向、方向全名的list,老手一看就知道。关于录制遍历用的路径,最好是起点和终点为同一个地方。逆向行走会有很多麻烦。#alias recordpath {#var recordingpath#loo 1,%numitems( @nesw) {#alias @{nesw.%i} { @{fullnesw.%i} #var %1 %additem( @{nesw.%i}, @{%1}) _nodef path }}#unalias enter#unalias out}这是我用来录制路径的alias,输入recordpath 名称 ,然后像平时那样走路就会自动记录下路径了,比如recordpath yangzhou,就会把你之后的行走路线记录到变量@yangzhou里。最后两行删掉了enter 和out,呵呵,因为某些原因......第二行的@recordingpath作为临时变量用于储存区域名称,其实应该叫做recordingarea的,无所谓了。#alias addp {#var @recordingpath %additem( %1, @{@recordingpath}) _nodef path}addp 用来在路径中添加enter、out和基本路径以外的指令。比如:addp {open door;e} 。指令有空格的或多个命令要用{}括起来。#alias recordingfin
{#loo 1,%numitems(@nesw) {#unalias @{nesw.%i}}}用这个来结束录制。路径录制好了,该说下使用方法了。我定义了两3种用法,goto、walk和bianli。goto就是很单纯的把一个路径从头走到尾:#alias goto {#loo 1,%numitems( @{%1}) {#wa 70#exe @{%1.%i}}#alias gotofin {#unalias gotofin#exe %2} tell @myid gotofin}goto本身没什么好说的,每步的延迟70ms,一口气走到路径结束。最后的#alias gotofin和tell @myid的用处是方便和其它指令衔接。在复杂机器人的行进过程中遇到任务模块衔接时,单纯的用”;”分割指令很容易产生触发冲突和busy、等待的处理上的难题,为了确保在一段指令切实完成后再执行下一部分的指令,最好的办法就是在指令末尾tell自己,通过触发的方式执行下一段指令。goto在走完路径后会告诉自己指令结束,然后通过trigger执行gotofin指令。gotofin在执行goto时定义,运行一次自动删除,非常方便。用于配合的trigger:#trigger {^你告诉*:gotofin$} {gotofin}goto使用方法:goto 路径名 {结束后要执行的指令}例:goto yangzhou {#wa 3000;dance;laugh;haha;hehe;cry}goto可以嵌套,如:goto a {goto b {goto c}}下一个是walk:#alias walk {#if (%1=null) {#var walkcount 0 _nodef path}#if (@walkcount=%eval( %numitems( @{%1})+1)) {tell @myid 行走结束#add walkcount 1}#if (@walkcount<=%numitems( @{%1})) {#exe %item( @{%1}, @walkcount)#add walkcount 1}}其实zmud是没有null的,呵呵,不过这个alias的使用没有问题,懒得改。直接输入walk会重置相关参数,然后一直输入walk 路径名 就可以一步一步走了。手动遍历,方便测试。走完后tell自己行走结束,触发执行bianlifin #alias bianli {#var currentpath @{%1}walk look #alias bianlifin {#unalias bianlifin#exe %2 }}#trigger {^%s这里*的出口是*} {#t+ walktrig#cr}#trigger walktrig {^> *$} {#t- walktrigwalk currentpath}触发式遍历指令用于搜索,格式: bianli 路径名 {完成后指令},需要和两个trigger配合使用。如果把出口信息直接当做走下一步的trigger,会出现找到人时多走一步的问题,需要逆向行走一步,这会产生很多麻烦,比如目标刚好处于路径不可逆的位置,真的很麻烦...... 而用以上两个trigger就绝不会出现这样的问题。可以和goto配合使用。例如:goto a {bianli b {goto c {bianli d}} }这样就可以很方便的实现一个指令走完路径a再遍历路径b再到c再遍历d。当然也可以 bianli a {bianli b {bianli c {bianli d}}},如果goto路上有什么障碍就这么做。触发式遍历在使用时需要配合应对busy及拦路的的trigger,形如: #trigger {谁拦住了你} {#add walkcount -1;kill who} #trigger {那谁死了} {walk currentpath} #trigger {正忙不能移动} {#wa 1000;#add walkcount -1;walk currentpath}注:currentpath是使用bianli指令时赋值的一个临时变量,储存了要走的路径。
呃,开始直接在浏览器里写写了一半崩溃了,我那个郁闷啊。然后word里写了粘贴过来全是乱的,编辑了半天,完了都1点半了...... 剩下的明天再写吧。 继续开工。
在完成goto、walk、bianli 3个alias后,已经可以用它们满足基本的遍历需求了,再来谈谈简化路径。简化路径的用处有2,一是之后的房间到房间功能为了增加效率需要用到,二是在遍历地图找到目标完成任务返回时,需要走完余下的路径以回到起点(前面提过遍历路径起点和终点录在一起),这时候也需要简化路径增加效率。
#alias jianhua {
#if (%len(%2)=0) {#var jianhuaStFrom 1} {#var jianhuaStFrom %eval( %2)}
#unvar jianhuaPath
#loop @jianhuaStFrom,%numitems( @{%1}) {#var jianhuaPath %additem( @{%1.%i}, @jianhuaPath)}
#var jianhuaStFrom 1
jianhua1
}
#alias jianhua1 {
#if (@jianhuaStFrom<%numitems( @jianhuaPath)) {
#if (%ismember( %item( @jianhuapath, @jianhuaStFrom), @nesw)=%ismember( %item( @jianhuapath, %eval( @jianhuaStFrom-1)), @reversenesw)) {
#delnitem jianhuaPath @jianhuaStFrom
#delnitem jianhuaPath %eval( @jianhuaStFrom-1)
#add jianhuaStFrom -1
} {#add jianhuaStFrom 1}
jianhua1
}
}
使用格式:jianhua 目标路径 起始步数,比如 jianhua yangzhou 10,得到一个变量@jianhuapath,它是yangzhou路径第10步到最后一步的简化路径。
起始步数可以忽略,这时默认为1。在和bianli配合使用时,当找到目标完成任务返回时,需要输入 jianhua currentpath @walkcount ,前面贴过的alias bianli里有这两个变量,currentpath 是临时变量用于储存当前遍历的路径,walkcount是计数器记录行走步数。 在简化完毕后,用walk将步数清零,然后bianli jianhuapath走完简化路径回到起点。
到这里已经可以用上面提到的alias制作一些简单的半全自动机器人了,比如慕容任务。所谓半全自动,是因为可以实现自动接任务---到达目标区域---遍历---找到目标---完成任务---返回的功能。但是需要值守,因为不能自动处理因各种原因导致的错位问题,比如长江发大水、泰山甩跟头等。。。运气好最多能正常运行1、2个小时,运气差不到半小时就出错了,这时就需人工干预复位。
最后以慕容任务做个简单的示例介绍如何使用上面提供的工具制作机器人,作为这部分的结尾:
trigger: ^慕容复说道:大燕传国玉玺在(*)附近出现,快去吧! ---- 用这个trigger获得叛徒所在区域信息
#var pantuarea ""
#loop 1,%numitems( @xkxarea) {
#if (%pos( %item( @xkxarea, %i), %1)>0) {
#var pantuarea %item( @xkxarea, %eval( %i+1))
}
用这段来确定叛徒所在区域。@xkxarea是形如 张家口|zhangjiakou|扬州|yangzhou|福州|fuzhou 的list,用于储存你录制过的区域,奇数是中文区域命,偶数是对应的区域路径名,看你录制时是怎么取的了。
#if (%len( @pantuarea)>0) {#exe mr@pantuarea} {
#beep
}
紧跟着这段用来执行任务。#exe mr@pantuarea 这段表示用形如mrfuzhou的形式来完成任务。要从扬州到福州遍历地图然后返回,只需mrfuzhou这么一个alias。
假设从ct出发通过金牛道到成都,然后坐车到福州,遍历完后坐车返回成都再回扬州,那么:
首先需要从ct到成都马车行的路径,假定是ctcd,返回的路径是cdct,然后需要一个通用alias:
#alias mache {gu;qu %1;#alias xiache {#unalias xiache;#exe %2} 用这个alias来坐车。格式:mache 目的地 {下车后执行的指令}
配合 可以下(xia)了 的trigger在下车后执行 xiache 这个alias,这是前面提过的指令衔接的方式。
路上过蜀道时遇到艰难险阻记得设置触发#add walkcount -1;walk currentpath
#alias mrfuzhou {mrbianli ctcd { mache fuzhou {bianli fuzhou {mache chengdu {mrbianli cdct}}}}
有了这个alias在接到去福州的任务时在ct输入mrfuzhou就可以完成任务自动返回了。其它地点当然也如法炮制。
当然别忘记设置触发在找到叛徒后停下来。
#trigger walktrig {^> *$} {#t- walktrig;walk currentpath} 这是前面提到过的触发,找到叛徒后#t- walktrig就停止自动遍历了。
第一部分就到这里结束吧。对于不追求极限的玩家来说,以上的内容就够用了。只要隔段时间看一眼,发现出错手动复位一下就行了。
下一部分是地图信息的录制和使用。 好帖,来插个楼顶一下
不过,弱弱地说一句,慕容任务有1/3的图片,所以即使能跑1、2个小时,也得手动翻译图片…… 本帖最后由 digeyes 于 2013-4-4 05:21 PM 编辑
先占楼,今晚可能没空写了。
回楼上:图片识别还是得手动的。
这篇文章主要是为了解决任务机器人的移动和搜索问题而制作一个通用的工具。慕容任务只是作为一个简单示例介绍如何使用第一部分完成的工具。
崩溃了一次好伤心,重写好累,本来打算今天先不写了,咬咬牙还是坚持住了。。。。
房间信息的记录和使用
前面的遍历和录制固定路径部分因为相关内容挺多的,基本思路也都大同小异,所以比较简略,也没多少注解。这部分开始对思路的说明会比较详细点。在开始之前先抱怨下zmud721的乱码问题,实在是太恶心了,在处理中文信息时会各种掣肘,恶心呐!!!好了进入正题。
前面提过,固定路径的遍历无法处理各种意外因素导致的错位,因其自身不具备重定位能力,一旦任何一步出错路径出现偏差,那么整个机器人就中断了。为在错位后能够重新定位让路径回到轨道上,就需要依赖房间信息。权衡工作量和使用效果,我只考虑房间名和出口信息,忽略房间描述。本文的房间信息是基于已录制好的路径的。在有了房间信息后,通过随机遍历验证房间信息来实现路径的重定位。本文提供的随机遍历的目标有两个:在遍历深度内没有环形路径的区域做到无冗余步数,尽量减少环形路径遍历的冗余。随机遍历在第三部分介绍。 信息录制的思路是:通过遍历自动记录路径中每一步对应的房间名和出口,同时生成判定该步数对应的房间是否是所在路径中有唯一房间名和出口的array。
基本格式示意如下:
pathname: "n|e|s|w"
pathnameroomname: 房间1|房间2|房间3|房间4
pathnameroomexit: n,e,s,w|s,e|w,s|n,e
pathnameisuniq:1|0|0|1
pathname是已经录制好的路径的路径名,命名储存房间信息、出口信息和判定信息的变量时一律在前面加上路径名,如yangzhouroomname。
不过在正式开始录制房间信息前,必须先谈一下乱码处理的问题。先来看一个例子:
#var a 张家口
#var b @a
#var a
#var b
#say @a
#say @b
把张家口赋值给变量 a,然后把变量a赋值给变量b。
#var a 显示:variable:a张家口
#var b 显示:variable:b张家Z
#say @a显示:张家Z
#say @b显示:张家Z
我们把zmud的变量分成两种状态,#var 变量名 显示的是储存值,#say @变量名 显示的是使用值,可以看到变量a的储存值是张家口,这是我们需要的结果,而用@a使用它时就变成了张家Z。把a赋值给b,会看到b无论是储存值还是使用值都是张家Z,它不能正确和"张家口"匹配:%pos(张家口,@b)和%ismember(张家口,@b)的结果都为0。所以在zmud中在处理中文时,间接赋值要慎之又慎。在录制房间信息时,每进入一个房间,都需要抓取房间名和出口并存入list中,为了判断房间和出口信息是否都是唯一的,需要搜索已经记录的数据并进行匹配,英文可以保证正确匹配,但是中文会受到乱码的影响。如:
#var roomname "房间1|房间2"
#trigger {(*)} {
#var roomname %additem(%1,@roomname)
#say %pos(%1,@roomname) 显示:0
}
#var roomname显示:variable :roomname "房间1|房间2|张家Z"
可以看到乱码出现,无法正确匹配。以上的代码如果使用#additem roomname %1添加房间是可以避免乱码的,但是#additem不能添加重复的值到list中,而我们需要的是记录路径里每一步的房间和出口,显然无法满足需求。解决办法就是以毒攻毒,以乱码配乱码:
#trigger {(*)} {
#var temproom %1
#var temproom @temproom
#var %additem(%1,@roomname)
#if (%pos(@temproom,@roomname))
}
将%1赋值给temproom,再自己赋值给自己变成乱码,自我糟践一番后总算可以匹配成功了。这种方法可以应用到各种乱码处理问题上,说白了就是将所有中文信息进行二次赋值转化为乱码后再使用。但是要小心,如果编写的时候在某处忽略了这一步,机器人就无法正确识别了,所以还是很麻烦。以后的中文处理的问题都会用到这个办法,所以花了这么长篇幅来说明,shout !
#trigger saveroomname {^(*)%s-%s$ } {
#if (%len( %1)<20) {
#var currentroom %1
#var currentroom @currentroom
}
}
#trigger saveroomexit {^%s这里*的出口是(*)} {
#var currentexit %replace( %1, "。", "")
#var currentexit %replace( @currentexit, "、", "|")
#var currentexit %replace( @currentexit, "和", "|")
#var currentexit %replace( @currentexit, " ", "")
#var currentexit %sort( @currentexit)
#var currentexit %replace( @currentexit, "|", ",")
#var tempvarnameexit @currentarea"roomexit"
#var tempvarnameroom @currentarea"roomname"
#var tempvarnameisuniq @currentarea"isuniq"
#no %arrset( @tempvarnameisuniq, @walkcount, 1)
#if (@walkcount>1) {
n=%eval( @walkcount-1)
#while (@n>=1) {#if (%ismember( %item( @{@tempvarnameroom}, @n), @currentroom)) {
#if (%ismember( %item( @{@tempvarnameexit}, @n), @currentexit)=0) {
#no %arrset( @tempvarnameisuniq, @walkcount, 0)
#loop 1,%numitems( @{@tempvarnameroom}) {
#if (%ismember( %item( @{@tempvarnameroom}, %i), @currentroom)) {
#no %arrset( @tempvarnameisuniq, %i, 0)
}
}
}
#var n 0
} {#add n -1}}
}
#var @currentarea"roomexit" %additem( @currentexit, @{@tempvarnameexit})
#var @currentarea"roomname" %additem( @currentroom, @{@tempvarnameroom})
walk currentpath
}
上面两个trigger可能看起来有点晕,没办法.......用于抓取房间名和出口,默认对应房间的isuniq 值为真,然后检查当前房间名和出口信息与记录中的是否全部匹配,否则将同名房间对应的isuniq值全部设为0。配合下面的alias使用,这个alias是bianli的变体。
#alias bianlircd {
#var currentpath @{%1}
#var currentarea %1
#var @currentarea"isuniq" %array("")
#t+ saveroomname
#t+ saveroomexit
walk
look
#alias walkend {
unalias walkend
#t- saveroomname
#t- saveroomexit
}
有了这个alias在房间,在路径入口处输入bianlircd 路径名 就可以自动录入房间信息了。
今天到这里吧,以上代码的说明和如何使用房间信息明天再写,再之后是随机遍历和重定位最后是全球定位。哎,好累,主要是浏览器崩溃了一次,然后在word里重写赋值粘贴过来格式又混乱不堪,排版排了半天....
ps: 前面忘了说,本文提供的trigger和alias 不要直接复制后在zmud命令栏里输入,因为通配符%1 %2这种会消失。需要自己在编辑界面新建,然后再把执行部分粘贴到代码栏里
今天开始写房间信息的使用和随机遍历,预计晚上发布。修正了上面的第二个trigger,之前手头没有zmud凭记忆写的,这次直接从zmud里粘贴过来。
4月4日下午更新:
先解释下前面两个trigger。
#trigger saveroomname {^(*)%s-%s$ }:实在没什么好说的。。。抓到的房间名存到temproomname里是为了避免误触发,在第二个trigger里把它赋值给crueentroom使用。不要忘了前面提过的乱码处理问题,这样正好是二次赋值不用担心出现乱码匹配不上。
#trigger saveroomexit {^%s这里*的出口是(*)} : 一大段代码,其实除了中间那段判定是否唯一房间的代码,其余的都是简单的字符串处理和赋值。几个%replace把抓取的出口信息排序后存储为n,e,s,w的形式。判断房间是否唯一的思路是:遍历过程中每进入一个房间,先假定房间唯一,然后用while循环检测记录中每一个和当前房间名匹配的房间,如果匹配,则检测当前房间与匹配到的房间出口信息是否一致,如果有任何一个房间不一致,则将同名房间的isuniq值全部设为0。因为在一个路径里唯一的房间无论在遍历时经过几次,其出口信息都是一致的,所以可以保证isuniq值为1所对应的步数涵盖所有唯一房间。而对于那些非唯一的房间,像什么山路、城墙、大道这些,99%的情况不可能在一条遍历路径里刚好出口信息全部一致,即使出现了,通过路径判定和多次随机遍历也能实现精确定位。细心的朋友会注意到,有的房间虽然房间名不是唯一的,但是在同名房间中出口信息却是唯一的,和唯一房间一样可以起到同样的定位功能,上面的做法却会将它们的isuniq标记为0。这是当时写机器人时的疏忽,这部分的判定被写在了随机遍历部分。。。。不过这个并不是大问题,只要写个循环就能处理了,还是留到后面和随机遍历一起吧。
isuniq判定在这一部分还用不到,它主要是为了给通过随机遍历寻找唯一房间的时候提供支持。通过随机遍历寻找唯一房间,并通过isuniq获得对应的步数,可以让机器人回到正确的遍历轨道上。当然最后一部分的全球定位也需要它的支持。
回到正题,通过上面两个trigger和alias,可以实现自动录入路径内房间的信息。现在初步谈谈如何使用这些信息,更进一步的在随机便利部分介绍。
#alias gotoroom {
#var tempvarnameroom %1"roomname"
#var n %ismember(%2,@{@tempvarnameroom})
#add n -1
#if (@n>1) {
#var temppath ""
#loop 1,@n {#var temppath %additem(%item(@{%1},%i),@temppath)}
jianhua temppath
goto jianhuapath
}
}
}
前面提过,房间信息和步数相对应,而命名规则是 路径名roomname ,所以路径yangzhou对应的房间名list是yangzhouroomname。在路径起点输入gotoroom yangzhou 钱庄,首先从yangzhouroomname中找到第一个值为钱庄的变量,它所在的位置n减1就是从起点到这里需要走的步数。将yangzhou路径的第1到n-1步截取为temppath,用前面贴过的alias jianhua简化路径,最后使用前面提到的goto一口气走到钱庄。bianliroom的alias,自然只要把最后一行 goto jianhuapath 改成 bianli jianhuapath就可以了。
前面谈到的信息录制都是通过遍历时自动处理的,下面这部分则需要一点手动操作。
#alias addnode{
#var areanodevarname %1"node"
#var node %concat(%2,",",%3)
#var node %concat(@node,",",%4)
#var @areanodevarname %additem(@node,@{@areanodevarname})
}
用以上alias来为路径添加节点,路径节点储存在形如:pathnamenode的list中,比如yangzhounode。格式:addnode 所在路径(区域)名 连接路径(区域)名 连接点步数 去目标路劲(区域)的路径名 。 比如要为yangzhou添加到xiangyang的节点(未免混淆提一下,路径名如果是某城市的拼音,那么就是遍历这个区域的路径,也作为区域名使用):
addnode yangzhou xiangyang 50 yzxy
以上指令,为扬州添加去襄阳的节点,节点位于路径的第50步(从起点移动了50步,所以房间数是第51个,不要混淆。)。从扬州到襄阳的路径是 yzxy。显然,也应该为xiangyang添加节点:
addnode xiangyang yangzhou 32 xyyz
为了方便添加节点,还是以yangzhou到xiangyang为例,可使用第一部分介绍的walk。在起点输入walk,然后一直walk yangzhou,直到走到你想添加节点的房间,这时候需要输入的步数是 @walkcount -2
#var n %eval(@walkcount -2)
addnode yangzhou xiangyang @n yzxy
输入以上两个指令即可。然后可以使用前面提到的recordpath来录制 yzxy ,注意yzxy最好走到xiangyang路径的起始处。襄阳到扬州的节点添加也如法炮制。有了以上信息就能轻松实现相邻城市快速旅行的,要实现途径多个城市的远距旅行,只要搜索所有区域的节点建立一个旅行顺序依次执行即可。所以我们需要几个list来记录主要区域的路径名:areahuanghebei 、 areazhongyuan、areachangjiangnan。比如areazhongyuan就包括: "yangzhou|qufu|xiangyang|luoyang"。
#alias travel {
#var tempnodevarname %1"node"
#var nodeat 0
#loop 1,%numitems(@{%1}) {#if (%pos(%2,%item(@{@tempnodevarname},%i))) #var nodeat %i}
#if (@nodeat) {
#var nodeat %item(@{@tempnodevarname},2)
#var tonextpath %item(@{@tempnodevarname},3)
#var tonoderoom ""
#loop 1,@nodeat {#var tonoderoom %additem(@{%1},%i)}
#var currentpath %concat(@tonoderoom,"|",@tonextpath)
jianhua currentpath
bianli jianhuapath %3
} {#say 输入错误或节点不存在}
}
上面的alias travel实现相邻两个城市间的旅行,它是远程旅行的基础。格式: travel 所在区域路名 目标区域名 完成后指令,比如travel yangzhou xiangyang {say over} 。运作方式是:首先查找所在区域节点信息中是否包含目标区域,如是,获得节点信息,截取当前路径从起点到节点位置的路径tonoderoom,与到目标区域的路径tonextpath连接赋值给currentpath,简化后得到简化路径jianhuapath,然后遍历jianhuapath到达目标地点。
今天写到这里吧,休息了。下次开始就是随机遍历了。哦,还有远程旅行如何索引区域节点获得需要的路径,这两个计算上有相似的地方放一起写好了。 回复 3# hijacker
俺忍了半天没敢插楼,这下可以放心顶贴了 版主咋还不来加个精涅 @zgbl 如果把出口信息直接当做走下一步的trigger,会出现找到人时多走一步的问题
digeyes 发表于 2013-4-1 01:32 PM http://pkuxkx.net/forum/images/common/back.gif
在 set brief 2 的情况下,房间的npc信息显示在出口信息之前,就不会出现多走一步的问题了。 回复 7# alucar
学习了,原来还有这种办法! 崩溃了,chrome崩溃了我也崩溃了。写第一部分时也崩溃了一次不过损失不大,后来用word写好粘贴过来排版全是乱的整理半天。这次写了一个多小时一下就没了,晚点再发吧,郁闷。 用个好点的文本编辑器起来写,边写边保存