常言笑的家

Spring, Hibernate, Struts, Ajax, RoR

ZMUD机器人制作(下篇)

第十章 机器人实例--朝廷守门机器人

这是一篇介绍机器人制作方法的帖子,因此尽管你可能不需要守门机器人,还是可以看下守门机器人的制作过程,至少没有坏处不是?
本章注释非常详细,尽管你可能不了解守门任务,仍然能很容易读懂。

建议:做机器人之前先总体规划一下,打算做成一个什么样的机器人.如果你不这样做,很多时候要花费更多的时间来修改,事实上修改比制作更费劲.更糟糕的是,有时候没法修改,你不得不重新做.

机器人的用途:仅仅为了培养朝廷大米,不适用于主id

希望机器人具备的特点:安全,稳定,效率最大化,适用面最大化,界面丰富

安全方面:拥有应付蒙面杀手和盗宝人的安全措施

稳定方面:防发呆,断线自动重连

效率最大化方面:
这是专门为大米写的机器人,守完门之后没有精力帮大米做任务,所以只希望守完门之后经验值尽可能的高,对守门的速度和花费的时间没有要求.每1次守门都力求获得8000左右的经验,由此确定效率a和b
(a):守800次门需要很长时间,不急,慢慢挂
(b):如果出现2个及以上的玩家同时守一个门,会影响守门奖励,此时退出游戏放弃守门
守完门之后有7m多,这个时候再学技能,前200级可以直接学了,高经验高读书写字高内功支持下full技能也更快更省潜能,所以晚上不守门的时候没事可做了.由此确定效率c
(c):天亮时上线守门,守完门下线

适用面方面:没什么好说的,只适用于朝廷.机器人里保存好帐号密码就可以了.

界面丰富方面:设计好status bar,显示出我关心的所有数据.
最终确定为:每次守门杀死壮汉数目@zhuanghan,获得经验@expgrain,当前贡献度@gongxian,当前经验,守门是否成功@success

开始制作

步骤一:先做好主体部分:接任务做任务完成任务下线

