MUD-武林MUD资料站-武侠MUD游戏

 找回密码
 注册

QQ登录

只需一步,快速开始

打印 上一主题 下一主题
开启左侧

[教程] 机器人速成(高级篇)

[复制链接]
跳转到指定楼层
1# .
花九黎 发表于 2012-8-17 12:11:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 花九黎 于 2012-8-17 12:13 编辑

高级篇

第十一章 variable和alias的进一步应用

以后将主要介绍我在胡一刀和推车机器人中用到的遍历方法,会出现大量的嵌套alias,也会大量的应用list变量,如果你阅读的比较吃力,请多翻翻前面的章节.

阅读前请确认你已经熟练掌握了以下命令:%item(),%additem(),%delitem(),%numitems(),%ismember(),%dups(),%sort()以及对应的#additem,#delitem,#delnitem

本章用list变量实现path的全部功能,教你做出属于自己的遍历机器人

有些人回帖说看不懂,补充紫色部分,看不懂的话在zmud里试验这5个例子吧
--------------------------------------------------------------------------

#var abc 20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1
%item(n,@abc)表示abc的第n项,比如%item(14,@abc)就是7,  %item(2,@abc)是19
先定义一个变量abc来试验,下面来遍历abc这个变量
A      先遍历2步
#alias bianli {#if @n {%item(@n,@abc);#add n -1;bianli}}
#var n 2
然后输入bianlin,结果就是先执行第2项19,n减1,接着执行第1项20,n再减1,执行了这2项之后n已经被减成0了,#if @n判断不为真,遍历结束
要小心,#alias a {north;;#add n -1;a}这个alias是无限次嵌套,永远不会停止,命令行里输入a的话在将会死机崩溃,因为没有语句让它停止,谨慎!!
B   遍历20步
#alias bianli {#if @n {%item(@n,@abc);#add n -1;bianli}}
#var n 20
在命令行里输入bianli,结果就是1;2;3;4;5;6;7;...;20
C    每遍历2步停止1秒,一直遍历20步
多用一个命令%mod(),是数学里的模,%mod(@n,10)意思是@n除以10后的余数,如果#var n 18,那么%mod(@n,10)的值是8
#if (%mod(@n,2)=0) {say n是偶数} {say n是奇数}
一个判断n是偶数还是奇数的办法
#alias bianli {#if @n {#if (%mod(@n,2)=0) {%item(@n,@abc)} {#wa 1000;%item(@n,@abc)};#add n -1;bianli}}
#var n 20
跟例子B不同的是,直接执行第n项被替代成了先判断奇偶,n是偶数直接执行,n是奇数就wait 1000然后执行
命令行里输入bianli,结果等同于#wa 1000;1;2;#wa 1000;3;4;#wa 1000;5;...18;#wa 1000;19;20
D    执行到第5项的时候中止遍历,用trigger来让它继续
#var abc 20|19|18|17|kill zhang|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1
第5项被替换成了kill zhang
#alias bianli {#if @n {#if (@n=5) {%item(@n,@abc)} {%item(@n,@abc);bianli};#add n -1}}
#var n 20
#trigger 张死了 {bianli}
命令行里输入bianli,结果就是1;2;3;4;5;......;13;14;15;kill zhang,等张死了,继续17;18;19;20

E   执行到第5项时等待10秒再执行,其他的直接执行,用这个方法来坐船
#var abc 20|19|18|17|16|enter boat|14|13|12|11|10|9|8|7|6|5|4|3|2|1
第6项被替换成了enter boat
#alias bianli {#if @n {#if (@n=5) {#wait 10000;%item(@n,@abc)} {%item(@n,@abc)};#add n -1;bianli}}
#var n 20
命令行里输入bianli,结果就是1;2;3;4;...;13;14;enter boat;#wait 10000;16;17;18;19;20

-------------------------------------

11.1 给出一个路径.从ct出发到无量山溜一圈回到ct的所有方向组成的list,用一个变量保存起来.

#var wuliang {s|s|s|s|s|s|s|s|s|sw|w|w|nw|w|e|sw|sw|sw|n|n|n|s|s|s|s|sw|sw|ne|ne|n|sw|wu|nu|sd|ed|nw|n|s|nu|nu|nu|climb stiff|up|climb yafeng|e|se|sw|n|s|sw|sw|sw|s|e|n|w|n|w|e|s|ne|ne|ne|s|push stone|s|e|ed|ed|push men|e|n|s|e|w|s|out|guo qiao|d|s|se|ne|ne|ne|ne|se|e|e|ne|n|n|n|n|n|n|n|n|n}

