jarlyyn 发表于 2024-10-12 21:33:50

杰哥瞎扯蛋之处理触发和计时器的委员会系统

在我的新框架里,除了为了便于维护,添加的基本事件触发器(永远不会启用禁用那种)。

其他的所有的模块都基于我新制作的这个委员会(committee)系统。

叫委员会呢,也是我的起名恶趣味,主要是强调两点

1.整个系统的事件和时间处理有分工,有不同的职位。
2.不同的职位不是长期的,有各自的任期,过了任期后,所有已经安排的工作的全部失效,人走茶凉。

职位主要指写代码时的不同层。比如我现在分为

1.连接,每次重连后重置。
2.房间,进入新房间后重置。
3.任务,开始新任务后重置。
4.指令,执行新指令后重置。
5.指令组,进入新的指令组后重置。
6.战斗,脱离和进入战斗后重置。

每个指令下会绑定不同的 触发,计时器,事件函数,以及 任务(核心,独立的触发计时器事件函数的集合)。

通过一个统一的触发和计时器,接入committee,然后依次分发给绑定的各个元素。

jarlyyn 发表于 2024-10-12 21:42:26

本帖最后由 jarlyyn 于 2024-10-12 09:54 PM 编辑

关于任务(task)

Task时我新框架的基本编程工具。

他代表一个指定作用空间(职位,别入当前房间内),一个一次性的对将来发生的状况的预期,取代我原来的很多简单状态机的功能。

他可以添加 触发器,计时器,事件函数,作为结束信号。

默认情况下,任何一个结束新号触发时,会使整个任务失效,调用回调函数,将失效相关的信息传去处理。

有点类似 北侠比较流行的wait.lua的功能,但比那个实用和靠谱的多(wait.lua的源代码时通过添加客户端触发器和计时器实现的,明显会污染整个客户端编程环境)

简单来个代码

    App.Map.StepPlan = new App.Plan(
      App.Map.Position,
      function (task) {
            let tt=task.NewTimer(App.Map.StepTimeout).WithName("timeout")
            task.NewCatcher("core.longtimestep",function(){
                tt.Reset(App.Move.LongtimeStepDelay)
                return true
            })
            task.NewCatcher("core.wrongway").WithName("wrongway")
            task.NewCatcher("core.walkbusy").WithName("walkbusy")
            task.NewCatcher("core.walkresend").WithName("walkresend")
            task.NewCatcher("core.walkretry").WithName("walkretry")
      },
      function (result) {
            switch (result.Type) {
                case "cancel":
                  break
                default:
                  switch (result.Name) {
                        case "timeout":
                            App.Map.OnStepTimeout()
                            break
                        case "wrongway":
                            App.Map.Room.ID=""
                            App.Map.Retry()
                            break
                        case "walkbusy":
                            App.Map.Resend()
                            break
                        case "walkresend":
                            App.Map.Resend(0)
                            break
                        case "walkretry":
                            App.Map.Retry()
                            break
                  }
            }
      }
    )

这是一个发送移动指令后,进入新的房间时的Task

里面明显监听了多个事件。

retry事件,会重试行走(重新计算路线,比如发现某条路被拦住过不去后,打上标签,重新计算路线)
busy事件,会1秒后重走
resend事件,立刻重发指令(比如拿武器被拦了,unwield后重新走)
timeout,超时。
longtimestep,一个长时间的移动,比如北侠的爬树,重设timeout未更长的事件,同时return true,使得当前任务不会结束。

当没有return true的事件触发,或者整个职位换任期(进入新的房间)后,task失效,开始进入结算流程,更具result的数据进行结算和处理。

整个模拟了我的简单状态机,熟悉wait.lua的也会发现和wait.lua也比较像。
result有Type属性,cancel,timer,trigger,event,来决定了结束的类型。

有Name和Data属性,可以通过WithName和WithData函数设置,可以快速的设置结果。


jarlyyn 发表于 2024-10-12 21:50:19

本帖最后由 jarlyyn 于 2024-10-12 09:52 PM 编辑

task的机制,解决了Mud机器人的一个重要功能

对未来信号的等待预期。

很多时候,机器是被动的,会在那里等待一些预期里的事件发生。

这个等待的过程,有独立的上下文。

等待结束后,会根据实际收到的信好做下一步的处理。

等待过程中,出现了某些新号,不一定直接结束等待,而是调整等待的姿态。
上一贴中,我还引入一个计划(Plan)的概念。

就是一个预设的任务生成器,本质就是个Builder函数。

jarlyyn 发表于 2024-10-12 21:57:11

