jarlyyn 发表于 2021-11-18 00:08:18

由于这种 到某个地方,执行某个命令,进入某个状态是一种常见的需求,所以将这块提取出来

建立一个Active数据结构
(function(app){
    let Active=function(location,cmd,final,nobusy){
      this.Location=location
      this.Command=cmd?cmd:""
      this.FinalState=final?final:""
      this.Nobusy=nobusy?true:false
      this.Data=null
    }
    Active.prototype.WithLocation=function(location){
      this.Location=location
      return this
    }
    Active.prototype.WithCommand=function(cmd){
      this.Command=cmd?cmd:""
      return this
    }
    Active.prototype.WithFinalState=function(final){
      this.FinalState=final?final:""
      return this
    }
    Active.prototype.WithNobusy=function(nobusy){
      this.Nobusy=nobusy?true:false
      return this
    }
    Active.prototype.WithData=function(data){
      this.Data=data
      return this
    }
    Active.prototype.Start=function(){
      let transitions
      if (this.Nobusy){
            transitions=["core.state.active.move","nobusy","core.state.active.execute","nobusy"]
      }else{
            transitions=["core.state.active.move","core.state.active.execute"]
      }
      let a=app.Automaton.Push(this.FinalState,transitions)
      a.WithData("Active",this)
      app.ChangeState("ready")
    }
    returnhttps://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/include/active.js

修改goods状态
(function (app) {
    let basicstate = Include("core/state/basicstate.js")
    let StateGoods=function(){
      basicstate.call(this)
      this.ID="goods"
    }
    StateGoods.prototype = Object.create(basicstate.prototype)
    StateGoods.prototype.Enter=function(context,oldstatue){
      basicstate.prototype.Enter.call(this,context,oldstatue)
      let item=app.GetContext("Item")
      let a=app.NewActive(item.Location,item.Command,"",true)
      a.WithData(item)
      a.Start()
    }
    return StateGoods
})(App)




引入 "core.state.active.move" "core.state.active.execute" "checkitem" 三个状态
https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/active/activemove.js

https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/active/activeexecute.js

https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/statecheckitem.js



jarlyyn 发表于 2021-11-19 13:30:36

接下来把一些重要模块也状态化了。

比如
等待模块,等待一定延时后进入后去状态。为了避免出现问题,做了一个生成unid再判断的流程

(function (app) {
    let basicstate = Include("core/state/basicstate.js")
    let StateWait=function(){
      basicstate.call(this)
      this.ID="wait"
      this.SN=""
    }
    StateWait.prototype = Object.create(basicstate.prototype)
    StateWait.prototype.OnEvent=function(context,event,data){
      switch(event){
            case "core.state.wait.after":
                if (this.SN==data){
                  app.ChangeState("ready")
                }
            break
            default:
                Move.prototype.Enter.call(this,context,event,data)
      }
    }
    StateWait.prototype.Enter=function(context,oldstatue){
      basicstate.prototype.Enter.call(this,context,oldstatue)
      let delay=app.GetContext("Delay")
      let sn=world.GetUniqueID()
      this.SN=sn
      world.DoAfterSpecial(delay, 'App.OnStateEvent("core.state.wait.after","'+sn+'")', 12);
    }
    return StateWait
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/statewait.js

然后重写了proposal部分,成功返回需要执行的proposal

https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal.js

将propsal都状态化

https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/cash.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/drink.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/food.js

就做好了基础的准备了



jarlyyn 发表于 2021-11-19 14:20:25

接下来我们就要改第一个正式的模块,也可以说是一个通用的功能模块

这个模块可以执行一串字符串,比如我们要实现自动配药

那么输入

#prepare||#to yzyp||ask ping yizhi about 工作||n||peiyao||#afterbusy||#delay 5000||s||give cheng yao to ping yizhi||#loop



可以实现

[*]准备(取现,买吃喝)
[*]前往扬州药铺
[*]向平一指打听工作
[*]前往配药房
[*]配药
[*]等待不忙
[*]延迟5秒
[*]回平一指处
[*]给配好的药
[*]循环

这样一个循环队列。很明显,诸如打坐,吐纳,练武功,钓鱼之类都能很容易的使用这形势执行

我们先调整queue.task,分割字符串,并进入queue的主状态 core.state.queue.next


(function (app) {
    let Task = Include("core/task/task.js")
    let queue = Include("core/queue/queue.js")

    var re = /\|\|/
    let Queue = function () {
      Task.call(this, "queue")
      this.Execute = function (data, onFinish, onFail) {
            Task.prototype.Execute.call(this, data, onFinish, onFail)
            if (!data) {
                data = ""
            }
            let list = data.split(re)
            let q=new queue(list)
            app.Send("l")
            app.Automaton.Push("",["core.state.queue.next"]).WithData("Queue",q)
            app.ResponseReady()
      }
    }
    Queue.prototype = Object.create(Task.prototype)
    app.RegisterState(new (Include("core/state/queue/queuenext.js"))())
    app.RegisterState(new (Include("core/state/queue/queueloop.js"))())
    app.RegisterTask(new Queue())

})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/task/queue.js



core.state.queue.next主状态代码如下


(function (app) {
    let basicstate = Include("core/state/basicstate.js")
    let StateQueueNext=function(){
      basicstate.call(this)
      this.ID="core.state.queue.next"
    }
    StateQueueNext.prototype = Object.create(basicstate.prototype)
    StateQueueNext.prototype.Enter=function(context,oldstatue){
      basicstate.prototype.Enter.call(this,context,oldstatue)
      let queue=app.GetContext("Queue")
      if (queue.Remain.length === 0) {
            app.Finish()
            return
      }
      let str = queue.Remain.shift()
      let current = new Directive(str)
      switch (current.Command) {
            case "#prepare":
                app.StartFullPrepare(this.ID)
                break
            case "#to":
                let c = new Directive(current.Data)
                let vehicle
                let target
                if (c.Data) {
                  vehicle = c.Command
                  target = c.Data
                } else {
                  vehicle = ""
                  target = c.Command
                }
                app.Drive(vehicle)
                app.NewActive(target,"","core.state.queue.next",false).Start()
                break
            case "#move":
                break
            case "#afterbusy":
                app.Automaton.Push("core.state.queue.next",["nobusy"])
                app.ChangeState("ready")
                break
            case "#do":
                app.NewActive("",current.Data,"core.state.queue.next",false).Start()
                break
            case "#delay":
                let data=current.Data
                if (isNaN(data) || (data - 0) <= 0) {
                  throw "delay 的秒数必须为正数"
                }
                let delay=(data - 0) / 1000
                app.Wait(delay,"core.state.queue.next")
                break
            case "#loop":
                if (!app.Stopped) {
                  queue.Remain = CloneArray(queue.Queue)
                }
                app.ChangeState("core.state.queue.loop")
                break
            default:
                app.NewActive("",str,"core.state.queue.next",false).Start()
      }

    }
    return StateQueueNext
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queuenext.js

很明显,弹出队列里的最新命令,判断并进入相应的状态。本身简单清晰容易维护

对比状态化之前的代码,明显容易维护很多

老代码:https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/task/queue.js

最后是一个core.state.queue.loop状态。这其实是一个空在状态,用途是进入会回到老状态,触发原状态的enter和leave事件

(function (app) {
    let basicstate = Include("core/state/basicstate.js")
    let StateQueueLoop=function(){
      basicstate.call(this)
      this.ID="core.state.queue.loop"
    }
    StateQueueLoop.prototype = Object.create(basicstate.prototype)
    StateQueueLoop.prototype.Enter=function(context,oldstatue){
      basicstate.prototype.Enter.call(this,context,oldstatue)
      app.ChangeState("core.state.queue.next")
    }
    return StateQueueLoop
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queueloop.js


这个模块就做完了。

我们还可以用来进行抄经工作,分为两块

#prepare||#to qf-sms||#do ask sengren about job
将验证码地址复制进新页面打开,并刷新到人能认出的程度

#to ts-sjy||#do write jing||#afterbusy||#delay 2000||#to qf-sms||#do ask sengren about done
将页面里的验证码输入


然后循环。



jarlyyn 发表于 2021-11-19 14:26:26

状态话后的代码我维护也非常简单

由于自动机的栈是在app.data里的,一个debug马上就可以看到现在程序执行到什么程度,上下文是什么,数据是什么,下一步是什么



如图,当前状态在"wait",等待状态

数据是 Deley=5,等5秒

下一个动作是"core.state.queue.next"

然后调用他的上一级的数据是Queue,内容包括
[
            "#prepare",
            "#to yzyp",
            "ask ping yizhi about 工作",
            "n",
            "peiyao",
            "#afterbusy",
            "#delay 5000",
            "s",
            "give cheng yao to ping yizhi",
            "#loop"
          ]目前剩下需要处理的指令为
[
            "s",
            "give cheng yao to ping yizhi",
            "#loop"
          ]

jarlyyn 发表于 2021-11-19 14:28:30

好了,下一步就是北侠这个mud做机器人对我最大的考验了

怎么做出一个同时满足


[*]我觉得有意义,至少不丑陋
[*]北侠允许公开讨论和传播
[*]普通玩家愿意用

的机器人了

不的不承认,对我来说,最大的限制是第二点……


jarlyyn 发表于 2021-11-19 17:47:11



搞了一个all 20 id,做读书测试

读书的命令是

#prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop

还没做打坐和睡觉的判断模块,效率略低,但能用

会自动买吃喝,取钱。



vicrly 发表于 2021-12-6 15:09:57

这个帖子写的好啊!我已经苦恼好久了,总觉得自己的机器人如同一堆散乱的触发器,没有条理。仔细学习一下,才有醍醐灌顶之感。继续更新啊~

vicrly 发表于 2021-12-6 20:14:36

又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mini版?{:7_268:}
有个问题想请教一下楼主。
#prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop 这个指令是无限循环的话,如果我要做别的事,比如做任务,是不是要切换指令集?

jarlyyn 发表于 2021-12-7 14:00:01

vicrly 发表于 2021-12-6 08:14 PM
又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mi ...

这是一个指令化的执行队列

以||分割,目前有 #prepare,#to,#do,#delay,#loop ,#nobusy这几个指令

如果需要做别的 就做新的队列就行了。

这个只是一个粗的架构,实际真的要做的话,就要对游戏有一定的了解了。

ptolemy 发表于 2022-6-21 13:34:28

屡次想玩,屡次被机器人制作劝退,最多能做做打坐的机器人,而且即便想着要硬啃lua,想起来也不知道从何处下手,楼主这篇帖子有点从思路上让我找到了一点方向,其实之前就是在想,要怎么样从底层来拆分并且一个模块一个模块的机器人慢慢制作,然后可以拼起来,不至于换一本书又要重新复制修改一个机器人,同时读书的时候如果想插入其他的事情,又要如何可以方便的插入。拆开来看真的就是需要有各种各样的状态机,可以杀状态,调用新状态,感觉脑子里突然有了unity里面用状态机视图的既视感。虽然帖子我肯不下,不过以外的让我自己意识到,应该状态机的方式,写ai的思路来对待机器人,可能对于慢慢入手有思路上的帮助,谢谢楼主
页: 1 2 [3]
查看完整版本: 一步一步在北侠做机器人[进阶]状态篇