这个路径的制作方法可以模拟path路径录制的方法,下一章再详细讲,先不管这个.

比path路径要灵活的是,不需要定义方向,可以将任何命令添加到list里面去.这里说的任何命令,不光是上面@wuliang包含的常规方向和非常规方向,类似#var xxx xxx,#wa xxx,#alarm xxx,tell @myid xxx都可以添加进去,甚至可以自己定义复杂的alias和function,然后把alias和function添加进去.总之,没有什么是不能添加的,而这种灵活性将使你的遍历非常强大.

11.2 快速行走

快速行走方法一:
#forall @wuliang {%i}
//这就已经完全模拟了path的快速行走,但是这个模拟跟path的快速行走一样太简单了.上面的路径总共包含98个item,命令输入过多游戏拒绝执行.

快速行走方法二:
#alias bianli {#if (@step<=@steps) {#if (%mod(@step,@bushu)=0) {#wa @wait;halt;%item(@area,@step);#add step 1;bianli} {%item(@area,@step);#add step 1;bianli}}}
//一个简单的嵌套,@step表示你的当前步数,@steps表示要走完的步数,因此@step<=@steps作为嵌套结束的判断条件.这个alias能让你从第@step-1步开始走完第@steps步.路径保存在@area中
//当@step<=@steps时,判断%mod(@step,@bushu)是否为0:如果为0,休息一会#wa @wait之后走一步%item(@area,@step),当前步数加1进入下一次嵌套;否则,直接走一步,当前步数加1进入下一次嵌套
//这里@bushu,@wait用来控制行走的快慢,每次连续走@bushu步就停@wait秒,然后接着走.这里不用具体数字而是用变量来控制行走快慢,可以让你更方便的调节行走速度.

#alarm 00:30:00 {#var bushu 15;#var wait 1000}
#alarm 12:30:00 {#var bushu 8;#var wait 1500}

//用这2个trigger来自动调节行走速度,晚上人少网络流畅,可以走快些,中午过后网络不好可以走慢些.当然自动调整不满意,你可以随时手动调整,或者制作按钮来改变@bushu和@wait的值

无量山的快速行走
#alias 无量山 {#var step 1;#var area @wuliang;#var steps %numitems(@area);bianli}
//将@wuliang保存在@area中,@step值为1表示从第0步开始,@steps值为所有item的个数,表示走完全程
//你只需要在命令行输入"无量山"即可完成无量山的快速行走

注一:可以看到这个方法和path相比强大的多,你可以10步一走,也可以2步一走,也可以走一步停一下(这就已经是慢速行走了),完全可以随心所欲.

细心的同学会发现climb stiff和guo qiao有busy,上述方法有问题.没错,下面做出修改.

#alias bianli {#if (@step<=@steps) {#if %ismember(@step,@busy) {#wa @time;halt;%item(@area,@step);#add step 1;bianli} {{#if (%mod(@step,@bushu)=0) {#wa @wait;halt;%item(@area,@step);#add step 1;bianli} {%item(@area,@step);#add step 1;bianli}}}}
//与前面唯一不同的是先判断当前步数是否属于有busy的步数,如果是等待@time,接着走;否则跟上面完全一样
//有busy的步数保存在变量@busy中

#alias 无量山 {#var step 1;#var area @wuliang;#var steps %numitems(@area);#var time 30000;#var busy 43|79;bianli}
//手动输入#show %ismember("guo qiao",@wuliang)%ismember("climb stiff",@wuliang)显示为7842,所以需要在"无量山"这个alias中添加#var busy 43|79,并且,#var time 30000将busy的等待时间设置为30秒
//如果你的路径中没有这类具有busy的方向,比如丐帮洛阳,你
只需要#var busy {}
//climb stiff和guo qiao命令输入之后都将等待30秒输入下一个命令,可以用下面的trigger调整

#tri {突然你突然脚下踏了个空,向下一滑,身子登时堕下了去。} {#wa 1}
#tri {你终于一步步的终于挨到了桥头} {#wa 1}
//这里利用#wa命令的缺点,用#wa 1来替代#wa 30000,起到加速的作用,不会真的等待30秒那么长的时间,也绝不会等待时间太短,就是刚刚好