#tri {你向多隆打听有关『守门』的消息。} {#t+ job}
//多隆的回答全部取一个class名:job
#tri {这里没有这个人。} {#t- job;#wa 20000;ask duo about job} {job}
#tri {但是很显然的,多隆现在的状况没有办法给你任何答覆。} {#t- job;#wa 20000;ask duo about job} {job}
//多隆死了或者晕了,等待20秒再要任务
#tri {多隆说道:「大人位高权重,已经不用干这种活了。」} {#t- job;#play xxx.wav;#var zhanghao {};#var mima {};quit} {job}
//守门结束了,清空帐号密码阻止自动连线,播放一首音乐轻松下
#tri {多隆说道:「大人需要升迁之后再继续作守门任务。」}
                {#t- job;do 3 s;do 4 w;s;s;ask ming zhu about bonus;ask ming zhu about 升迁;n;n;do 4 e;do 3 n;ask duo about 守门} {job}
#tri {多隆说道:「守门只有早晚两班,你到时间再来吧。」} {#t- job;#timer on} {job}
//上线时间不一定准确,此时打开tick timer以确保不会发呆10分钟掉线,然后等待天色触发
设置tick timer,每300秒输入1个hp命令,仅仅为了保证不会发呆掉线
#tri {你的任务是去(*)守门。} {#t- job;%1} {job}
//注意,这里的做法很巧妙,用任务地点做为命令,请看下面定义的alias,你就会明白了
#alias {东直门} {do 3 s;e;guarding}
#alias {阜成门} {do 3 s;do 7 w;do 3 s;guarding}
//alias是可以取中文名字的,直接用任务地点制作路径alias,可以省去很多#case#if的判断
//鉴于篇幅考虑,不一一写出18个地点的alias了,此文主要介绍机器人的制作方法,请原谅

注:我看到有的人把多隆吩咐任务这句话做成18个trigger,每个trigger单独指定去目标地点的路径和返回的路径,或者为每个门编上号码,然后通过号码来确定去和回的路径,需要用一个很长的#case命令,比我的这个方法复杂了很多很多倍

注:前面章节中曾经提到过能简洁坚决不要复杂,越简洁的东西越稳定;而且一个复杂的大型机器人拥有很多触发变量和别名,简洁一倍,花在修改和测试上的时间可能就节约10倍,至少在本例中假如我guarding拼写错误,我只需要改一个trigger而不是去改18个trigger

注:由于这里采用守门结束下线的方法,所以不需要返回的路径。鉴于有的同学需要返回多隆那里打坐吐纳或者继续守晚上,下面用紫色文字给出返回的实现方法,看是否比你的方法简单
#tri {你的任务是去(*)守门。} {#t- job;%1} {job}
#alias {东直门} {do 3 s;e;guarding;#var fanghui 东直门b}
#alias {东直门b} {w;do 3 n}
#tri {城门的钟敲了两下,你的换班时间到了。} {halt;#wa 3000;halt;@fanghui}
//在去的时候将返回的命令保存在@fanghui里面,回来时全部统一的输入@fanghui就可以了,如果你细心的话,类似方法在第三章中就已经介绍过了

步骤二:把所有天亮的天色描述做成trigger

#tri {【 天色 】风吹起来了,和着东升太阳的万道金光,清晨不似从前那般凉爽} {#timer off;ask duo about 守门} {天色}
//别忘了关掉tick timer,为了篇幅考虑,下面只给出所有的天色描述,trigger的做法完全一样
//最好为所有的天色描述去一个class名:天色,然后单独保存起来便于以后加载
【 天色 】金黄色的太阳刚从东方的地平线上冉冉升起
【 天色 】起风了,树木光秃秃的在风中瑟瑟的发抖
【 天色 】起风了,新发芽的枝叶在风中幽雅的摇弋着
【 天色 】起风了,一片片发黄的树叶,纷纷扬扬随风飘起
【 天色 】秋雨淅沥沥地下了起来,凉丝丝的
【 天色 】如雾的小雨带着春意,飘向每个闲情的窗口
【 天色 】太阳从东方的地平线冉冉升起,放射着耀眼的光芒
【 天色 】太阳从东方的地平线升起了
【 天色 】太阳从东方的地平线升起了。一片红红的朝霞
【 天色 】太阳从东方的地平线升起了,苍白无力地发射着光芒
【 天色 】太阳从东方升起来了
【 天色 】太阳刚从东方的地平线升起
【 天色 】太阳露在东方的地平线上,显示了一天的勃勃生机
【 天色 】太阳升起来了,躲在云层后面呼呼地喘着气
【 天色 】太阳吞吐着万道金芒,跃出地平线,大地一片金黄
【 天色 】太阳在东方的天边吞吐着万道金光,太阳升起来了
【 天色 】天光终于放亮了,风裹着云在天上飞速的跑着
【 天色 】微风带着一缕泥土的芳香吹了起来,天光放亮了
【 天色 】细若牛毛的春雨淅沥沥地下了起来,早起的小鸟欢快地呢喃着
【 天色 】小雪夹杂着雨无声无息的下着
【 天色 】小雨正淅沥沥地下着
【 天色 】雪加着绵绵的小雨无声的下着,不一会地上就湿了
【 天色 】一道耀眼的金光倏的从东方的天边射了出来,一轮红日喷薄而出
【 天色 】一道耀眼的金光倏的从东方的天边射了出来,一轮红日升了起来
【 天色 】一道耀眼的金光倏的从东方的天边射了出来,一轮红日跃出天际
【 天色 】一轮红艳艳太阳刚从东方的地平线升起
【 天色 】又是一个新年的早晨,从东方的天边射了万道霞光,一轮红日跃出天际

步骤三:制作守门过程

#tri {你一叉腰,对身旁的官兵道:*。} {#t+ 守门;#var success 进行中.....;#var zhuanghan 0;#var expgrain 0}
//这句话出现表示守门正式开始,打开守门class,接下来所有的触发都取一个class名:守门
//在定义status bar的时候要用到@success,用来显示守门前,守门成功,守门失败,守门进行中.....,守门被干扰5种情况,方便查看
//@zhuanghan和@expgrain也用在status bar中,用来查看守门过程中杀敌数目和守门奖励
#tri {江湖汉子快步走了过来。} {pancha jianghu hanzi} {守门}
//盘查江湖汉子。白天守门任务有2类,其中之一需要盘查江湖汉子。

#tri {*对着(*)大喝道:挡我者死!} {#if (%1!=你) {#t- 守门;退出}} {守门}
#tri {江湖汉子一言不发,陡然向(*)发难。} {#if (%1!=你) {#t- 守门;退出}} {守门}
//其他玩家也在同一个地方守门,受到干扰,退出游戏

#tri {官兵拦住(*)说道:看告示了没有?您想进带着*进城呐?先问问*大人吧!} {#if (%1!="壮汉") {#var name %1;look}} {守门}
//官兵可能拦住壮汉,也可能拦住玩家蒙面杀手盗宝人,如果是后者,把名字保存到name中,同时用look来抓去玩家蒙面杀手或者盗宝人的id
#tri {@name~((*)~)} {#t- allow;#alarm +2 {#t+ allow};#var id %lower(%1);allow %lower(%1)} {allow}
//除去壮汉之外的所有人全部放行。有时候玩家可能会疯狂闯门,为了避免输入allow命令过多导致守门失败,在这个trigger触发之后短暂的关闭2秒再打开。

#tri {你一脚踢开江湖汉子的尸体,得意地笑了笑。} {#add zhuanghan 1;hp} {守门}
#tri {你踢了一脚} {#add zhuanghan 1;hp} {守门}
//守门任务有2类分别是杀江湖汉子和壮汉,做2个trigger来计算杀敌数目

#tri {你的经验增加了(*)!} {#var expgrain %1;#var success 成功;#wa 8000;#var zhanghao {};#var mima {};halt;#t- allow;#t- 守门;quit;#alarm +690 {#var zhanghao @zhanghao2;#var mima @mima2;#connect}} {守门}
//守门成功正常退出游戏之前,为@expgrain,@success赋值,关掉放行和守门2类trigger
//清空zhanghao,mima阻止自动连线,11分半之后zhanghao,mima的值还原,重新连线

#tri {你本次守门任务看来做得并不成功!} {#var success 失败;#wa 8000;#var zhanghao {};#var mima {};halt;#t- allow;#t- 守门;quit;#alarm +690 {#var zhanghao @zhanghao2;#var mima @mima2;#connect}} {守门}
//这是由于放行次数过多导致守门失败的trigger,尽管这种情况出现可能性非常小,还是必须要考虑到的,将@success的值改变为"失败"

#tri {慢慢地你终于又有了知觉....} {jifaskills;退出} {守门}
//有放行和受伤自动退出为你的安全作双重保障,但是仍然不能100%保证安全,这个trigger有必要加上
//这里jifaskills退出都是alias,退出这个alias用于所有的非正常退出

#alias jifaskills {jifa dodge juemen-gun;jifa staff juemen-gun;jifa parry juemen-gun}
//没什么好说的,死了之后重新jifa
#alias 退出 {#var success 被干扰;#var zhanghao {};#var mima {};#var time [1440-%ctime-30];halt;quit;#alarm +@time {#var zhanghao @zhanghao2;#var mima @mima2;#connect}}
//mud中一天是24分钟,不论你何时退出游戏,只需要等待[1440-%ctime]秒之后就是天亮,这里为了提前半分钟上线,多减了30.退出前将success的值改为"被干扰".

步骤四:仅仅有放行还是不够安全,加入受伤自动退出;如果你已经将挨打的信息都做成了trigger,那么只需要载入就可以了

#TRIGGER {( 你气喘嘘嘘,看起来状况并不太好。 )} {exert recover} {安全}
#TRIGGER {( 你似乎十分疲惫,看来需要好好休息了。 )} {exert recover} {安全}
#TRIGGER {( 你受伤过重,已经有如风中残烛,随时都可能断气。 )} {halt;#untr +@time;退出} {安全}
#TRIGGER {( 你摇头晃脑、歪歪斜斜地站都站不稳,眼看就要倒在地上。 )} {halt;er;#untr +@time;退出} {安全}
#TRIGGER {( 你看起来已经力不从心了。 )} {halt;#untr +@time;退出} {安全}
#TRIGGER {( 你受了相当重的伤,只怕会有生命危险。 )} {halt;#untr +@time;退出} {安全}
//注意:这几个trigger可能有多个被触发,而触发时间略有差异,那么就会同时创建好几个alarm类型的trigger,因此这里创建新的alarm类型trigger之前用#untr +@time删除旧的trigger

步骤五:加入重新连线功能,并且做好守门之前的准备工作,吃饱喝足

#alias {atconnect} {@zhanghao;@mima;yes} {System}
#tri {欢迎来到北大侠客行!} {#var success 守门前;#t- 守门;#t- allow;#t- job;#timer off;#untr +@time;#untr +690;w;s;d;out;draw cloth;draw boots;draw head;draw surcoat;draw armor;remove all;wear all;draw staff;draw staff;wield all;#wa 4000;enter;u;hp;tell @zhanghao 已经全副武装} {准备}
//连线进入之后要做的事非常多,应该关闭的trigger全部关闭,tick timer关闭,没有及时删除的alarm也删除掉,改变success的值为"守门前"
//进入游戏领一套新手装,然后tell自己的id,然后用这句话做触发
#tri {*~(@zhanghao~)告诉你:已经全副武装} {s;s;w;drink;e;n;n;#if (@food<100) {n;n;e;buy ganliang} {#wa 3000;#say 准备就绪}} {准备}
//判断食物,小于100就去买干粮,否则准备就绪
#TRIGGER {你从店小二那里买下了一块干粮。} {#wa 3000;#10 eat ganliang;w;s;s;#wa 3000;#say 准备就绪} {准备}
#TRIGGER {穷光蛋,一边呆着去!} {#wa 3000;w;s;w;qu 30 silver;e;n;e;buy ganliang} {准备}
#TRIGGER {哟,抱歉啊,我这儿正忙着呢*您请稍候。} {#wa 3000;buy ganliang} {准备}
#tri {准备就绪} {#wa 2000;enter shudong;say 天堂有路你不走呀;d;3;ne;ne;u;sw;e;sd;e;e;do 3 ne;n;#wa 1500;do 3 e;u;e;do 4 n;#wa 1500;w;d;do 7 n;#timer off;ask duo about 守门} {准备}


还需要抓取经验,食物和贡献度,直接载入就好了
#TRIGGER {【%s饮水%s】%s(%d)%s/%s%d*【%s经验%s】%s(%d)} {#var drink %1;#var exp %2} {hp}
#TRIGGER {【%s食物%s】%s(%d)%s/%s%d*【%s潜能%s】%s(%d)} {#var food %1;#var pot %2} {hp}
#TRIGGER {贡%s献%s度:%s&gongxian$} {} {score}


步骤六:制作按钮,定义status bar

#button 0 {修改帐号} {#pr zhanghao "你的帐号";#pr mima "你的密码" "*";#var zhanghao2 @zhanghao;#var mima2 @mima}
#button 0 {删除alarm} {#untr +690;#untr +@time}
#st {杀死壮汉【@zhuanghan】个   获得经验【@expgrain】  当前经验 【@exp】 【贡献度@gongxian】  【守门@success】}


整个制作过程结束

中级篇已经全部结束,后面主要介绍遍历。既然是高级篇,怎么也得整些复杂点的东西,恩,更多复杂变态的东西准备奉献给大家,请继续关注,谢谢阅读!




补充章 Path的用法

走路一直是机器人制作中的难题,很多人希望学习path的用法,应大家要求补充此章.尽管现在我已经完全不用path了,但是path还是有一些比较好的用途.

个人认为,462和555版本比较老,Path、Map和DB的功能都不完善,相对于721更属于半成品.大家如果想用好这3个玩意儿,建议还是用zmud721.因此本章主要以zmud721为准来介绍path.

另外本人在zmud555和zmud721中使用Path时至今尚被一个问题困扰,如有高手能解决此问题还请赐教.用#slow .2s3e4u完成慢速行走之后,#path显示出Path Behind:4s6e8u,我所有的方向都一个变俩了.怀疑是slow walk的时候不断将已走过的命令添加到Path Behind中,同时还在为我录制路径,于是每走一步添加2次命令到Path Behind中.这个问题在zmud462中不存在

这个问题导致#back,#retrace都不好用了.到处寻找设置方法或者命令来解决此问题失败,愤而完全放弃path另外寻求方法.由于zmud提供的可用于操作path的命令或者函数都比较有限,我感觉还不如直接从path的"本质"出发另外寻求方法取代path,于是就有了高级篇中我要介绍的方法,不过这属于后话了,下面进入正题.

Path简而言之就是预先录制好的路径,用法自然就包含2个方面录制路径和使用路径

补充.1 录制路径

1.1 录制路径之前需要预先定义方向.

zmud一般有预先定义好的几个标准方向,分别是east,west,north,south,nw,ne,sw,se,u,d.
这10个标准方向是远远不够的,游戏中的方向非常多,不光有最常见的nu,nd,su,sd,wu,wd,eu,ed,enter,out,还有enter shudong,enter boat,climb,jump,ban stone等等,甚至丐帮暗道那里123456789都是方向.

点击菜单View->Directions就可以定义方向了.

一个方向包含4个要素:Direction,Reverse,Commands,Map

Direction为方向的代号,只允许单个的符号.比如字母A-Z,!@#$%^&*()[]{}都可以作为方向的代号。由于方向非常的多,你要做的就是在键盘上寻找各种各样的符号作为方向的代号
Reverse为它的反方向的代号。对于可逆的方向,最好把它的反方向的代号填到这里
Commands就是方向的命令了,比如e,enter boat,ban stone等等
Map在画地图时才有用,画地图时方向就是房间之间的连线。

并不是所有的方向都要定义之后才能用在path里,比如你也可以把(kill dizi)添加到path里.要注意的是对于没有定义过的方向需要加上()才能用在path中。这个做法在zmud462和zmud555中行不通,仅在zmud721中有效。

注:你不仅可以把方向添加到path里,你也可以把诸如(#say 遍历结束)(#say 需要坐船)这样的命令添加到path中,然后通过#say显示的内容制作trigger来坐船或者返回。你还可以将一系列连续的命令包括变量赋值等待建立trigger等都作为一个方向加入,比如(#var xxx xxx;#wa 3000;#tri {} {}),类似这样的技巧请开动脑筋多多运用

1.2 方向定义好之后,录制路径就很简单了。你可以有2种方法录制路径,推荐使用方法B

A:输入#mark命令,然后就在游戏里走路吧,用#path可以查看录制情况。把你要录制的路径走完,用#path查看确认无误,就可以用#path pathname保存起来了,pathname就是你要保存路径的名字。

B:点击菜单Actions->Record Path,弹出一个小窗口,不用管它,接着在游戏里走路就可以了,你走过的方向都以代号的形式出现在那个弹出的小窗口内,路径走完之后点击保存就可以了。

实例一:#path aaa 3n(kill dizi)2e
//这里直接用#path命令新建一个path,path名为aaa,路径是3n2e,表示n;n;n;kill dizi;e;e
//用这种方式新建的path与录制制作的path完全一样

实例二:#path .fff .3n(kill dizi)2e
//跟上例完全一样,只不过path前面加了一个".",表示该path的路径
//为什么这里可以多余的加个"."?其实你要弄清楚path其实就是特殊的alias,当你在命令栏输入.fff或者.3n(kill dizi)2e的时候,命令{n;n;n;kill dizi;e;e}就发送出去了,就好像alias一样。这个是path的快速行走的用法。

补充.2 使用路径

2.1 快速行走

假设已经将路径9n9e9n9e保存到fff中,或者直接在命令栏输入#path fff 9n9e9n9e
实例三:输入命令.fff或者输入.9n9e9n9e
//这2个命令效果完全一样,都是完成了一次快速行走,36个方向命令按顺序同时执行

实例四:上面例子中快速行走命令太多游戏拒绝执行
#direction q "#wa 3000"
//将等待3秒定义为一个方向
#path fff 9nq9eq9nq9e
//每9个命令之间加入方向q
.fff
//作用等同与在命令栏输入#9 n;#wa 3000;#9 e;#wa 3000;#9 n;#wa 3000;#9 e
//对于较长的路径,如果你想快速行走,定义路径的时候要采取本例做法
//当然你也可以不定义方向q,而将(#wa 3000)添加到fff中

2.2 慢速行走

主要用于搜索npc,相关命令#stop,#step,#back,#retrace,#ok,#slow

用之前请参考下图设置,将勾勾去掉,timeout value的值根据网速修改,当你慢速行走时,每个方向命令的间隔时间就是这个值,单位为毫秒。


ZMUD机器人制作_原著:tangguo(下) - icebergzx - icebergzx的博客
新建位图图像 (3).bmp (667.55 KB)
2009-7-28 05:46 AM



#path fff 3n2e
#slow .3n2e或者#slow 3n2e或者#slow fff
//慢速行走3n2e
#pause
//#pause表示行走是成功的并且暂停慢速行走,当你找到npc用这个命令停止。
#stop
//很多人不知道#stop和#pause的区别,#stop表示行走不成功并且停止慢速行走,一般用于被npc挡住或者被系统跘了一下
#step
//#step继续慢速行走。如果你是用#stop停止的,这个命令会从刚才不成功的那步开始直至走完;如果你是用#pause停止的,这个命令会从下一步开始直至走完
#ok
//确认行走成功
#back
//退回一步,网速不好时你会走过头,用这个命令一步步往回搜索吧,也可以用这个慢慢的回家
#retrace fff
//逆向快速行走。fff的路径是3n2e,此命令相当于w;w;s;s;s,帮助你回家
//此命令缺点太多。如果路径包含有非方向,例如(enter shudong) (#wa 3000),行走到这些地方就会中止
//如果#retrace不指定路径单独使用,则逆向行走当前路径。用#path查看,逆向行走时针对的当前路径内容为Path Behind.

这些命令主要用在你的trigger中,被npc或者门挡住你可以#stop然后杀npc或者开门,杀掉npc之后你可以#step走完剩下的路径。当然你也可以直接把(kill npc)或者(open dor)添加到路径里。搜索到盗宝人你可以#pause然后杀掉,任务完成之后你可以#back或者#retrace,也可以#step继续走剩下的。

注一:path只是机器人的脚,trigger才是机器人的灵魂。你制作了很多路径,也需要很多trigger来使用这些路径。trigger做的不好,机器人就会中断。游戏中有很多地方都是制作机器人的障碍,迷宫船挡路npc沙漠有busy的方向(例如杀手帮的台阶)需要跟npc对话的地方(例如ask chuan fu about 出海或者answer 送信)等等,你需要用trigger来解决这些障碍。从制作路径到制作trigger,整个过程需要很多时间才能完成一个遍历机器人,祝你制作愉快,嘿嘿!

补充.3 Path的"本质"

zmud721中有2个函数很好的诠释了path的本质,可惜的是zmud462和zmud555没有这2个函数

在第5章结尾我提到过%ailas()这个函数,作用是将一个alias转化为string
在本章的实例二中说过,其实path就是alias详情可参阅#help.因此我们可以把任意一个path转化为string

实例五:#path fff 3n2e(enter dong)d
#say %alias(fff)
显示为3n2e(enter dong)d

zmud721中才有的2个函数%pathexpand()和%pathcompress()
%pathexpand()和%pathcompress()的参数都不能是path名字或者alias名字,跟%alias()不一样,需要将path名字进行转化才能用到%pathexpand()中。

#path fff 3n2ed
实例六:#say %pathexpand(%alias(fff))
//显示为n|n|n|e|e|d
实例七:#say %pathcompress(n|n|n|e|e|d)
#path ggg %pathcompress(n|n|n|e|e|d)
//显示为.3n2ed,注意"."
实例八:#path fff 3n2ed;#var fff "n|n|n|e|e|d"
#if (@fff=%pathexpand(%alias(fff))) {#say yes} {#say no}
//显示为yes
#say %format(%alias(fff))
//显示为n|n|n|e|e|d

注二:通过实例6,7,8会发现path其实可以与list类型变量相互转化。基于这点,我考虑放弃path而转用list类型变量。path是zmud提供的一个工具,我觉得这个工具功能不完善,定义方向太麻烦,使用起来不灵活,这些也是有些人觉得path不好用的原因。

在第6章结尾曾经说过,第6,7两章是所有章节中最重要的两章,第七章的重要性可能大家已经感觉到了,在高级篇中我会用list类型变量实现path的全部功能,并且能很轻易做到path做不到的事情,个人觉得list类型变量用起来更加随心所欲一些。

高级篇

第十一章 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:如果[email=%1=@step]%1=@step[/email],停留在原地

可以看到这个遍历功能很多,如果你在制作路径的时候记录的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}}

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类型变量和嵌套罢了。

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个小区域都很短,一般为最短路径,整个过程速度非常快。

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

posted on 2011-08-17 17:16 常言笑 阅读(10431) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 

My Links

Blog Stats

常用链接

留言簿(5)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