活跃技术板块之二:应对地图变化的常用函数。
虽然我的水平不高,但久病成医,之前发过一篇帖子来介绍任务中常用的函数http://pkuxkx.com/forum/viewthread.php?tid=41060&highlight=%2Bnrm
这次呢,再发一篇,活跃活跃死气沉沉的技术板块。
由于最近的地图变化较多。
考虑到有一些新人可能应付起来会比较难。
在这里分享几个思路和部分代码。
需要具备一定lua基础。(初学者请先自学神灯教教材)
总共分成三部分来讲吧
一、关于出口顺序的变化
大家应该发现了最近的房间出口显示顺序会变化,
比如 east;south;west。那么再look可能会变成south;east;west。
由于逍遥行最初的出口比对是字符串对比,因此会造成房间无法识别。
那么解决的方案呢,就是将出口存成table,然后比对两个table内容是否相同就可以了,不考虑顺序。
用到的两个函数:
function simTableIndex(str,list)-----这个函数用来判断list这个table中是否存在str这个字符串。
for i,value in pairs(list)do
if (value==str) then
return i
end
end
return -1
end
function table_is_equal(list1,list2)-----这个函数用来判断两个内容为字符串的table,元素是否相等。
local result=-1
for i,value in pairs(list1) do
result=simTableIndex(value,list2)
if result==-1 then return false end
end
for i,value in pairs(list2) do
result=simTableIndex(value,list1)
if result==-1 then return false end
end
return true
end
如果你用的是小刀版的逍遥行,那么这两个函数是现成的。
只需要在 locate()函数中 对出口判断的部分改为
if table_is_equal(cur_room_exits,tb_db_exits) then
exits_is_same=true
end
北大侠客行MUD,中国最好的MUD 二、关于出口字符的变化
这是新出现的情况。如上例east;south;west。不但出口顺序会变,字符顺序也可能会变。
也就是说,有可能显示为 east;wset;sotuh。
这样一来,两个table的元素并不匹配,单纯的使用上例的table判断函数不能达到目的。
那么我们需要再增加两个函数。
function string_is_similar(str1,str2)------判断两个字符串内容是否相同
local st1_tb={}
local st2_tb={}
for i=1,string.len(str1) do
table.insert(st1_tb,string.sub(str1,i,i))
end
for i=1,string.len(str2) do
table.insert(st2_tb,string.sub(str2,i,i))
end
local result=table_is_equal(st1_tb,st2_tb)
return result
end
我们把字符串拆成一个一个的字符来比对,如果相同就认为两个字符串“相似”
这样一来 east 和 aest 可以匹配了。
那么接下来,就是需要判断连个table中元素是否相同,如果不相同再判断一次字符内容是否相同。
如果字符内容相同我们也认为这两个table元素是匹配的。
这样一来“east;south;west。”和“east;wset;sotuh。”就能够匹配了。
function table_is_similar(tb1,tb2) ----判断两个table元素是否相同或相似。
local result=-1
for i,v in pairs(tb1) do
local tem=-1
for index,value in pairs(tb2) do
if tb1==tb2 or string_is_similar(tb1,tb2) then
tem=1
end
end
if tem==-1 then
result=1
end
end
for index,value in pairs(tb2) do
local tem=-1
for i,v in pairs(tb1) do
if tb1==tb2 or string_is_similar(tb1,tb2) then
tem=1
end
end
if tem==-1 then
result=1
end
end
----------------------
if result==-1 then
return true
end
return false
end
那么在逍遥行中,只需要将出口判断部分再修改为
if table_is_similar(cur_room_exits,tb_db_exits) then
exits_is_same=true
end
就可以了。 二、关于出口字符的变化
这是新出现的情况。如上例east;south;west。不但出口顺序会变,字符顺序也可能会变。
也就是说,有可能显示为 east;wset;sotuh。
这样一来,两个table的元素并不匹配,单纯的使用上例的table判断函数不能达到目的。
那么我们需要再增加两个函数。
function string_is_similar(str1,str2)------判断两个字符串内容是否相同
local st1_tb={}
local st2_tb={}
for i=1,string.len(str1) do
table.insert(st1_tb,string.sub(str1,i,i))
end
for i=1,string.len(str2) do
table.insert(st2_tb,string.sub(str2,i,i))
end
local result=table_is_equal(st1_tb,st2_tb)
return result
end
我们把字符串拆成一个一个的字符来比对,如果相同就认为两个字符串“相似”
这样一来 east 和 aest 可以匹配了。
那么接下来,就是需要判断连个table中元素是否相同,如果不相同再判断一次字符内容是否相同。
如果字符内容相同我们也认为这两个table元素是匹配的。
这样一来“east;south;west。”和“east;wset;sotuh。”就能够匹配了。
function table_is_similar(tb1,tb2) ----判断两个table元素是否相同或相似。
local result=-1
for i,v in pairs(tb1) do
local tem=-1
for index,value in pairs(tb2) do
if tb1==tb2 or string_is_similar(tb1,tb2) then
tem=1
end
end
if tem==-1 then
result=1
end
end
for index,value in pairs(tb2) do
local tem=-1
for i,v in pairs(tb1) do
if tb1==tb2 or string_is_similar(tb1,tb2) then
tem=1
end
end
if tem==-1 then
result=1
end
end
----------------------
if result==-1 then
return true
end
return false
end
那么在逍遥行中,只需要将出口判断部分再修改为
if table_is_similar(cur_room_exits,tb_db_exits) then
exits_is_same=true
end
就可以了。 三、关于走迷宫
其实新增的迷宫如果不是随机变化的话,你只需要走一遍,记录一下路径,然后加到两头的节点上,或者制作一个alias,就可以了。
再变态一点的,就是将所有新增迷宫当做新房间都录入地图。但是这样难度大在于重复的房间太多,迷宫内定位会错误率较高。
当然,如果你没有录入所有房间也没有alias,或者说迷宫会随机变化,那么如何实现快速行走呢。
这里用到的就是根据房间出口进行自动行走。
关于抓取出口我一般用这个trigger
^\s+这里.+的(出口|方向)有 (.+)$
那么抓取之后我们把出口存成一个table。
chukou="%2"
if string.find("%2","。") then
chukou=string.gsub(chukou,"、",";")
chukou=string.gsub(chukou,"和",";")
chukou=string.gsub(chukou," ","")
chukou=string.gsub(chukou,"。","")
chukou=utils.split(chukou,";")
else
EnableTrigger("get_exists",1)
end
moved=1
EnableTrigger("get_exist",0)
由于有时候出口较多会超过一行,那么需要另作处理。在这先不讨论。
好了,房间出口会抓取,而且也转化成table了,那么我们只需要从中选取一个出口来开始走。
由于第一个房间我们只需要朝着一个方向前行,其它出口可以从table中删掉。
那么我们利用函数
function dellj(nowlj,fx) ----用于删除方向table中的某一个方向
local fx=fx
local nowlj=nowlj
local i=1
if fx==nil then return
else for i=1,table.getn(nowlj) do
if nowlj==fx then table.remove(nowlj,i) end
i=i+1
end
end
end
假设走了一步 north ,那么来到的房间肯定有一个出口 south,那么这个south是回去的路,
我们独立存入一个table,用来记录返程路径,当前出口table中我们把south删掉,我们下一步要走的是south之外的其它出口。
那么根据方向求反方向,这个很简单,也算是常用函数
function revfx(fx)
if fx=="enterbj" then return "outbj" end
if fx=="outbj" then return "enterbj" end
if fx=="swboxiaolu" then return "boxiaolune" end
if fx==nil then return end
if fx=="south" then return "north" end
if fx=="east" then return "west" end
if fx=="west" then return "east" end
if fx=="north" then return "south" end
if fx=="southup" then return "northdown" end
if fx=="southdown" then return "northup" end
if fx=="westup" then return "eastdown" end
if fx=="westdown" then return "eastup" end
if fx=="eastup" then return "westdown" end
if fx=="eastdown" then return "westup" end
if fx=="northup" then return "southdown" end
if fx=="northdown" then return "southup" end
if fx=="northwest" then return "southeast" end
if fx=="northeast" then return "southwest" end
if fx=="southwest" then return "northeast" end
if fx=="southeast" then return "northwest" end
if fx=="enter" then return "out" end
if fx=="out" then return "enter" end
if fx=="up" then return "down" end
if fx=="down" then return "up" end
if fx=="u" then return "d" end
if fx=="d" then return "u" end
if fx=="s" then return "n" end
if fx=="e" then return "w" end
if fx=="w" then return "e" end
if fx=="n" then return "s" end
if fx=="su" then return "nd" end
if fx=="sd" then return "nu" end
if fx=="wu" then return "ed" end
if fx=="wd" then return "eu" end
if fx=="eu" then return "wd" end
if fx=="ed" then return "wu" end
if fx=="nu" then return "sd" end
if fx=="nd" then return "su" end
if fx=="nw" then return "se" end
if fx=="ne" then return "sw" end
if fx=="sw" then return "ne" end
if fx=="se" then return "nw" end
return fx
end
好了我们来写前进的函数
有点基础的朋友会发现,我们需要的实际上就是一个深度优先遍历。
首先我们用一个table来存储每一步中可用的出口方向 chukou_all_list (请原谅我的英语水平,只能用中英文混合。名字你自己随便设置)
chukou_all_list表示第一个步
chukou_all_list表示第一步中的第一个方向
再用另一个table来存储返回的路径 fx_back
然后我们需要知道最终的目的,我们设置一个变量 findmubiao (好吧,又是中英文混合的)表示到达目的,这个需要通过到达的房间的触发来给变量赋值1。
另外我们也可能需要中途停止遍历,那么再用一个变量 bl_finish 来表示终止。
这两个值有一个被赋值为 1 ,那么遍历行动停止。
再然后呢,需要通过一个触发来进行下一步的动作,在这里,我选择set
set bianli_wait 前进一步
set bianli_wait 后退一步
通过触发来进行下一个动作,如果走到死胡同,或者我们设定的出口不是想要的出口,那么后退一步。
否则的话,就继续向下走一步。
有时候走进迷宫之后有多个出口,比如既能通往松树林又能通往达摩洞,但我们不希望走进松树林。
那就通过松树林的描述触发,赋值给reach_end函数,告诉程序,这个路径点不希望继续下去,往回走。
好吧,放一段完整的函数供参考:
require "wait"
wait.make( function()
i=i+1
if math.fmod(i,"15") < 1 then
print("延时1秒后继续")
wait.time(1)
end
if i==1 then
table.insert(chukou_all_list,chukou)
else
if moved==1 then
if fx~=nil then
table.insert(chukou_all_list,chukou)
local fx_b=revfx(fx)
dellj(chukou_all_list,fx_b)
moved=0
end
else
Note("被拦路,放弃此出口!!")
dep=dep-1
ck_i=ck_i-1
end
end
if ck_i==1 and chukou_all_list==nil then
Note("结束遍历!")
EnableGroup("bianli",0)
Send("unset brief")
else
if tonumber(GetVariable("bl_finish"))==1 or tonumber(GetVariable("findmubiao"))==1 then
Execute("say action 找到目标,遍历结束")
EnableGroup("bianli",0)
Execute("unset brief;look")
else
if chukou_all_list==nil or tonumber(GetVariable("reach_end"))==1 then
Note("后退一步")
dep=dep-1
table.remove(chukou_all_list,ck_i)--删除此空记录
ck_i=ck_i-1--应该走的方向列表
fx=fx_back
Execute(fx..";set bianli_wait 后退一步")
table.remove(fx_back,1)
else
fx=chukou_all_list--当前房间出口的第一个方向
Note("前进一步,当前层数"..dep..",当前步数 "..i)
EnableTrigger("get_exist",1)
Execute(fx..";set bianli_wait 前进一步")
dellj(chukou_all_list,fx)
ck_i=ck_i+1
dep=dep+1
fx_b=revfx(fx)
table.insert(fx_back,1,fx_b)
end
end
end
end)
那么同理,稍微修改一下就是后退的函数:
require "wait"
wait.make( function()
if ck_i==1 and chukou_all_list==nil then
Note("未找到出口!")
EnableGroup("bianli",0)
else
i=i+1
if math.fmod(i,"15") == 1 then
print("延时1秒后继续")
wait.time(1)
end
if tonumber(GetVariable("bl_finish"))==1 or tonumber(GetVariable("findmubiao"))==1 then
Execute("say action 完成遍历,结束自动行走。")
EnableGroup("bianli",0)
else
if chukou_all_list==nil or tonumber(GetVariable("reach_end"))==1 then
Note("后退一步")
dep=dep-1
table.remove(chukou_all_list,ck_i)--删除此空记录
ck_i=ck_i-1--应该走的方向列表
fx=fx_back
Execute(fx..";set bianli_wait 后退一步")
table.remove(fx_back,1)
else
fx=chukou_all_list--当前房间出口的第一个方向
Note("前进一步,当前层数"..dep..",当前步数 "..i)
EnableTrigger("get_exist",1)
Execute(fx..";set bianli_wait 前进一步")
dellj(chukou_all_list,fx)
dep=dep+1
ck_i=ck_i+1
fx_b=revfx(fx)
table.insert(fx_back,1,fx_b)
end
end
end
end) 我发现有点问题
代码中有一些中括号发帖的时候丢了。这......
我再研究研究怎么补齐了。 感谢牛大分享精彩的内容,真是太实用啦,技术就是应该这样分享~~~!
我看过牛大的帖子,也想提出一些自己的想法,下面就一一道来。 虽然我的水平不高,但久病成医,之前发过一篇帖子来介绍任务中常用的函数
这次呢,再发一篇,活跃活跃 ...
nrm 发表于 2017-10-23 02:10 PM http://hk.pkuxkx.com/forum/images/common/back.gif
我重写了一下函数,看起来应该更简洁一些,而且从算法复杂度上也应该能稍快一些吧,不过我并没有进行过计时对比,:)
function table_is_equal(list1,list2)
local ckA = list1
table.sort(ckA)
local ckSA = table.concat(ckA,";")
local ckB = list2
table.sort(ckB)
local ckSB = table.concat(ckB,";")
return ckSA == ckSB
end 三、关于走迷宫
其实新增的迷宫如果不是随机变化的话,你只需要走一遍,记录一下路径,然后加到两头的节点 ...
nrm 发表于 2017-10-23 02:10 PM http://hk.pkuxkx.com/forum/images/common/back.gif
对于抓取方向这里我也有一点建议,我采用的这样的方式:
trigger:
^\s{4}(?:这里.+的(?:出口|方向)(?:是|有)\s*(.+)|浓雾中你.*觉得似乎.+通往\s*(.+)方向。)$
保存为table:
local fx = ("%1"~="") and "%1" or "%2"
local ckt = {}
for w in string.gmatch(fx,"%l+") do
ckt[#ckt+1]=w
end
SetVariable("chukou",table.concat(ckt,";")) 临时补习了一下sort 和 concat 的用法,然后才看明白。
汗一个。
顺便感谢高手的补充。 对于抓取方向这里我也有一点建议,我采用的这样的方式:
......
local fx = ("%1"~="") and "%1" or "%2"
......
longzaitian 发表于 2017-10-24 12:19 AM http://pkuxkx.com/forum/images/common/back.gif
谁来给解释一下这个赋值是怎么一个规则。
麻蛋,哥看不懂。yct63.
唉,我是懒癌比较严重的那种,能解决眼前的问题,就懒得再深入研究。
所以水平不高,但是将将够用。再高深一点的就云里雾里了。
页:
[1]
2