事实上,所有的方向无非就是有busy没busy两种(杀挡路npc也可以算是有busy的方向的一种),因此用上面的方法已经可以做出一个跟dammya完全一样的遍历机器人了,还缺少的只是下一章的路径录制方法.

注二:这里将所有的有busy的方向统一处理.事实上,你可以做得更精细一些,分成几类来处理效果会更好

11.3 慢速行走

慢速行走方法一:把上面的@bushu值改为1就是慢速行走了,不过这种行走无法停下来,#stop,#step,#back都无法实现

慢速行走方法二:
#alias bianli2 {#if (@step<=@steps) {#if (@finddbr=0) {#wa @time;halt;%item(@area,@step);#add step 1;bianli2}}
//行走前先判断@finddbr的值,如果为0,表示没有找到dbr,走下一步;否则嵌套结束,停止行走
//不管当前步数有没有busy,统一等待@time,@time在下面的alias中设置为30000

#alias 无量山2 {#var finddbr 0;#var step 1;#var area @wuliang;#var steps %numitems(@area);#var time 30000;#var busy 43|79;bianli2}
//慢速行走之前将@finddbr的值改变为0,表示没有找到dbr

剩下的就是做trigger了

#tri {这里明显的出口是} {#if %ismember(@step,@busy) {#wa 1}}
//成功走出了一步.如果下一步没有busy,用#wa 1加速;否则什么都不做

注意;在无量山,成功的push stone,push men都有相应的描述,都表示成功走出一步,因此都需要做trigger加速.不加速的话也只是浪费点时间而已

#tri {突然你突然脚下踏了个空,向下一滑,身子登时堕下了去。} {#wa 1}
#tri {你终于一步步的终于挨到了桥头} {#wa 1}

//有busy的步数用专门的trigger来加速

#tri {盗 宝 人*@dbrname~(@dbr~)} {#var finddbr 1;#wa 1;大开杀戒}
//找到dbr之后先改变finddbr的值,再加速然后开杀

任务完成返回:

顺向返回:没什么好说的,很简单的返回方法了

逆向返回:
#var fangxiang {n|e|nw|ne|nu|nd|eu|ed|u|enter|s|w|se|sw|sd|su|wd|wu|d|out}
#var fangxiangb {s|w|se|sw|sd|su|wd|wu|d|out|n|e|nw|ne|nu|nd|eu|ed|u|enter}
//2个变量对应的方向正好相反

依次取出前一个方向,通过@fangxiang获得序号,通过序号在@fangxiangb里查找反方向,行走的具体做法我就不写了

注三:无量山这里只能顺向返回.你也可以在返回时加入自己的判断,从而把顺向返回和逆向返回结合起来,从而将效率最大化.

注四:上面只给出了标准方向的反方向获取方法.对于非标准且可逆的方向,都可以相应的添加到上面的@fangxiang,@fangxiangb中.

注五:丐帮暗道2的反方向是sw,但是sw的反方向可能是2,也可能是ne,解决的办法也有很多.实在想不出什么好办法,你也可以为2和se制作专门的alias,然后将alias作为2和se的替代方向来使用.

注五:对于其他的路径,有的需要开门,有的需要给钱老板gold,这些命令都可以作为方向添加到路径里去,慢速行走时作好相应的trigger来加速,不一一细说了

注六:行走失败时,无非是被系统跘住或者被自动叫杀的npc纠缠住或者busy还没结束不能移动,此时当前步数减一继续行走,不详细给出相应的trigger了.

注七:此方向效率比path高很多倍,慢速行走每步之间的间隔几乎没有.如果你用path来做遍历,至少要设置等半秒吧,一个大型区域比如丐帮洛阳凌霄都有几百步,需要几分钟才能走完,一个胡一刀任务的完成时间通常就会超过15分钟.

注八:上面的方法已经实现了#step,#stop,#back,#pause等功能.事实上实现的方法有很多种,上面的方法也肯定不会是最佳的一种.

注九:还是那句话,遍历做起来工作量很大,需要很多trigger来使用路径.

下一章将介绍路径的制作方法,以及完成更多path根本做不到的事情.
第十二章 遍历实现的一种方法

本章继续介绍遍历的制作.先给出路径的制作方法,然后将遍历以及返回的方式多样化.

