北大侠客行MUD论坛

 找回密码
 注册
搜索
热搜: 新手 wiki 升级
123
返回列表 发新帖
楼主: jarlyyn

一步一步在北侠做机器人[进阶]状态篇

[复制链接]
 楼主| 发表于 2021-11-18 00:08:18 | 显示全部楼层
由于这种 到某个地方,执行某个命令,进入某个状态是一种常见的需求,所以将这块提取出来

建立一个Active数据结构
  1. (function(app){
  2.     let Active=function(location,cmd,final,nobusy){
  3.         this.Location=location
  4.         this.Command=cmd?cmd:""
  5.         this.FinalState=final?final:""
  6.         this.Nobusy=nobusy?true:false
  7.         this.Data=null
  8.     }
  9.     Active.prototype.WithLocation=function(location){
  10.         this.Location=location
  11.         return this
  12.     }
  13.     Active.prototype.WithCommand=function(cmd){
  14.         this.Command=cmd?cmd:""
  15.         return this
  16.     }
  17.     Active.prototype.WithFinalState=function(final){
  18.         this.FinalState=final?final:""
  19.         return this
  20.     }
  21.     Active.prototype.WithNobusy=function(nobusy){
  22.         this.Nobusy=nobusy?true:false
  23.         return this
  24.     }
  25.     Active.prototype.WithData=function(data){
  26.         this.Data=data
  27.         return this
  28.     }
  29.     Active.prototype.Start=function(){
  30.         let transitions
  31.         if (this.Nobusy){
  32.             transitions=["core.state.active.move","nobusy","core.state.active.execute","nobusy"]
  33.         }else{
  34.             transitions=["core.state.active.move","core.state.active.execute"]
  35.         }
  36.         let a=app.Automaton.Push(this.FinalState,transitions)
  37.         a.WithData("Active",this)
  38.         app.ChangeState("ready")
  39.     }
  40.     return
复制代码
https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/include/active.js

修改goods状态
  1. (function (app) {
  2.     let basicstate = Include("core/state/basicstate.js")
  3.     let StateGoods=function(){
  4.         basicstate.call(this)
  5.         this.ID="goods"
  6.     }
  7.     StateGoods.prototype = Object.create(basicstate.prototype)
  8.     StateGoods.prototype.Enter=function(context,oldstatue){
  9.         basicstate.prototype.Enter.call(this,context,oldstatue)
  10.         let item=app.GetContext("Item")
  11.         let a=app.NewActive(item.Location,item.Command,"",true)
  12.         a.WithData(item)
  13.         a.Start()
  14.     }
  15.     return StateGoods
  16. })(App)
复制代码




引入 "core.state.active.move" "core.state.active.execute" "checkitem" 三个状态
https://github.com/hellclient-sc ... ctive/activemove.js

https://github.com/hellclient-sc ... ve/activeexecute.js

https://github.com/hellclient-sc ... e/statecheckitem.js



北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-11-19 13:30:36 | 显示全部楼层
接下来把一些重要模块也状态化了。

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

  1. (function (app) {
  2.     let basicstate = Include("core/state/basicstate.js")
  3.     let StateWait=function(){
  4.         basicstate.call(this)
  5.         this.ID="wait"
  6.         this.SN=""
  7.     }
  8.     StateWait.prototype = Object.create(basicstate.prototype)
  9.     StateWait.prototype.OnEvent=function(context,event,data){
  10.         switch(event){
  11.             case "core.state.wait.after":
  12.                 if (this.SN==data){
  13.                     app.ChangeState("ready")
  14.                 }
  15.             break
  16.             default:
  17.                 Move.prototype.Enter.call(this,context,event,data)
  18.         }
  19.     }
  20.     StateWait.prototype.Enter=function(context,oldstatue){
  21.         basicstate.prototype.Enter.call(this,context,oldstatue)
  22.         let delay=app.GetContext("Delay")
  23.         let sn=world.GetUniqueID()
  24.         this.SN=sn
  25.         world.DoAfterSpecial(delay, 'App.OnStateEvent("core.state.wait.after","'+sn+'")', 12);
  26.     }
  27.     return StateWait
  28. })(App)
复制代码
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/statewait.js

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

https://github.com/hellclient-sc ... pt/core/proposal.js

将propsal都状态化

https://github.com/hellclient-sc ... re/proposal/cash.js
https://github.com/hellclient-sc ... e/proposal/drink.js
https://github.com/hellclient-sc ... re/proposal/food.js

就做好了基础的准备了



北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-11-19 14:20:25 | 显示全部楼层
接下来我们就要改第一个正式的模块,也可以说是一个通用的功能模块

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

那么输入

  1. #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


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

  4.     var re = /\|\|/
  5.     let Queue = function () {
  6.         Task.call(this, "queue")
  7.         this.Execute = function (data, onFinish, onFail) {
  8.             Task.prototype.Execute.call(this, data, onFinish, onFail)
  9.             if (!data) {
  10.                 data = ""
  11.             }
  12.             let list = data.split(re)
  13.             let q=new queue(list)
  14.             app.Send("l")
  15.             app.Automaton.Push("",["core.state.queue.next"]).WithData("Queue",q)
  16.             app.ResponseReady()
  17.         }
  18.     }
  19.     Queue.prototype = Object.create(Task.prototype)
  20.     app.RegisterState(new (Include("core/state/queue/queuenext.js"))())
  21.     app.RegisterState(new (Include("core/state/queue/queueloop.js"))())
  22.     app.RegisterTask(new Queue())

  23. })(App)