再来个简单的应用,取代开关触发组

    App.BindEvent("core.skills", App.Core.OnSkills)
    // ┌─────────────┬─────────────┬──────┬──────┐
    // │名称                      │ID                        │描述      │级别      │
    // ├───四项基本功夫────┼─────────────┼──────┼──────┤
    // │基本内功                │force                     │【不足挂齿】│71 / 56   │
    // │□叫花内功                │jiaohua-neigong         │【不足挂齿】│71 / 7    │
    // ├─────────────┼─────────────┼──────┼──────┤
    // │基本轻功                │dodge                     │【不堪一击】│12 / 58   │
    // │基本拳脚                │unarmed                   │【不堪一击】│   2 / 40   │
    // │基本招架                │parry                     │【不堪一击】│   1 / 0    │
    // ├───一项知识技能────┼─────────────┼──────┼──────┤
    // │读书写字                │literate                  │【新学乍用】│26 / 97   │
    // └─────────────┴─────────────┴──────┴──────┘
    var matcherSkillsType = /^├─+.+项([^─]+)─+┼─+┼─+┼─+┤$/
    var matcherSkills = /^│(|□)(\S+)\s+│(\S+)\s+│【.+】│\s*(\d+) \/\s*(\d+)\s*│$/
    var matcherSkillsEnd = /^└─*┴─*┴─*┴─*┘$/
    var PlanOnSkills = new App.Plan(App.Positions.Connect,
      function (task) {
            task.NewTrigger(matcherSkillsType, function (trigger, result, event) {
                LastType = result
                return true
            })
            task.NewTrigger(matcherSkills, function (trigger, result, event) {
                let skill = {}
                skill.ID = result
                skill["名称"] = result
                skill["激发"] = (result.trim() != "")
                skill["类型"] = LastType
                skill["等级"] = result - 0
                skill["进度"] = result - 0
                App.Data.Player.Skills = skill
                return true
            })
            task.NewTimer(5000)
            task.NewTrigger(matcherSkillsEnd)
      },
      function (result) {
            checkerSkills.Reset()
      })


在当前连接(连接的Position)下,到MatcherSkillsEnd或5秒为止,处理每个匹配 matcherSkillsType 和 matcherSkills的 行,记录用户所有的技能。


都在一个文件里,不需要来回切换编辑器和客户端维护,维护比较简单。

jarlyyn 发表于 2024-10-12 21:58:50

本帖最后由 jarlyyn 于 2024-10-12 10:00 PM 编辑


    let checkerHP = App.Checker.Register("hp", "yun recover;yun regenerate;hp", 5000)
    // ┌───个人状态────────────┬───────────────────┐
    // │【精气】 160   / 160          │【精力】 0       / 0       (+   0)    │
    // │【气血】 523   / 523          │【内力】 1724    / 920   (+   0)    │
    // │【食物】 638   / 400      [很饱]    │【潜能】 14376                        │
    // │【饮水】 224   / 400      [缺水]    │【经验】 145283                     │
    // │                                    │【体会】 38                           │
    // ├───────────────────┴───────────────────┤
    // │【状态】 健康、平和                                                         │
    // └──────────────────────────────口口口口─────┘
    var matcherHPLine1 = /^│【精气】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【精力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
    var matcherHPLine2 = /^│【气血】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【内力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
    var matcherHPLine3 = /^│【食物】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【潜能】\s+(-?\d+)\s+│$/
    var matcherHPLine4 = /^│【饮水】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【经验】\s+(-?\d+)\s+│$/
    var matcherHPLine5 = /^│\s+│【体会】\s+(-?\d+)\s+│$/
    var matcherHPEnd = /^└─+.+─+─┘$/

    var PlanOnHP = new App.Plan(App.Positions.Connect,
      function (task) {
            task.NewTrigger(matcherHPLine1, function (trigger, result, event) {
                App.Data.Player.HP["当前精气"] = result - 0
                App.Data.Player.HP["精气上限"] = result - 0
                App.Data.Player.HP["精气百分比"] = result - 0
                App.Data.Player.HP["当前精力"] = result - 0
                App.Data.Player.HP["精力上限"] = result - 0
                App.Data.Player.HP["加精"] = result - 0
                return true
            })
            task.NewTrigger(matcherHPLine2, function (trigger, result, event) {
                App.Data.Player.HP["当前气血"] = result - 0
                App.Data.Player.HP["精气气血"] = result - 0
                App.Data.Player.HP["气血百分比"] = result - 0
                App.Data.Player.HP["当前内力"] = result - 0
                App.Data.Player.HP["内力上限"] = result - 0
                App.Data.Player.HP["加力"] = result - 0
                return true
            })
            task.NewTrigger(matcherHPLine3, function (trigger, result, event) {
                App.Data.Player.HP["当前食物"] = result - 0
                App.Data.Player.HP["最大食物"] = result - 0
                App.Data.Player.HP["潜能"] = result - 0
                return true
            })
            task.NewTrigger(matcherHPLine4, function (trigger, result, event) {
                App.Data.Player.HP["当前饮水"] = result - 0
                App.Data.Player.HP["最大饮水"] = result - 0
                App.Data.Player.HP["经验"] = result - 0
                return true
            })
            task.NewTrigger(matcherHPLine5, function (trigger, result, event) {
                App.Data.Player.HP["体会"] = result - 0
                return true
            })
            task.NewTimer(5000)
            task.NewTrigger(matcherHPEnd)
      },
      function (result) {
            checkerHP.Reset()
      })