注:在机器人制作过程中,并不一定要用到本章及随后两章介绍的所有方法。一切以你给机器人设定的功能为基础,兼顾稳定效率简洁选择合适的方法。如果你觉得有些方法实现起来太复杂,你可以简化它,也可以不用理会它或者改进它。本章及随后两章仅供参考

12.1 路径的制作

一个list变量如果手动添加item以及间隔符号"|",不仅浪费时间,也容易出错,参考path的制作过程,制作下面的alias

制作路径之前需要将相关变量清空,为此制作一个按钮

#button 0 #mark {#var area {};#var busy {}}
//这里的按钮相当于path路径制作#mark的功能,所以取名为#mark,路径临时保存在@area中,busy方向的序号保存在@busy中

如果你把有busy的方向分得更精细,你还需要更多的变量来保存这些方向的序号,请一并清空

#alias g {%1;#var area %additem(%1,@area);#say @area}
//定义一个alias,在走路的同时将方向添加到@area中,并且显示出@area的内容

有了这个alias,你只需要用g s,g e,g "enter boat",g "aliasname"这些命令在游戏里走路就可以了.在你走路的同时,s,e,enter boat,aliasname都自动添加到变量@area中了,并且同步显示出变量@area的内容.

#var pathname @area;#var area {}
//路径制作完毕之后,保存到pathname中,同时清空@area准备下一条路径的制作

再用下面的按钮辅助就更省事了
#button 0 记录busy方向 {#var busy %additem(%numitems(@area),@busy)}
#button 0 显示busy方向 {#say @busy}
//碰到有busy的方向,轻轻点击一下第一个按钮.等整个路径走完之后点击第二个按钮就可以知道哪些步数有busy了.在保存路径的同时顺便将路径的遍历方法一并写好.

注一:你可以边走路边制作trigger,例如上一章中快速行走慢速行走需要的trigger都在走路的时候就作好,将会节约你大量的时间

注二:整个路径走完,与该路径有关的alias,variable,trigger就已经全部制作好了,当场测试一遍确认无误就可以开始下一条路径的制作!

为了让遍历的方式更加多样化,在制作路径的时候可以记录更多的内容,本章的第2小节将会用到.
#button 0 记录关键地点 {#var room %additem(%prompt("","关键地点"),@room);#var roomnum %additem(%numitems(@area),@roomnum)}
#button 0 记录关键npc {#var npc %additem(%prompt("","关键npc"),@npc);#var npcnum %additem(%numitems(@area),@npcnum)}
//用到函数%prompt(),跟#pr类似,不过这个函数不会将你输入的内容赋值给变量,而是将你输入的内容作为函数的值.
//在录制路径的时候,点击这2个按钮,会弹出一个对话框,让你输入"关键地点"或者"关键npc",你可以为当前地点或者这个房间的npc取一个名字,名字被保存在@room或@npc中,当前地点的编号自动保存在@roomnum或@npcnum中
//比如你在发呆室,可以点第一个按钮输入"修理装备"来保存修理装备的地点
//你在胡一刀那里,可以点第二个按钮输入"老胡"来保存胡一刀的位置

12.2 遍历的多样化

步骤一:请大家自己动手完成这一步,用上一章的方法,写好针对@area的遍历,包括顺向快速行走,逆向快速行走,顺向慢速行走,逆向慢速行走

为了下面叙述方便,假设你已经完成了上一步,顺向快速行走的alias是fastwalk,逆向快速行走是fastwalkb,顺向慢速行走是slowwalk,逆向慢速行走是slowwalkb

这些alias中用到的变量有:
@step--表示你的当前位置,在起点位置为1,在终点位置为%numitems(@area)+1,依次类推
@steps--表示行走的终点位置,即顺(逆)向快(慢)行走停止之后你所在位置应该为@steps
@busy--有busy的方向在路径中的编号

注三:有兴趣动手完成这个步骤的同学,可以在做好这4个alias之后测试一下,一定要确保行走停止之后@steps和@step的意义与上面所述相同,如有差异请稍作修改。对list变量的遍历感到陌生的同学,这会是一个很好的锻炼

步骤二:用遍历行走的时候,不管你是什么原因停了下来,你的trigger都应该保证@step的值是你当前所在的位置

注四:由于某些trigger无法判断的原因,导致@step中的值不是你当前所在的位置,我们称这一现象为"乱入",这在胡一刀和护镖任务中都可能发生,解决办法留待后面2章介绍,现在暂时不管这个。

步骤三:制作功能更强大的遍历alias