复制代码

https://github.com/hellclient-sc ... /core/task/queue.js



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


  1. (function (app) {
  2.     let basicstate = Include("core/state/basicstate.js")
  3.     let StateQueueNext=function(){
  4.         basicstate.call(this)
  5.         this.ID="core.state.queue.next"
  6.     }
  7.     StateQueueNext.prototype = Object.create(basicstate.prototype)
  8.     StateQueueNext.prototype.Enter=function(context,oldstatue){
  9.         basicstate.prototype.Enter.call(this,context,oldstatue)
  10.         let queue=app.GetContext("Queue")
  11.         if (queue.Remain.length === 0) {
  12.             app.Finish()
  13.             return
  14.         }
  15.         let str = queue.Remain.shift()
  16.         let current = new Directive(str)
  17.         switch (current.Command) {
  18.             case "#prepare":
  19.                 app.StartFullPrepare(this.ID)
  20.                 break
  21.             case "#to":
  22.                 let c = new Directive(current.Data)
  23.                 let vehicle
  24.                 let target
  25.                 if (c.Data) {
  26.                     vehicle = c.Command
  27.                     target = c.Data
  28.                 } else {
  29.                     vehicle = ""
  30.                     target = c.Command
  31.                 }
  32.                 app.Drive(vehicle)
  33.                 app.NewActive(target,"","core.state.queue.next",false).Start()
  34.                 break
  35.             case "#move":
  36.                 break
  37.             case "#afterbusy":
  38.                 app.Automaton.Push("core.state.queue.next",["nobusy"])
  39.                 app.ChangeState("ready")
  40.                 break
  41.             case "#do":
  42.                 app.NewActive("",current.Data,"core.state.queue.next",false).Start()
  43.                 break
  44.             case "#delay":
  45.                 let data=current.Data
  46.                 if (isNaN(data) || (data - 0) <= 0) {
  47.                     throw "delay 的秒数必须为正数"
  48.                 }
  49.                 let delay=(data - 0) / 1000
  50.                 app.Wait(delay,"core.state.queue.next")
  51.                 break
  52.             case "#loop":
  53.                 if (!app.Stopped) {
  54.                     queue.Remain = CloneArray(queue.Queue)
  55.                 }
  56.                 app.ChangeState("core.state.queue.loop")
  57.                 break
  58.             default:
  59.                 app.NewActive("",str,"core.state.queue.next",false).Start()
  60.         }

  61.     }
  62.     return StateQueueNext
  63. })(App)
