nrm 发表于 2017-10-23 22:10:04

活跃技术板块之二:应对地图变化的常用函数。

虽然我的水平不高,但久病成医,之前发过一篇帖子来介绍任务中常用的函数
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

nrm 发表于 2017-10-23 22:10:27

二、关于出口字符的变化
这是新出现的情况。如上例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
就可以了。

nrm 发表于 2017-10-23 22:10:31

二、关于出口字符的变化
这是新出现的情况。如上例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
就可以了。

nrm 发表于 2017-10-23 22:10:58

三、关于走迷宫
其实新增的迷宫如果不是随机变化的话,你只需要走一遍,记录一下路径,然后加到两头的节点上,或者制作一个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 22:13:14

我发现有点问题
代码中有一些中括号发帖的时候丢了。这......
我再研究研究怎么补齐了。

longzaitian 发表于 2017-10-24 07:56:42

感谢牛大分享精彩的内容,真是太实用啦,技术就是应该这样分享~~~!

我看过牛大的帖子,也想提出一些自己的想法,下面就一一道来。

longzaitian 发表于 2017-10-24 08:08:37

虽然我的水平不高,但久病成医,之前发过一篇帖子来介绍任务中常用的函数


这次呢,再发一篇,活跃活跃 ...
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

longzaitian 发表于 2017-10-24 08:19:12

三、关于走迷宫
其实新增的迷宫如果不是随机变化的话,你只需要走一遍,记录一下路径,然后加到两头的节点 ...
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,";"))

nrm 发表于 2017-10-24 09:44:10

临时补习了一下sort 和 concat 的用法,然后才看明白。
汗一个。
顺便感谢高手的补充。

nrm 发表于 2017-10-24 09:53:45

对于抓取方向这里我也有一点建议,我采用的这样的方式:
......
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
查看完整版本: 活跃技术板块之二:应对地图变化的常用函数。