#alias fastgoto {#if %isnumber(%1) {#var steps %1};#if %ismember(%1,@room) {#var steps %item(@roomnum,%ismember(%1,@room))};#if %ismember(%1,@npc) {#var steps %item(@npcnum,%ismember(%1,@npc))};#if (@steps>@step) {fastwalk} {fastwalkb}}
//快速行走的alias,拥有一个参数,这个参数可以是一个数字,也可以是房间的名字,也可以是npc的名字
//%isnumber()这个函数可以判断是否为数字,如果是直接赋值给@steps
//如果参数时房间的名字或者npc的名字,将房间的编号赋值给@stpes
//通过比较@step和@steps的大小关系选择是顺向行走还是逆向行走

fastgoto 修理装备:
从任意地点走到发呆室修装备
fastgoto 老胡
从任意地点走到胡一刀那里
fastgoto [@step+1]:
从任意地点顺向走一步
fastgoto [@step-1]:
从任意地点逆向走一步
fastgoto 1或者fastgoto [%numitems(@area)+1]:
从任意地点走到起点或者终点
fastgoto %1:如果%1=@step,停留在原地
可以看到这个遍历功能很多,如果你在制作路径的时候记录的npc或者房间名称很丰富,你的行走将随心所欲

此命令有2个缺点:
(1)无法判断路径是否可逆
(2)路径是全区域的遍历,因此行走过程中可能要走很多不必要的步数

注五:下一章会修改这个alias,将所有的情况用一个alias来完成.如果可逆就逆向行走至目的地,如果不可逆就顺向返回起点再顺向行走至目的地;并且在不需要遍历的时候将路径简化,只走最短路线

慢速行走完全类似
#alias slowgoto {#if %isnumber(%1) {#var steps %1};#if %ismember(%1,@room) {#var steps %item(@roomnum,%ismember(%1,@room))};#if %ismember(%1,@npc) {#var steps %item(@npcnum,%ismember(%1,@npc))};#if (@steps>@step) {slowwalk} {slowwalkb}}


MUD - MUD游戏 - 文字MUD - 武林MUD - 长期、稳定、高速、互助、活跃、更新的武侠MUD站点,一起MUD吧!
2# .
 楼主| 花九黎 发表于 2012-8-17 12:15:39 | 只看该作者


12.3 遍历结束方式的改进

拿上一章的两个例子来说

快速行走;#alias bianli {#if (@step<=@steps) {#if (%mod(@step,@bushu)=0) {#wa @wait;halt;%item(@area,@step);#add step 1;bianli} {%item(@area,@step);#add step 1;bianli}}}
慢速行走;#alias bianli2 {#if (@step<=@steps) {#if (@finddbr=0) {#wa @time;halt;%item(@area,@step);#add step 1;bianli2}}

基本上遍历就靠这这2个alias完成,但是遍历的目的不一样,有可能是为了返回,有可能是为了逆向或者顺向一步步搜索,遍历结束也分达到目的或者未达到目的2种情况,导致判断下一步行动的trigger作起来并不容易,甚至根本无法判断遍历是否已经结束,因此要作些改进.

改进的快速行走:#alias bianli {#if (@step<=@steps) {#if (%mod(@step,@bushu)=0) {#wa @wait;halt;%item(@area,@step);#add step 1;bianli} {%item(@area,@step);#add step 1;bianli}} {#exe @action}}
//只在嵌套结束之后加上#exe @action

你可以在各种trigger中先改变@action的值再使用遍历,比如
#var action "tell @zhanghao 出发寻找盗宝人"
#var action "tell @zhanghao 返回结束"
#var action "#say 开始逆向搜索"
//通过这些结束信号作trigger就容易多了。

有的区域过于庞大,如果做成一个路径的话,像洛阳丐帮北京等区域都会超过300步,整个遍历下来非常费时间影响效率,因此最好分成几个小路径。
这就涉及到多个路径的连续遍历,如果遍历目的达到,直接返回;否则继续遍历下一个路径。多个路径连续遍历的具体方法将在第14章中介绍。

本章结束,下章介绍路径的逆转和简化,简单说说胡一刀和推车全自动实现的障碍及应对办法


第十三章 胡一刀和护镖全自动分析

继续前一章关于遍历的一些技巧,有的时候需要获得一个路径的返回路径,有时候需要走最短路线以提高效率,这就是路径的逆转和简化.