这个道理一样,应该不需要多解释。

jarlyyn 发表于 2024-10-12 22:04:33

    let matcherOnHeal = /^    (\S{2,8})正坐在地下(修炼内力)。$/
    let matcherOnObj = /^    ((\S+) )?(\S*「.+」)?(\S+)\(([^\(\)]+)\)( \[.+\])?(( <.+>)*)$/
    var PlanOnExit = new App.Plan(App.Positions.Connect,
      function (task) {
            task.NewTrigger(matcherOnObj, function (trigger, result, event) {
                let item = new objectModule.Object(result, result, App.History.CurrentOutput).
                  WithParam("身份", result).
                  WithParam("外号", result).
                  WithParam("描述", result || "").
                  WithParam("状态", result || "").
                  WithParam("动作", "")
                App.Map.Room.Data.Objects.Append(item)
                event.Context.Set("core.room.onobject", true)
                return true
            })
            task.NewTrigger(matcherOnHeal, function (trigger, result, event) {
                let item = new objectModule.Object(result, "", App.History.CurrentOutput).
                  WithParam("动作", "result")
                App.Map.Room.Data.Objects.Append(item)
                event.Context.Set("core.room.onobject", true)
                return true
            })

            task.NewCatcher("line", function (catcher, event) {
                return event.Context.Get("core.room.onobject")
            })
      }, function (result) {
            if (result.Type != "cancel") {
                if (App.Map.Room.Name && !App.Map.Room.ID){
                  let idlist=App.Map.Data.RoomsByName
                  if (idlist&&idlist.length==1){
                        App.Map.Room.ID=idlist
                  }
                }
                App.RaiseEvent(new App.Event("core.roomentry"))
            }
      })

这个开始有点特殊了。

这个是抓房间里的NPC的触发。逻辑基本和北侠的接进。

我的框架里trigger一定是比event要优先的

也就是说,如果匹配到NPC,则不结束,并给当前行的事件打个标记。

如果行匹配结束时没有标记,说明这行不是npc,npc信息结束。

房间信息到此为止。

北侠我现有机器的逻辑和这个差不多。

但是因为意外(BUG)更多,我还加入了GA判断和超时判断。

jarlyyn 发表于 2024-10-12 22:09:48

