如何利用数据库制作全自动解谜机器人
作者:八级风
这篇东东献给所有热爱XYJ,热爱Mudfan的朋友。
第一章 制作全自动解谜机器人的途径
在XYJ里,如何快速增长道行,是一个困扰许多玩家的大问题,
PK、NK、打花、解谜……PK要甘冒天下之大不韪;NK到了后期很难找到合适的NPC对象;
打花的确是个好方法,我曾经8小时打了430多年,不过很多站点被cut了,
:(。只有解迷,成了一个最可靠的方法。无论去哪个站点,
只要能熟练掌握解谜方法和技巧,都能较快的成长。
然而,解谜的繁琐和辛苦程度令很多玩家望而却步。解谜手册的熟记,
解谜物品的准备,问谜,解谜,清谜,再问谜,再解谜,精神 高度集中,
键盘敲个不停,就象一只陀螺,疲惫地转个不休。一个全自动的解谜机器人,
无疑是所有痴迷XYJ的玩家的梦想。把解谜变成一件轻松的事情,
留下更多时间享受聊天乐趣,让全自动机器人的梦想成真,就是这篇文章的目的。
据我了解与尝试,在现有的条件下,制作全自动解谜机器人大致
可以走以下几条路:
1、把所有任务列入Trigger中,根据问谜的结果触发,完成解谜任务;
2、利用Zmud本身提供的DDE(Dynamic Data Exchange,动态数据交换)
函数来开发一个外部程序,通过外部程序来控制Zmud的运行,实现自动解谜功能;
3、利用Zmud V5.55及其更高版本提供的Zmudapi.dll来开发一个控制Zmud运行的外部程序。
4、利用Zmud V5.x及其更高版本提供的数据库模块构建解谜数据库,配合Trigger来控制解谜。
下面我们来分析一下各种方法的利弊与可行性。
1、现有的很多所谓自动解谜机器人就是这种类型的。有些是专门做
一种解谜任务的,有些更全一点,最好的好象可以做四种任务。
说到底,这样的机器人不过是简单的触发堆积,Trigger庞大的要命,把机器拖的半死,
完成解谜任务又相当刻板,对于XYJ2000的解谜系统根本不适合的。我认为,
这样的Trigger只能拿来给新手做解谜的辅助与提示,没有更大的实际意义。
2、DDE的支持从Zmud很早的版本就开始了,当然DDE本身也是个老古董了。
对于大家常用的4.62版本来说,是除了方法1之外的唯一途径了。
当年浙大西游记的郭大路(henry)曾经用Delphi开发了一个自动解谜的外部控制程序的雏形。
他之所以选用Delphi开发,理由是:
Zmud是用delphi编写的,所以用Delphi最好,VC太复杂,VB的DDE控件不行,
只有Delphi的DDE控件与它联系起来最方便。
雏形出世的时候我还没有开始玩XYJ,同时这个雏形也没有引起太多人的注意。
所以henry的计划随着他去清华读博夭折了。
用DDE开发自动解谜机器人,无疑比方法1迈进了很大一步,
但是,在开发上的难度是相当大的。
3、从Zmud V5.55开始,ZuggSoft公司提供了一个叫Zmudapi.dll的动态链接库。
这个DLL本来是提供给Zmud用户来开发各种插件(Plugin)的。
我在连到ZuggSoft站点下载Zmud 5.55的时候注意到了它的介绍。
(在Zmud 5.55中还附带了Zmudapi.hlp,这个帮助文件简单介绍了该
DLL的功能与用途。在更高版本里不提供这个帮助文件。)
它提供了对Zmud的命令控制、数据传送(单向)以及其他一些功能的函数。
当时我就决定通过调用这个DLL来替代DDE实现外部控制程序,
可惜,我用VC无论如何也无法正常调用DLL提供的函数。这也促使我转向寻找其他方法。
不过,会用Delphi的朋友可以去试一下,Zuggsoft是推荐用Delphi开发的。
4、我的目光焦点是最后转向这个方法的。从Zmud 5.x开始,提供数据库功能,
而这个数据库对于庞大的解谜系统无疑是十分适合的。
解谜的任务如果不包括玩家送物和玩家拜贤,数目接近2000个,用现成的数
据库来处理要比自己编写专门的数据处理程序要便捷的多,处理速度上也有优势。
虽然Zmud提供的数据库功能并不强大,但对于我们编写自动解谜数据库而言足矣,
控制解谜的其他部分可以用少量的Trigger来实现。
在多次尝试之后,我最终选择了第四种方法。在下一章,我会从开发
解谜机器人的实际过程开始,按我所经历的实践思路跟大家探讨:
第二章 建立解谜数据库
建立解谜数据库的最主要目的是,提供迅速快捷的解谜信息的查询。
另外,还可以利用数据库排序、选择的基本操作,来实现自动选择执行任务的功能,
这点我会在下一章里详细说明。
下面就开始搭建全自动解谜系统的基石——解谜数据库。
在Zmud 5.x和6.x版本中,工具栏里有个DB按钮,通过它进入Zmud的数据库模块。
根据上面列出的两个目的,我建立了两个数据库:一个是静态的数据库Record.db,
一个是动态的数据库Quest.db。
Record.db用来保存所有的解谜任务,里面的内容只能通过数据库的数据管理来做修改,
在自动解谜系统工作的时候,它的内容是不会发生变化的;
Quest.db用来记录当前的解谜任务,总共七条记录,
它的内容随着解谜的进行不断更新,
并且通过建立Quest.db的视图按条件选取下一个该执行的任务。
两个数据库的构造如下:
Record.db
这个数据库的记录有6个字段(Field):
Type(任务类型,该字段为Single Option类型,字段的值为
食物、送物、拜贤、灭妖、武器、盔甲、募捐之一。)
Name (任务名称, 字段类型Text)
Ename(任务英文名称, 字段类型Text)
Extra(任务附加选项, 字段类型Text)
Place(任务地点, 字段类型Text)
Todo (执行该任务的命令, 字段类型Memo)
Type字段将任务的类型加以区分,对应七种不同的任务种类;
Name和Ename字段在不同任务中分别对应食物、武器、盔甲、募捐的物品和送物、
拜贤的人物的中文名及英文名;Extra字段在送物中对应所送物品名称,
在拜贤中对应所询问事宜;Place字段记录任务中人物或物品所在地点;
最后一个Todo字段是最重要的,记录了执行该任务的命令操作。举例如下:
Type Name Ename Extra Place Todo
拜贤 大和尚 he shang 佛学 车迟/金平府/乌鸡 略
在实际操作中,Place字段是可以省略的,但考虑到对新手具有提示作用,把该字段保留了。
建好了Record.db的框架,我们可以把记录向里面添加进去了。
如果用手动一个个填写添加,不但容易出错、遗漏,而且工作量大的惊人。
幸好Zmud的数据库对记录的输入提供了Import的功能,可以直接从文件中读入。
Import可以从两种文件读入记录,一种是普通的txt文件,这种txt文件每行为一个记录,
不同字段用Tab隔开;另一种是csv文件,用逗号(comma)来间隔字段。
实际上,这两种文件的区别只在于间隔 符号的不同,我们无须刻意选择,
因为在Import的时候,可以向Zmud指定说明间隔符号的类型。
为减少工作量,我以源码中的任务列表(/d/kaifeng/npc/quest_*.c)
为基础,把其中无用及重复的任务剔除掉,进行整理补充,将所有记录保存在QuestDB.txt中。
然后将这个txt文件Import到数据库中。在选择完Import文件之后,Zmud将弹出一个对话框,
把各个字段及其相应记录显示在一个预览表格里,如果正确,将自动把文件里的
每个记录添加到Record.db中去。这一步是相当便捷的,
也使我的热情没被添加记录的繁琐给磨灭。:)
建立完成之后,Record.db里总计有大约1800条记录。
Quest.db
我在这个数据库中新建了四个域(Field),其名称与类型分别如下:
Type (任务的类型, 字段类型Single Option)
Name (任务的名称, 字段类型Text)
Cloud(是否有云, 字段类型Boolean)
NotDo(多久未完成该项任务, 字段类型number)
Type、Name的作用与Record.db中的Type、Name一样;Cloud字段是个布尔量,非零即一,
记录对应此任务的云是否存在;NotDo字段记录此任务有多久未做。
我们知道,食物、送物、拜贤、灭妖、武器、盔甲、募捐七种任
务对应的云彩分别为红、青、绿、黄、蓝、粉、白。输?quot;Quest"命令
查看当前任务的时候,可以知道身上有哪几朵彩云。我们可以利用函
数%pos(para1,para2)来得到Cloud字段的值。例如:
“慢慢地一小团红白两色祥云在你的身边升起。”
我们就得到一个Colorlist=“红白”,%pos(红,@colorlist)可
以返回“红”这个字在字符串“红白”中的位置,如果不存在则返回值为零。
因为Cloud字段为布尔量,如果需要查找的字存在于字符串中,则返回值非零,
赋给Cloud就为一;反之则为零。利用这个方法
我们可以得知哪些任务有相应的云彩存在,哪些没有,为选择任务打 好基础。
建好了这两个数据库,我们可以开始构建上层建筑了。后面,我们继续讨论:
第三章 任务选择机制的实现
为了实现自动解谜,我们首先考虑机器人的工作流程。
我们可以知道,选择这一环节至关重要,它就是整个自动解谜系统的大脑。
下面我们来初步实现选择这一功能。
首先,这个流程需要一个起点,在图中“问谜”这一步引起了Quest.db的更新,
从而引发后续的整个过程,相应地,我们从游戏里也要找到这个起点。
我选择的是命令"quests"。
当下命令quests后,会显示当前需要完成的七项任务。我们通过Trigger来获取这些任务,
并将数据传送到Quest.db中。数据库中有个命令#LoadDB,这个命令用于加载一个数据库。
如果这个数据库还没有加载到内存,就从文件中读取并加载;如果这个数据库已经被加载了,
就将它作为当前的数据库激活,那么后面的数据库操作都是在这个数据库中执行。
我们用#LoadDB
命令把当前的数据库置为Quest.db,然后输入"quests"指令。接下去,
下面的Trigger会自动更新数据库内容。
#TR {^????食物:(%x)}
{food=%1;0.Name=@food;0.Nodo=@red}
#TR {^????送物:(%x) (%x)}
{yin=%1;thing=%2;1.Name=@yin;1.Nodo=@cyan}
#TR {^????拜贤:(%x)}
{baixian=%1;2.Name=%1;2.Nodo=@green}
#TR {^????灭妖:(%x)}
{hu=%1;3.Name=@hu;3.Nodo=@yellow}
#TR {^????武器:(%x)}
{weapon=%1;4.Name=@weapon;4.Nodo=@blue}
#TR {^????盔甲:(%x)}
{armor=%1;5.Name=@armor;5.Nodo=@pink}
#TR {^????募捐:(%x)}
{lan=%1;6.Name=@lan;6.Nodo=@white}
其中诸如0.Name之类的变量代表数据库中编号为0的记录的Name字段。
而red、cyan、green等变量是我在Trigger里用来对某个任务未完成次数记数的,
每完成一个任务,对应的任务的记数置成零,其他任务记数加一。
那么,这个数字越大的,就代表越久没有完成。
然后,根据判断Cloud字段的值。
#TR {^????你的身上徐徐飘浮起一小团(%x)祥云。}
{colorlist=%1;
0.Cloud=%pos( 红, @colorlist);
1.Cloud=%pos( 青, @colorlist);
2.Cloud=%pos( 绿, @colorlist);
3.Cloud=%pos( 黄, @colorlist);
4.Cloud=%pos( 蓝, @colorlist);
5.Cloud=%pos( 粉, @colorlist);
6.Cloud=%pos( 白, @colorlist)
}
#TR {^????慢慢地一小团(%x)色祥云在你的身边升起。}
{colorlist=%1;
0.Cloud=%pos( 红, @colorlist);
1.Cloud=%pos( 青, @colorlist);
2.Cloud=%pos( 绿, @colorlist);
3.Cloud=%pos( 黄, @colorlist);
4.Cloud=%pos( 蓝, @colorlist);
5.Cloud=%pos( 粉, @colorlist);
6.Cloud=%pos( 白, @colorlist)
}
这个时候,Quest.db的数据已经全部被更新,我们可以通过下面的方法来选择任务:
1、在数据库Quest.db中建立一张视图,取名为order。order视图按照{Cloud|Nodo}排序,
也就是排序优先级先Cloud,再Nodo。
2、下命令#View,将在Zmud中显示这张视图,排在第一行的就是我们所选取要执行的命令。
3、用Trigger得到该命令。
图例如下
#View order {Cloud|Nodo}
#View
0 食物 鹫肉 - 20
2 拜贤 王孙 - 5
1 送物 孙三叔 - 4
6 募捐 雕木斜靠椅 - 3
5 盔甲 王八甲 - 1
3 灭妖 笑波儿天 - 0
4 武器 月牙弯刀 X 2
(显示"-"代表没有云彩,"X"代表有云彩)
然后,我取得下面要做的任务名为“鹫肉”,将当前数据库切换为Record.db,
用#Query命令对该任务进行查询,获得应该执行的命令。
(关于查询请参考帮助文件,这里不作赘述。)
这样,一个简单的自动解谜机器人的框架搭好了。
但是这样的模型离投入使用的成品还相差太远,需要我们一步步加以优化和调整。
最重要的还是提高“选择”的正确性,因为任务的选择直接关系到奖励的高低即Trigger的效率。
第四章 解谜技巧的应用
为了提高解谜所获得的奖励,我们要合理选择需要完成的任务,
并且将它与Trigger 的可实现性结合起来,也就是说,
判断的条件必须是可以通过Trigger 实现的,否则,
再完美的技巧也无法应用在我们的自动解谜机器人中。
首先,我们假设机器人运行在一种理想状态之下,所有的谜都是可以完成的。
那么我们将遇到下面两种情况:
一、云彩数目小于七朵。这时,下一个要执行的任务应该是没有对应的云彩的那项。
我们知道,解谜的奖励和云彩数目关系密切,云彩越多,获得奖励越多,
机器人效率也越 高。因此,任务的选择首先必须符合倾向云彩增多的原则。
例如:你的身上有青绿黄蓝粉白六彩祥云,那我们可以通过触发器判断得知缺少红色云彩,
即下一个任务应该做猪八戒的食物。做完食物这一项,就可以将云彩累积到最大数目。
二、云彩达到最大数,即七彩祥云。这种情况下我们应
该遵循FDFD(First Done First Do即最先完成最早执行)原则。
让我们先来看看上一章提到的Nodo记数,它的机理如下:
设置七个变量red、cyan、green、yellow、blue、pink、white
分别对应该种云彩对应的任务记数。当完成某项任务,则将 相应变量置零,
其他变量加一。例如:
#Trigger {^????陈光蕊对你说道:多谢这位}
{quest_type=拜贤;green=0;#add cyan 1;#add blue 1;
#add yellow 1;#add red 1;#add white 1;#add pink 1}
这样我们可以通过这七个变量的值了解到某种类型的任务有多久没有完成。
因为云彩的存在是有时限的,自它产生以来,经历8次记数,也就是变量值为9的时候,
这朵云彩就消失了。
所以我们应该优先执行记数大的任务,以保证云彩维持在七彩。
上面提出的两种原则已经在我们的模型中采用了,即按照“Cloud|Nodo”的次序进行排序,
先考虑是否有云,然后考虑做最久未做的任务。但是,在实际解谜中,我们并不可
能完成所有的任务,一定会遇到有些谜无法完成。
所以我们在数据库Record和Quest中引入一个新的字段:CanDo。
这个字段为一,表示该任务可以完成,这个字段为零,则说明是无法完成的。
随着Player的成长,可以手动的对这个字段进行修改。
比如Player早期很难完成一些灭妖以及菩提祖师的拜贤、羊脂玉净瓶等任务,
到了后期,就可以调整为可以执行。
此外,我们对何时清谜做一个考虑。如果因为某些任务无法完成,
只能保持在较少数目云彩下解谜,奖励会降低;
而清谜会造成两个损失,一是随机减少一朵云,二是时间上的等待,
但清谜之后可以抛开那些挡路的任务,为七彩云创造条件。有得必有失,
如何选择清谜时机呢?根据我的解谜经验,六彩以下的任务是不值得做的。
我们可以判断,如果根据排序,最优先的两个任务无法完成,就可以去清谜了。
最后,我们再来考虑解谜任务的权重问题。各个任务的奖励是不同的,
在相同云彩数目下,有的任务比如兵器中的各种法宝和龙子兵器、
募捐中的龙珠等奖励高,有的任务奖
励低。这就牵涉到一个对任务赋以权值的问题。权值对两种情况造成影响:
一是在正常的要清谜的情况下如果有权值
很高的任务未做,可以考虑先做这个任务再清谜;二是当云彩少于七彩的时候,
考虑在没有其他损失的情况下,将权值高的任务放到云彩多的时候做,
以获得更高奖励。权值的设定可以不必在数据库中增加字段,将它与CanDo 字段合并,
CanDo为零,代表无法完成任务,否则表示该任务权重。
好了,经过这样的补充,我们的自动解谜机器人可以算是完成了,
首先具有可执行性,其次也满足了对高效率的要求。
当然,另外还有一些技巧因为很难在机器人中实现,我
们就不在这里赘述了。
第五章 完善与展望(结束篇)
经过前面四篇文章的讨论,我们已经得到了一个可以用的自动解迷机器人,
它包含了一个容纳所有任务的数据库,以及一个用来处理当前任务选择的数据库,
可以实现自动选
择当前任务,自动完成解迷任务的功能。当然还是比较粗糙的,
在运行过程中我们可能会遇到许多非理想状况,干扰机器人的运作,
另外,注意一些细节也可以提高效率。
首先我们注意解迷的等待问题,因为用机器人解迷,动作要比手动迅速的多,
所以从接迷到完成通常只要很短的时间,就需要等待,虽然这段时间一般只有1、2分钟,
但累积起来也不是一个小数目,所以我们要采取的策略是在每完成
一个谜之后立即询问该类型的任务。比如做猪八戒的食物,
把食物给猪以后,去皇宫领赏,然后再ask zhu about food。
由于选择的任务的一般趋势是各种类型任务轮流完成,所以在你完成其他任务的时候,
也度过了等待的时间。在我的解谜机器人里是这样实现的:
设定七个alias分别对应七种任务如askzhu, askchen, askyin等等,
在完成任务的时候有七句
触发如:
#trigger {^???猪八戒对你说道:多谢*} {type=zhu}
然后从皇宫领赏出来,执行alias ask@type就可以询问相
应的任务了。
其次,对于非固定位置的任务,比如送物,灭妖的NPC,要有自动寻找的手段。
这个利用路径是很合适的,例如:得到任务送物:街头小商人--黄金,
我们从数据库中得到方位在宝象,定义一个alias search如下:
#alias search
{
#untrigger {@findname};
findname=%-1;
#tr {@findname}
{#cw red;find=1;#untrigger {@findname}}
}
使用search @yin(yin这个变量的内容就是“街头小商人”)
就生成了一个触发,当#slow .baoxiang沿路径行走的时候,
遇到街头小商人就自动停下来,配合自动给物品的trigger就完成了这个任务。
另外,一个特定任务可能有多个选择方案,在手动解迷过程里,
如果遇到可以在多处完成的任务,我们可以自由选择,
即使一个地方没有,还可以去其他地方尝试。为了模拟手动解迷以提高任务的完成率,
我们也可以加入完成任务的多种方法。因为todo字段是一个memo变量,
我们可以输入很多内容。所以我们增加一个字段,类型为整数,
记录todo中的方法数量,然后依次执行。具体如何实现,请大家思考。
除这些以外,我们还有很多细节要考虑,比如PK(//grin),
比如小小怪的拦路,再比如@#%&^@*$#。这些问题就仁者见仁,智者见智了,
需要技术与经验的结合。不过我的原则是安全稳定第一,
处理问题的方法要简单,这个原则放在机器人制作具体的讲就是宁用alias,
少用trigger。这样说可能有失偏颇,但alias的执行是可以不考虑网速的,
而trigger往往受网速的限制。上次看到一个捡舍利子的机器人,
几乎所有动作都用trigger触发,有些明明可以
用一个alias解决的,也被拆分成了好几句trigger,害处就是第一容易被破坏,
第二机器负担太重,判断触发是相当占用时间的(当然如果你已经用上P4了,当我没说过)。
随着Zmud日益功能强大,我们可以实现的机器人也会越来越多,也会越来越完美。
目前我最看好的是automap,这个功能其实在Zmud 4.62里就有了,
automap结合trigger(其实也是一种形式的数据库结合trigger,因为
automap实际上是一个储存房间记录的数据库)应该会非常强大的。
但我看了很多介绍文章,也从zuggsoft 的网站上下载了资料,就是没有办法搞定,
没有办法正常做语法分析,区别一个房间的名字、描述与出口,
另外绘制地图的时候提供的方向也少了点,只有普通的
south,north,southeast,up,down之类。不知道有哪位已经搞定automap
的,希望指点一下。
好了,我终于结束了婆婆妈妈的废话,最后希望mudfan所有朋友
好运,玩得开心。
|