注一:本章的例子如果看不懂,可以将复杂的部分分解成小部分慢慢理解,也可以直接将代码复制到zmud命令栏中进行试验验证.本章就不过于罗嗦注释了,如果掌握了方法,这些代码写起来还是很简单的.

13.1 路径的逆转和简化

逆转就是将路径倒过来,并且每个方向都替换成逆方向.

#var fangxiang {n|e|nw|ne|nu|nd|eu|ed|u|enter|s|w|se|sw|sd|su|wd|wu|d|out|open door}
#var fangxiangb {s|w|se|sw|sd|su|wd|wu|d|out|n|e|nw|ne|nu|nd|eu|ed|u|enter|open door}
//这2个变量只保存了常规的可逆的方向,如有其它可逆的方向都可以添加进去,只需要注意2个变量对应的方向正好相反就可以了
//不属于上述2个变量中的方向,默认为不可逆.路径不可逆时只能继续遍历到终点

#alias reverse2 {#if (@step>0) {#if %ismember(%item(@path,@step),@fangxiang) {#var path2 %additem(%item(@fangxiangb,%ismember(%item(@path,@step),@fangxiang)),@path2);#add step -1;reverse2} {#say 路径不可逆}} {#var path @path2;#say 路径逆转完毕}}
//逆转路径的alias写起来很简单,将你要逆转的路径保存在@path中,从@path的最后一个item开始依次取出每个方向,如果可逆则获得反方向添加到@path2中,否则"#say 路径不可逆"并且终止逆转

#alias reverse {#var path2 {},#var step %numitems(@path),reverse2}
//初始化变量@path2和@step,然后启动reverse2开始逆转

上面2个alias就完成了逆转,逆转的对象是@path.

使用逆转的方法:
要逆转某个路径@abc,输入命令#var path @abc;reverse.如果路径可逆,则将逆转后的路径保存在@path中并且输出信息"路径逆转完毕";否则输出信息"路径不可逆"并且@path中的内容不变.你可以分别用"路径逆转完毕"和"路径不可逆"制作trigger继续下一步行动.

简化就是判断路径中相邻的2个方向是否互为可逆,如果是则同时删除.

实例:
#var xyscq {n|n|w|w|w|e|e|n|s|s|e|w|n|e|n|n|n|s|s|s|e|n|w|e|s|s|n|e|s|e|s|n|w|nw|se|w|e|s|e|w|w|e|s|n|n|n|e|n|s|s|n|e|n|s|s|n|e|w|w|w|n|se|e|w|nw|w|e|e|u|d|w|n|w|u|d|e|e|u|e|w|u|d|d|w|n|n|n|nw|n|s|se|n|n|n|n|过河|n|e|w|w|u|d|e|n|n|w|nu|sd|w|nu|sd|e|e|ne|ne|n|s|sw|sw|n|n|e|w|n|n|n|n|w|e|n|w|e|n|w|e|e|w|n|e|n|s|e|n|s|e|w|w|w|n|e|w|n|e|w|w|e|n|s|s|s|w|n|s|s|n|w|s|n|n|s|w|w|w|w|e|e|e|e|e|e}
//这个变量中保存了从林震南那里一直到襄阳史春秋附近的遍历路径,沿途各个分叉口全都遍历到了

搜寻伙计可以遍历这个路径,但是返回仍然用这个路径则会浪费很多时间,需要写一个alias获得从起点到终点的最短路径

#alias jianhua2 {#if (@step>=%numitems(@path)) {#say 路径简化完毕} {#var num1 %ismember(%item(@path,@step),@fangxiang);#math step2 @step+1;#var num2 %ismember(%item(@path,@step2),@fangxiangb);#if (@num1=0) {#add step 1;jianhua2} {#if (@num1=@num2) {#delnitem path @step;#delnitem path @step;#add step -1;jianhua2} {#add step 1;jianhua2}}}}
#alias jianhua {#var step 1;jianhua2}
//这2个alias就完成了路径@path的简化,简化结束后输出信息"路径简化完毕"

#var path @xyscq;jianhua
//将@xyscq赋值给@path,然后简化它

#say @path
//显示为n|n|e|e|n|n|n|n|n|n|n|n|n|过河|n|n|n|n|n|n|n|n|n|n|n|n,得到从林震南那里到襄阳城中心的最短路径