复制代码
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queuenext.js

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

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

老代码:https://github.com/hellclient-sc ... /core/task/queue.js

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

  1. (function (app) {
  2.     let basicstate = Include("core/state/basicstate.js")
  3.     let StateQueueLoop=function(){
  4.         basicstate.call(this)
  5.         this.ID="core.state.queue.loop"
  6.     }
  7.     StateQueueLoop.prototype = Object.create(basicstate.prototype)
  8.     StateQueueLoop.prototype.Enter=function(context,oldstatue){
  9.         basicstate.prototype.Enter.call(this,context,oldstatue)
  10.         app.ChangeState("core.state.queue.next")
  11.     }
  12.     return StateQueueLoop
  13. })(App)
复制代码
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queueloop.js


这个模块就做完了。

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

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

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


然后循环。



北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-11-19 14:26:26 | 显示全部楼层
状态话后的代码我维护也非常简单

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



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

数据是 Deley=5,等5秒

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

然后调用他的上一级的数据是Queue,内容包括
  1. [
  2.             "#prepare",
  3.             "#to yzyp",
  4.             "ask ping yizhi about 工作",
  5.             "n",
  6.             "peiyao",
  7.             "#afterbusy",
  8.             "#delay 5000",
  9.             "s",
  10.             "give cheng yao to ping yizhi",
  11.             "#loop"
  12.           ]
复制代码
目前剩下需要处理的指令为
  1. [
  2.             "s",
  3.             "give cheng yao to ping yizhi",
  4.             "#loop"
  5.           ]
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-11-19 14:28:30 | 显示全部楼层
好了,下一步就是北侠这个mud做机器人对我最大的考验了

怎么做出一个同时满足

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

的机器人了

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


北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-11-19 17:47:11 | 显示全部楼层


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

读书的命令是

  1. #prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop
复制代码

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

会自动买吃喝,取钱。



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 2021-12-6 15:09:57 | 显示全部楼层
这个帖子写的好啊!我已经苦恼好久了,总觉得自己的机器人如同一堆散乱的触发器,没有条理。仔细学习一下,才有醍醐灌顶之感。继续更新啊~
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 2021-12-6 20:14:36 | 显示全部楼层
又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mini版?
有个问题想请教一下楼主。
#prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop 这个指令是无限循环的话,如果我要做别的事,比如做任务,是不是要切换指令集?
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2021-12-7 14:00:01 | 显示全部楼层
vicrly 发表于 2021-12-6 08:14 PM
又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mi ...

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

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

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

这个只是一个粗的架构,实际真的要做的话,就要对游戏有一定的了解了。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 2022-6-21 13:34:28 | 显示全部楼层
屡次想玩,屡次被机器人制作劝退,最多能做做打坐的机器人,而且即便想着要硬啃lua,想起来也不知道从何处下手,楼主这篇帖子有点从思路上让我找到了一点方向,其实之前就是在想,要怎么样从底层来拆分并且一个模块一个模块的机器人慢慢制作,然后可以拼起来,不至于换一本书又要重新复制修改一个机器人,同时读书的时候如果想插入其他的事情,又要如何可以方便的插入。拆开来看真的就是需要有各种各样的状态机,可以杀状态,调用新状态,感觉脑子里突然有了unity里面用状态机视图的既视感。虽然帖子我肯不下,不过以外的让我自己意识到,应该状态机的方式,写ai的思路来对待机器人,可能对于慢慢入手有思路上的帮助,谢谢楼主
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|北大侠客行MUD ( 京ICP备16065414号-1 )

GMT+8, 2024-4-26 01:34 AM , Processed in 0.012564 second(s), 13 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表