模型看到什么
Ants 的观测是一个 JSON 对象,对应一个蚁群在一个回合里的所见。你清单中的每个适配器都把这个对象当作自己的完整文档来接收。游戏不发送任何初始化消息,也不发送终局观测:一个席位一旦停止参赛,它的模型就不会再被调用。
字段
| 字段 | 形状 | 含义 |
|---|---|---|
size | [rows, columns] | 地图尺寸 |
mine | [[row, column], …] | 你所有存活的蚂蚁,先按行再按列排序 |
ids | [id, …] | mine 中每一项是哪只蚂蚁,顺序相同(见下文) |
foes | [[row, column, owner], …] | 本回合可见的敌方蚂蚁 |
food | [[row, column], …] | 本回合可见的食物 |
hills | [[row, column, owner], …] | 本回合可见、尚未被摧毁的蚁丘 |
water.rle | [value, count, value, count, …] | 行优先的已知水域掩码 |
vis.rle | [value, count, value, count, …] | 行优先掩码,标出你本回合能看到的范围 |
坐标从 0 开始,并按世界所述环绕。任何列表都可以为空。列表下标不是蚂蚁的身份:引擎为每次观测重新排序 mine,出生、死亡和移动都会改变它的顺序。身份由 ids 给出。
蚂蚁 id
ids[i] 是位于 mine[i] 的那只蚂蚁的 id。mine 保持行优先顺序,所以你的动作和策略头始终与 mine 对齐,正如模型回答什么所述。
- 一个 id 终生只属于一只蚂蚁,永不重复使用。蚂蚁在每一次移动中都保留它,被阻挡的移动也不例外;蚂蚁死亡时,它的 id 也随之消失。
- id 按席位各自计数,从 0 开始:先是你的初始蚂蚁,按你蚁丘的顺序,然后是每只新蚂蚁,按出生顺序。每个席位都以同样方式计数,所以各席位保持对称。
- 只有你自己的蚂蚁带有 id。
foes不带:敌方蚂蚁的 id 会泄露那个蚁群总共出生过多少只蚂蚁,而这正是迷雾要隐藏的信息。 - id 远小于 224,所以转换为
f32后仍然精确无损。
没有记忆的模型用不到 ids。每只蚂蚁各带一份记忆的模型则用它们找到每只蚂蚁上一回合对应的那一行(怎么做)。适配器只读取它点名的键,所以从不提到 ids 的清单照常运行,不受影响。
运行器添加的键
游戏从不发送 memory 或 ant_memory。它们属于运行器:当你的清单声明了同名输出时,运行器会在你席位的下一次观测中以同一个键把上次的值交还给你,而在第 0 回合它不存在。完整规则见记忆。两个输出都不声明的模型永远不会看到这两个键。
归属标签
归属是相对于你的。在 hills 中,归属 0 是你的,1 及以上是对手的。在 foes 中,归属从 1 起,永远不会是 0,因为敌人不可能是你自己。在双席位地图上这个标签是固定的:你的蚁丘是 0,所有敌方蚁丘和蚂蚁都是 1。
观测里没有自己席位的字段,你也不需要:在观测看来,你永远坐在席位 0。把世界里的所有席位重新编号,同一位玩家收到的字节完全相同。这一性质让一场对局的两个席位成为同一分布的两个样本,自博弈训练正依赖于此。
已知水域
RLE 按 (value, count) 成对读取。值为 0 或 1,计数按行优先顺序覆盖 rows × columns 个格子。1 表示已发现的水域。0 可能是已知的陆地,也可能是尚未探索的格子。你在之前回合发现的水域,在蚂蚁失去视野后仍保留在掩码中。
举个编码的例子:size: [2, 3] 和 rle: [0, 2, 1, 1, 0, 3] 展开为 [[0, 0, 1], [0, 0, 0]];当然没有哪个赛季会用这么小的地图。用 rle_expand 来构建张量,不必在 JSON 里逐格循环。
本回合的视野
vis 是引擎过滤这次观测所用的掩码:与你任意一只蚂蚁的距离平方不超过 77 的每个格子(含环绕)为 1,其余为 0。它使用与 water 相同的 RLE 编码,所以 rle_expand 就能构建这个平面。
vis 把“这里什么都没有”和“这里我看不见”区分开。敌方平面上的 0,在 vis 为 1 处表示该格为空;而 vis 为 0 处的 0 表示你不知道。你的编码器需要这一区分:没有它,网络就会从它本不可能看见的格子里学到“没有敌人”。
训练器可以用四行 numpy 从
mine和半径推出vis。适配器做不到:它的表达式语言无法引用外层迭代器的元素,所以无法把每只蚂蚁周围的圆盘取并集,引擎正因此才发送这个掩码。如果你的训练器自己推导这个平面,请检查两者是否一致。
被隐藏的信息
引擎把敌人、食物和蚁丘过滤到本回合的视野内;只有已知水域会从一个回合延续到下一个。数据中没有得分、回合数、蚁巢存粮数,也没有已探索掩码。
一次模型调用只看到一份观测,以及在重量级允许记忆时,模型在上一回合写下的内容。其他什么都不会延续,所以模型想记住的任何别的东西,都必须写进那份记忆。训练时就要面对与比赛中相同的信息缺失。
一个完整示例
下面这份示意性的席位 0 观测有两只蚂蚁,没有已知水域:
{
"size": [64, 96],
"mine": [[12, 30], [13, 30]],
"ids": [2, 0],
"foes": [[12, 33, 1]],
"food": [[11, 31]],
"hills": [[12, 30, 0]],
"water": {"rle": [0, 6144]},
"vis": {"rle": [0, 1054, 1, 11, 0, 5079]}
}
第二只蚂蚁是 mine[1],所以第二个动作必须指向 [13, 30]。ids 说明那只蚂蚁是 0 号蚂蚁,蚁群的第一只;蚁丘上的那只是后来出生的 2 号蚂蚁;1 号蚂蚁已经死亡。水域字段表明这个视图里没有已发现的水域;地图的其余部分可能仍有。敌人和食物的坐标都在本回合的视野之内。
查看器目前还无法展示这一点。回放帧携带的是裁判所见的地图(每一只蚂蚁、全部水域),因为重新模拟动作流重建的就是那个视图。
replay-decode还无法说出一个席位在某回合知道什么,在它能做到之前,这里放一段回放反而会教给你与本页要点相反的东西。