注二:如果要获得从起点到当前位置再到终点的最短路径,只需要对上述方法进行修改即可.一个比较简单的方法就是:先将路径@path中当前位置的方向替换成myaddress,并且将该方向保存到@myaddress中,由于"myaddress"不可逆,因此简化的时候不会删除myaddress.简化结束后将路径@path中的myaddress还原或者新建一个alias:#alias myaddress {@myaddress}都可以。

注三:护镖任务的路径,路径都很简单,方向都很规范,用逆转和简化的时候不需要考虑可逆不可逆的问题。胡一刀的路径就要复杂些,很多特殊的方向和不可逆的方向,还有很多有时候可逆有时候不可逆的方向,在使用逆转和简化时就需要特别注意了,一般主要用于洛阳北京襄阳丐帮扬州这类路径较长并且方向规范的区域;如果硬要用于胡一刀的全部路径,也不是不可以,只是制作路径和定义可逆方向的时候复杂很多罢了。

注四:逆转和简化只是2个将路径"变形"的方法,每个人写机器人的方法都不一样,请参考使用。对路径的"变形"使用不仅仅只有这2个方法,如有需要,完全可以自己写一些其它的alias来使用路径。用list类型变量来制作遍历的优点就在这里,一个路径拿来,想怎么玩就怎么玩,这是path办不到的,只是要求能熟练的应用和list类型变量和嵌套罢了。


3# .
 楼主| 花九黎 发表于 2012-8-17 12:16:41 | 只看该作者
花九黎 发表于 2012-8-17 12:15
12.3 遍历结束方式的改进

拿上一章的两个例子来说快速行走;#alias bianli {#if (@step

13.2 胡一刀和护镖全自动

对于这种复杂的大型机器人,需要克服的障碍很多,运行过程中各种意外会经常出现,想一次性达到完美是不可能的。写机器人之前,不妨先大体考虑下主要会出现哪些问题,哪些是容易解决的,哪些是比较困难的,哪些是不可能解决的,该放弃的就适当放弃,比较麻烦的地方以后慢慢完善,毕竟最终的目的是要全自动。

搜索盗宝人

可以用慢速行走,第11章中的方法就可以完成。用这个方法时建议set brief 2,"这里明显的出口是 south 和 out。"类似这样的信息会出现在盗宝人的后面,用这些信息作trigger可以正确判断是继续下一步还是停下杀盗宝人。杀完之后的采用顺向返回,为了提高效率,返回时最好简化路径。不必追求完美的简化获得最短路径,非标准的方向都默认为不可逆,只简化掉标准的方向即可。相比完美的简化,仅仅就是多浪费0-4秒钟在返回的路上,但是实现起来却容易得多。

也可以用快速行走,优点就是搜索盗宝人的速度更快。由于主id用快速行走来搜索,肯定会走过头的,路过npc的时候也会导致盗宝人叫杀,盗宝人叫杀之后就会乱跑,因此推荐用大米快速搜索。快速搜索盗宝人需要通过数房间名出现的次数或者数"这里明显的出口是 south 和 out。"这类信息出现的次数得到盗宝人在路径中位置。如果坐船杀挡路npc或者开门也是路径中的方向的话,数数的时候也要作调整,一定要确保最后得到的结果是正确的。主id通过盗宝人的位置快速行走到盗宝人那里就可以了。这种方法也有缺点,被系统跘住或者被主动叫杀的路人甲纠缠住这类情况总是会发生的,如果是大米退出重新搜索就可以了;如果是主id,可以让大米再搜索一次,同时搜索自己和盗宝人。

对于第十一章中给出的快速行走也可以稍作修改

#alias bianli {#if (@step<=@steps) {#if %ismember(@step,@busy) {#wa @time;halt;%item(@area,@step);#add step 1;bianli} {{#if (%mod(@step,@bushu)=0) {#wa @wait;tell @zhanghao 继续快速行走} {%item(@area,@step);#add step 1;bianli}}}}
#tri {你告诉*:继续快速行走} {%item(@area,@step);#add step 1;bianli}
//对%mod(@step,@bushu)=0的情况作了修改,此时嵌套暂时结束并且tell自己一句话,用这句话作触发继续嵌套,可以完全避免网络lag时命令堆积过多服务器拒绝执行的情况。