到目前为止写的最复杂的是这个

    //你向店小二打听有关『123』的消息。
    let matcherAsk = /^你向(.+)打听有关『.+』的消息。$/
    //但是很显然的,$n现在的状况没有办法给$N任何答覆。
    //$n摇摇头,说道:没听说过。
    //$n疑惑地看着$N,摇了摇头。
    //$n睁大眼睛望着$N,显然不知道$P在说什么。
    //$n耸了耸肩,很抱歉地说:无可奉告。
    //$n说道:嗯....这我可不清楚,你最好问问别人吧。
    //$n想了一会儿,说道:对不起,你问的事我实在没有印象。
    let matcherUnknown = /^(但是很显然的,.+现在的状况没有办法给你任何答覆。|.+摇摇头,说道:没听说过。|.+疑惑地看着.+,摇了摇头|.+睁大眼睛望着你,显然不知道你在说什么。|.+耸了耸肩,很抱歉地说:无可奉告。|.+说道:嗯....这我可不清楚,你最好问问别人吧。|.+想了一会儿,说道:对不起,你问的事我实在没有印象。|.+对你说道:实在是对不起,我什么也不知道呀!|.+疑惑地看着你,摇了摇头。)$/
    let matcherRetry = /^(.+说道:阿嚏!有点感冒,不好意思。|.+说道:等...等等,你说什么?没听清楚。|.+说道:嗯,稍等啊,就好... 好了,你刚才说啥?|.+说道:这个... 这个... 哦,好了,啊?你问我呢?|.+说道:唉呦!... 不好意思,是你问我么?|.+说道:就好... 就好... 好了,你说啥?|.+说道:你干啥?没看我忙着呢么?|.+说道:\!\@\#\$%\^\&\*)$/
    //这个地方不能讲话。
    //这里没有这个人。
    //$N对着$n自言自语....
    //$N自己自言自语。
    //你现在的精神不太好,没法和别人套瓷。
    let matcherFail = /^(这个地方不能讲话。|这里没有这个人。|.+对着.+自言自语....|你自己自言自语。|你现在的精神不太好,没法和别人套瓷。)$/
    let PlanOnAsk = new App.Plan(App.Positions.Connect,
      function (task) {
            App.Sync(function () { task.Cancel("sync") })
            task.NewTrigger(matcherAsk, function (result) {
                if (App.Data.Ask.Mode == 0) {
                  App.Data.Name = result
                  App.Data.Ask.Mode = 1
                }
                return true
            })
            task.NewTrigger(matcherUnknown, function (result) {
                if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
                  App.Data.Ask.Result = "unknown"
                  App.Data.Ask.Mode = 2
                }
                return true
            })
            task.NewTrigger(matcherRetry, function (result) {
                if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
                  App.Data.Ask.Result = "retry"
                  App.Data.Ask.Mode = 2
                }
                return true
            })
            task.NewTrigger(matcherFail, function (result) {
                if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
                  App.Data.Ask.Result = "fail"
                  App.Data.Ask.Mode = 2
                }
                return true
            })
            task.NewCatcher("line", function (catcher, event) {
                if (App.Data.Ask.Mode != 0) {
                  App.Data.Ask.LineNumber++
                }
                if (App.Data.Ask.Mode == 1 && App.Data.Ask.LineNumber > 1) {
                  if (App.Data.Ask.Answers.length > (App.Data.Ask.Length > 0 ? App.Data.Ask.Length : MaxAnswer)) {
                        App.Data.Ask.Mode = 2
                        return true
                  }
                  App.Data.Ask.Result = "ok"
                  App.Data.Ask.Answers.push(App.History.GetLine())
                }
                return true
            })

      },
      function (result) {
            if (App.Data.Ask.Result == "fail") {
                App.Fail()
                return
            }
            if (App.Data.Ask.Result == "retry") {
                App.Commands.Insert(
                  App.Commands.NewWaitCommand(1000),
                  App.Commands.NewFunctionCommand(function () {
                        App.Ask(App.Data.Ask.ID, App.Data.Ask.Question, App.Data.Ask.Length)
                  }),
                )
            }
            App.Next()
      },
    )


记录向NPC ask的信息。

出现你向NPC打听 XXXX时,开始进入准备记录模式
打听失败,则标记为失败
NPC忙,则标记为重试,1秒后重试。
如果是正常状态,记录聊天记录直到指定行为止。

遇到和ASK一起发送的同步指令(相当于response),退出等待,进行后续处理。

实际使用大概是这样


    let re = /.+说道:据说(.+)急需一批(.+)。嘿!你说他想干什么?$/
    App.Quest.Beiqi.Check = function () {
      if (App.Quests.Stopped){
            App.Next()
            return
      }
      if (App.Data.Ask.Answers.length == 0) {
            App.Fail()
            return
      }
      let answer = App.Data.Ask.Answers
      let result = answer.Line.match(re)
      if (result) {
            let key = App.Quest.Beiqi.Data.Current.Key + "-" + result
            let receiver = App.Quest.Beiqi.Receivers
            if (receiver == null) {
                App.Fatal("beiqi","未知的收货人" + key)
                return
            }
            App.Quest.Beiqi.Data.Quest = {
                Receiver: receiver,
                Item: result,
            }
            App.Quest.Beiqi.Give()
            return
      }
      App.Fatal("beiqi","未知的回答:" + answer)
    }直接处理 App.Data.Ask.Answers 和 App.Data.Ask.Answers.Result可以直接处理最后依次的主动ask


完全不需要多行匹配之类的东西。

cmfu 发表于 2024-10-12 22:12:07

又是给程序员写的框架吗这种只能自娱自乐呀

jarlyyn 发表于 2024-10-12 22:12:59

整个Committee模块,解决的就是 失效 和 快速实现等待的功能。

和事件系统一起,解决了同步事件处理和异步事件处理,实现了条件反射功能。

为实现主动带目的性的机器功能(指令/指令对列)打下了基础。

基本模块说完了。

看看那下一次说说新的移动模块还是指令模块了。

jarlyyn 发表于 2024-10-12 22:15:46

总体来说,这个模块还是为了降低机器复杂度。

实现Timer,Trigger和Timer的异步处理。

然后指令(Command)系统处理的是函数/代码的异步问题。

这两个模块,就是为了以接近同步代码的形式处理异步逻辑。

使得流程代码能压平,成为一个流,而不是乱线团,便于编写和维护。
页: [1] 2
查看完整版本: 杰哥瞎扯蛋之处理触发和计时器的委员会系统