跳转至内容
  • 欢迎
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠
品牌标识

Hellclient 社区

  1. 主页
  2. Script脚本
  3. 深入浅出制作全自动Mud机器人-指令队列

深入浅出制作全自动Mud机器人-指令队列

已定时 已固定 已锁定 已移动 Script脚本
机器人全自动范例代码
1 帖子 1 发布者 13 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • jarlyynJ 离线
    jarlyynJ 离线
    jarlyyn
    写于 最后由 jarlyyn 编辑
    #1

    机器人的代码组织有很多种方式。

    我自己的机器人其实经历过3个阶段

    1. helllua ,使用的是回调链
    2. pkuxkx.noob ,试图尝试使用状态机,后来切换到指令队列
    3. newhelljs 精简的指令队列

    这里就说下我目前使用的指令队列结构。

    老规矩,线上代码

    代码地址

    什么是指令队列

    其实指令队列在Rts游戏以及衍生出的Moba游戏里是很常见的。

    就是在war3,starcraft或者dota2里,按住shift,依次发布多个指令,这些指令就会进入队列,一个一个执行完。

    这很策略,巧的是,写Mud全自动机器人也是个策略活。很自然的,我就引入了指令这个概念。

    指令的实现

    指令其实很简单

        class Command {
            constructor(name, data) {
                this.Name = name
                this.Data = data
                if (module.Debug) {
                    this.Stack = (new Error()).stack
                }
            }
            Context = {}
            Name = ""
            Stack = ""
            Data = null
            OnEvent = null
        }
    

    主体就是Name和Data,以及后续会被队列引入的上下文Context。

    那么执行的代码呢?

    执行的代码是分离开的概念,放在指令执行器里.

    执行其的作用是传入队列和 指令,然后初始化对应指令功能的模块

    比如

        module.ExecutorFunction = function (commands, running) {
            running.OnStart = function (arg) {
                running.Command.Data()
            }
        }
    

    为什么把指令和执行其分开呢?

    因为这样指令队列就是纯数据不包含代码,方便Debug和进行快照/恢复

    基础队列

    很明显,我们的队列,就是一个Command数组

    我们可以对数组进行Insert和Append操作。

    可以通过Next操作弹出第一个指令执行

    然后,我们还有一个压入指令组的功能(PushCommands)

    就是冻结当前队列,执行新队列,可以Pop中断继续执行前一个队列,同时切换队列的上下文(Context, 不同指令之间共享的运行时数据)

    这个结构下,队列Quesus,本质是一个 Command的二位数组

    控制结构

    当我们有了多层的数组后,我们就可以实现简单的控制结构了。

    我定义了以下控制接口

    • Push/PushCommands 压入Queue
    • Pop 放弃当前Queue剩余内容,返回之前的调用队列
    • Snap/Rollback 做快照/回滚快照。由于 Command是纯数据结构,就是克隆一份Queue出来
    • Append/Insert 在当前队列的最前端/后端插入指令
    • Fail 报错失败,会废弃所以没有定义FailCommand的队列,直到队列全空。FailCommand相当于一般语言的try..catch
    • FinalCommand 就算Pop中断也会执行的指令

    意外应对机制

    本质来说,指令队列是预先定义蓝图,在顺利的情况下完全实行。

    但实际上,往往会有意外发生,需要跳过当前指令和指令队列强行执行其他的。

    这时候,从逻辑上我们必须提供一个快照和回滚的机制,确保意外处理后有可能继续执行当前工作的方法。

    这就是我对指令队列打的补丁。

    相当于 Mud机器中不止有一个主逻辑,只是平时其他的逻辑都是蛰伏状态。当意外发生时,副逻辑介入,先保留住逻辑现场,处理完之后,再根据实际情况决定是否要恢复主逻辑运行(实际很少这样处理)

    异步与同步

    实际上Command本质是一个轻量级的Promise。是一个对未来的期望,是异步(在将来)执行的代码。

    所以,很多时候,我们需要和实际指令进行同步,就是等待Mud服务器返回一个特殊的返回,确保之前的指令都已经执行完毕了。

    我一般会用nobusy和sync 的Command来实现这个功能

    典型代码

    以我的炼药采买为例

        Lianyao.BuyAll = (cmds) => {
            $.PushCommands(
                $.To("65"),
                $.Do(cmds.join("\n")),
                $.Do("i"),
                $.Wait(1000),
                $.Sync(),
                $.Function(Lianyao.Make)
            )
            $.Next()
        }
    

    这个能很明显的看出队列的用法

    规划一堆指令,Sync去订都执行完毕了,然后继续后续。

    最后$.Next释放控制权

    指令队列的挑战

    指令队列或者其他纯代码逻辑控制的驱动方式,最大的挑战其实是内存泄漏。

    比如如果要循环执行指令的话,不停PushCommands会堆很多很多层空队列,直到爆栈为止。

    这是必须通过在最外层Insert指令,而不是PushCommands,才能避免内存泄漏

    1 条回复 最后回复
    • jarlyynJ jarlyyn 在 引用了 此主题
    回复
    • 在新帖中回复
    登录后回复
    • 从旧到新
    • 从新到旧
    • 最多赞同


    • 登录

    • 没有帐号? 注册

    • 登录或注册以进行搜索。
    Powered by Herbrhythm.
    • 第一个帖子
      最后一个帖子
    0
    • 欢迎
    • 版块
    • 最新
    • 标签
    • 热门
    • 用户
    • 群组