有些迷宫也是可以搜索的,比如大沙漠
#var shamo {w|heshui|w|heshui|w|heshui|s|heshui|w|heshui|w|heshui|w|heshui|s|heshui|w|heshui|w|heshui|w|heshui|w|heshui|w|heshui|s|heshui|w|heshui|w|heshui}
#alias heshui {#10 drink jiudai}
如果遍历星宿没有找到盗宝人,可以用慢速行走遍历大沙漠,酒袋里装满水去沙漠起点就可以开始遍历了,每个"heshui"方向都有busy。类似这些地方,初次制作机器人的时候最好直接放弃,以后再慢慢修改添加完善。

护镖

这个就比较简单了。由于路径都很简单,只包含标准方向,可以直接从林震南那里遍历搜索店铺伙计,也可以先将镖车推到伙计附近的某个固定地点,如果沿途没有看到店铺伙计,再搜索伙计附近的小型区域。搜索结束后将路径中伙计后面的方向全部删除,剩余部分简化就得到从镖车到伙计的最短路径,推过去就可以了。无论是大米搜索还是主id搜索,也无论是大米贴身跟随还是呆在家里,实现起来都没有难度。

逆转和简化在护镖机器人中用起来很方便,没有什么需要注意的地方。并且路径也不用做成封闭式的(即从起点出发溜一圈回到起点),可以自己写一个返回alias,从当前位置依次往前执行每个方向的反方向直到回到起点。无论在什么地方,只要没有迷路(此时@step的值就是当前所处位置在路径中的编号),将@step后面的方向删除,逆转简化顺序快速行走就返回了。

推车子的时候使用的路径也是类似{e|e|e|n|n|ne}这样的list变量,定义2个变量
#var fangxiang {n|e|nw|ne|nu|nd|eu|ed|u|enter|s|w|se|sw|sd|su|wd|wu|d|out}
#var fangxiang2 {north|east|northwest|northeast|northup|northdown|eastup|eastdown|up|enter|south|west|southeast|southwest|southdown|southup|westdown|westup|down|out}
用gan che to %item(@fangxiang2,%ismember(%item(@path,@step),@fangxiang))命令推车就可以了

解决乱入或者迷路的万能办法

无论是护镖中镖车被劫匪移位,还是胡一刀中在杀盗宝人的时候盗宝人到处乱跑,或者是其他的意外情况,都导致一个结果,就是@step的值不再是你在路径中的位置,将你所在区域告诉你的大米,让他来搜索你,重新更正@step的值或者直接更正路径都可以。

要求你有从林震南到店铺伙计那里的完整路径,沿途每个分岔口都要覆盖到,以确保乱入3步以内能被大米搜索到。

如果乱入到沙漠黑店ct上面的赏月台这样的地方,这些位置可能不在路径中,大米是搜索不到的,寻求大米帮助之前先判断下地名就可以了,如果是这类地方,推到路径中之后再向大米发出求助信息。

对于乱入,一般只是移位1-2步,比较简单,下章再介绍2个解决办法供参考。

注五:大概想到的困难也就这些,如果大家还有什么困难不能解决可以回帖说明,如果我有解决的办法会修改补充进来。

注六:建议大家在制作机器人之前不要想将所有困难一网打尽,一次性考虑所有情况只会使你做不下去,你的机器人就夭折了。对于一些细小的地方,该放弃的就放弃掉。比如为了鳌拜下面的密室这一个房间专门写很多trigger进去搜索是没有必要的。全自动做任务肯定没有半自动手动操作的成功率高,但是机器人胜在反应速度比人快,胜在不知疲倦,这些优点也足以忽视它的不足了。先保证机器人的效率和稳定,再慢慢提高机器人完成任务的成功率,对于麻烦的复杂的情况建议暂时做放弃处理,大不了退出重新连线开始下一个任务嘛!

下一章介绍多个路径的连续遍历方法,另外提供2个解决乱入的办法供大家参考!

第十四章 实际例子--解决乱入的2种办法

鉴于有人给我发消息推荐一些能对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个小区域都很短,一般为最短路径,整个过程速度非常快。


4# .
 楼主| 花九黎 发表于 2012-8-17 12:17:36 | 只看该作者
花九黎 发表于 2012-8-17 12:16
13.2 胡一刀和护镖全自动对于这种复杂的大型机器人,需要克服的障碍很多,运行过程中各种意外会经常出现, ...

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的各种方法技巧回顾归纳整理,并且从大家的回帖中,都学到了不少东西。

Archiver|武林MUD资料站 ( 鲁ICP备17038480号 本站关键词:mud 武林mud mud游戏 文字mud

GMT+8, 2024-4-26 04:25

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表