CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
失落深空(一)
deadline
2021年4月24日
失落深空游戏介绍
游戏简介: 失落深空是一个四人回合制对战游戏,玩家可以控制自己的角色在地图上行动、收集物资或与其他玩家交战。为了获得游戏胜利,玩家需要集齐四把密钥,并前往逃生舱启动逃离程序。每把密钥均对应一名玩家,使用其他玩家的密钥机或拾取其他玩家死亡后的掉落物均可以获得对方玩家的密钥。因此,玩家需要同时兼顾以下三件事情:收集物资增强自身战力;防守密钥机,阻止敌人获得自己的密钥;通过机动或战斗设法获得其他玩家的密钥。玩家必须小心地规划自己的行动,在进攻、防御与发育中做出抉择,才能战胜对手取得胜利。
基本规则: 游戏共有四名玩家,每名玩家将被随机分配到 0-3 的 id,并在每个回合内按照 0-3 的顺序行动。玩家每回合可以执行一次主要行动,也可以无限次使用次要行动。使用次要行动可以在主要行动之前或之后,但一些主要行动会强制结束回合,因此在执行这些行动时请注意使用次要行动的时机。关于各类行动的机制与效果见‘行动’一节。 游戏中存在 id 为 0-3 的四把密钥,分别对应相同 id 的玩家。玩家在游戏开始时或复活时只拥有与自己 id 相同的密钥。为了取得游戏胜利,玩家必须首先设法取得全部四把密钥。取得密钥的方法包括使用密钥机或拾取掉落物,详细介绍见‘密钥’一节。在取得全部密钥后,玩家需要前往地图上唯一的逃生舱,并在逃生舱处开启逃离倒数。在倒数结束后, 玩家将成功逃离并离开游戏。 玩家的角色拥有生命值与道具栏,在被击杀后可以在重生点复活。游戏地图上的一些区域将随着游戏进程的推进在固定回合数失效,失效后的区域将不能再通过。全部区域失效后,游戏将进入加时赛阶段。在此阶段下,玩家死亡后将不会复活,并且以失败状态离开游戏。加时赛持续 30 回合后,若仍有玩家尚未离开游戏,游戏将强制结束,仍存活的玩家也将被记为失败。游戏结束后,将根据‘排名规则‘一节的规则给出四名玩家的相对排名。
密钥: 编号为 0-3 的四把密钥对应于四名玩家。每名玩家在游戏开始或重生时只拥有自己的密钥。玩家只有集齐全部四把密钥才能启动逃生舱。 地图上每名玩家的出生点与下层对应位置均有一台编号相同的密钥机,若其他玩家进入有密钥机的位置并使用密钥机,就可以获得对应编号的密钥。 当玩家死亡时,所拥有的密钥将掉落并在死亡位置形成掉落物。其他玩家可以前来拾取掉落的密钥,拾取后掉落物将消失。拾取过程中,重复的密钥将会被丢弃,而拾取密钥的玩家将会获得+1 积分。当同一位置有多名玩家死亡时,他们的掉落物将集中在一起。 玩家不能得知其他玩家拥有的密钥情况。
排名规则: 在游戏结束后,将依据逃脱回合数对四名玩家进行排名。成功逃脱的玩家先于逃脱失败的玩家。在逃脱成功的玩家中,逃脱回合数越靠前的玩家排名越高;在逃脱失败的玩家中,死亡回合数越靠后的玩家排名越高。 若有多个玩家在同一回合逃脱或死亡,则按以下规则计算积分,积分高者靠前:拾取密钥(不论何种方式)每次+1;击杀玩家每次+2;死亡每次-3。若积分相同,则行动顺序
靠后的玩家排名靠前。
角色: 玩家的生命上限为 200,并且在游戏开始和重生时均拥有满生命。损失生命值时,只能通过使用医疗包来回复。玩家可以随时读取自己和同一层内其他玩家的生命值,但不能读取位于其它层内玩家的生命值。 死亡的玩家将在 5 回合后在自己的重生点复活,复活时只持有自己的密钥,并且道具将会清空。在加时赛阶段死亡的玩家不会复活,并且直接记为失败。
行动内容: 主要行动: 攻击(Attack):攻击一名和自己在同一位置或与自己直接相邻的敌人,造成 70 伤害。由电梯串联起来的不同层位置不视为直接相邻。 移动(Move):通过一条边到达一个相邻位置。若所在位置有电梯,也可使用电梯移动到地图不同层的对应位置。电梯可以在任意层间直接移动。 治疗(Heal):玩家可以使用一个医疗包,回复 100 生命。若道具栏中没有医疗包则无法执行该操作。 拾取物品(Collect):玩家可从物资点处拾取道具,或从死亡玩家的掉落物处拾取密钥。若一个位置上同时有掉落物和物资点,玩家在一次行动中只能拾取其一。拾取物资点时, 如物资点内有物资,则可以从四种道具中任选一种拿取一个;否则,玩家不能获得任何道具。拾取密钥时,玩家获得全部密钥并+1 积分,掉落物消失。 使用密钥机(GetKey):玩家从密钥机上获取一个对应的密钥。若已经持有该密钥,则无法执行该操作。该行动将强制结束回合。 使用逃生舱(Escape):玩家使用逃生舱逃离,若尚未集齐四把密钥则无法执行该操 作。执行操作后,玩家停留在逃生舱处并进入两回合倒数,倒数结束后逃离并结束游戏。倒数期间玩家不能执行任何其它操作,但可以随时结束倒数离开。结束倒数也视为主要行动。该行动将强制结束回合。 终止倒数:玩家强制停止逃生舱的倒数,恢复可以行动的正常状态。次要行动: 使用探查(Detect):检查一个直接相邻的位置,若发现该位置放有其他玩家的陷阱,则揭示并摧毁陷阱。探查技能每次使用后有 5 回合的冷却时间。 放置陷阱(PlaceTrap):玩家在当前位置放置一个陷阱。若玩家物品栏中没有相应种类的陷阱,或者当前位置已经有陷阱(不论种类,不论是否属于自己),则不能执行该操作。 使用闪现(Blink):玩家闪现到同一层内的相邻位置,会触发目标位置的陷阱。若没有闪现道具,则不能执行该操作。
逃生舱: 地图上只有唯一的一个逃生舱,玩家必须集齐全部四把密钥才能与之互动。在成功使用逃生舱后,玩家将进入两回合倒数状态,处于倒数状态中无法使用次要行动,也不能使用除了终止倒数外的任何主要行动。在每个回合开始时,玩家的倒数-1,相当于在下两个回合的开始时成功逃离。玩家可以主动终止倒数,之后将恢复正常行动的能力。 若玩家在倒数过程中受到攻击死亡,则倒数中断,玩家正常复活。非致命的伤害不会打断倒数。
地图: 游戏地图分为 3 层,每层是一个 7*7 的点阵,尽管不是每个点都有意义。一些无向边连通了这些点,玩家可以沿边移动。玩家可以通过电梯在不同层间移动。当任意玩家使用电梯到达三层时,所有其他玩家将得到提醒。地图一、二层各有四台密钥机,相同位置的密钥机编号相同。玩家的出生点在二层的密钥机处。 地图上的一些区域被设定为功能区,它们会随着游戏的进程逐渐失效,失效后玩家将不能进入这些区域。若玩家在功能区失效时处于其内部,就会直接死亡。功能区以外的区域属于核心区域,核心区域在整局游戏中始终不会失效。
五个功能区将以上图文字从左到右的顺序依次在第 40,50,60,70,80 回合结束时失效。当全部五个功能区失效后,游戏进入加时赛阶段,此时所有尚在游戏中的玩家将不能复活,已经死亡正在等待复活的玩家(包括在最后一次失效中死亡的玩家)仍可正常复活。在第 100 回合结束时,游戏结束,仍未逃离的玩家将被判定失败。 玩家只能拥有本层的视野,看到同一层中其他玩家的位置、掉落物的位置。当玩家死亡时,视野位置将固定到玩家死亡的位置,直到玩家复活为止。 地图上的物资点初始拥有 2 点物资,每当玩家从中获取一件道具时物资-1,物资为 0 时玩家将不能继续拾取物资。物资点的物资<2 时,将在 8 回合后回复 1 点物资,直到回满为止。玩家不能在执行拾取前获知物资点内是否有物资。若拾取时未能获得物资,也视为执行了一次主要行动。
道具: 玩家可持有的道具包括医疗包、闪现和两种陷阱。四种道具的效果如下: 名称 效果 携带数量 医疗包(MedKit) 回复 100 生命值 2 闪现(Blink) 闪现到相邻位置,不能通过电梯 2 地雷(Landmine) 造成 120 伤害 3 粘弹(Slime) 跳过下两回合(本回合和下回合) 3 陷阱会在其他玩家尝试进入布置陷阱的位置时触发,在触发一次后将被摧毁。玩家可以通过探查发现并摧毁其他玩家放置的陷阱。玩家可以获得自己放置的陷阱位置,即使它们不在自己的视野范围内。玩家可以获取现存陷阱的数量与位置,但不能直接得知消失的陷阱是否成功触发、被哪个玩家触发、是否消灭了玩家。玩家不能在逃生舱、密钥机、电梯处放置陷阱。
judger的使用说明
[TOC]
1、环境说明
-
python版本:经测试,python 3.7.6以上版本可用。python 3.7.6以下版本未经测试,由于基本没有用到新特性,因此预计python3以上基本都是可用的,如果使用过程中出现了问题,建议将版本升级到3.7.6及以上。
-
依赖库:除python原生支持的库之外,还需要您安装websockets库(在安装python和pip工具包的前提下),安装方法如下:
pip install websockets
2、使用说明
judger用来让选手进行简单的本地测试用,提供了简陋的本地人人、人机、机机对战功能。
需要注意的事情是,为了加强评测安全性和增强选手的可调试性,以及出于性能方面的考虑,网站评测端进行了从架构到代码方面的彻底重构,因此只兼容了本地测试用的judger的接口和通信协议。由于本地环境和评测环境可能存在差异,本地judger和评测时使用的在输入输出限制和空间限制等方面略有不同,因此本地judger测试通过并不能代表交到网站上就绝对没有问题,但是使用本地judger测试可以帮助您在交到网站上之前避免绝大多数的错误。
使用方法一:仅用于AI和AI对战
直接使用命令行参数启动,格式如下(切到judger.py和rserver.py对应的目录下)
python judger.py <启动游戏逻辑指令> <启动AI0指令> <启动AI1指令> <配置信息> <生成录像文件路径>
这里启动游戏逻辑和启动AI的指令格式说明如下:
- 如果是编译出来的可执行文件, 直接使用相对路径或者绝对路径即可
- 如果是python等解释型语言, 则将启动指令中的空格换成+号, 比如把python main.py变成python+main.py
需要注意的是: 如果使用相对路径, 应该使用相对judger.py的路径; 由于+和空格的替换, 因此路径名中请不要含有+或者空格.
配置信息是已经废弃的协议, 因此这里随便输入个字符串即可; 生成录像文件路径同样的, 如果使用相对路径, 则需要使用相对judger.py的路径
对于失落深空,一个可行的示例如下(直接从游戏包下载里面的文件结构, 同时您需要先对样例ai进行编译,如果您变更了文件结构, 可能需要自行修改):
python judger.py python+-m+src+./src/mapconf2.map ./sdk/main ./sdk/main ./sdk/main ./sdk/main replay.json
您有可能会得到以下错误输出:
File ".../python3.7/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec)
File ".../python3.7/runpy.py", line 85, in _run_code exec(code, run_globals)
File ".../LostInSpace/src/__main__.py", line 29, in <module>
main()
File ".../LostInSpace/src/__main__.py", line 25, in main
game.start()
File ".../LostInSpace/src/GameController.py", line 53, in start
assert 0 not in initdic['player_list'] # 断言所有玩家均成功进入游戏
AssertionError
这表明有玩家没有成功进入游戏,这可能是您未进行c++编译等情况导致的。(本地测试目前强制4人都进入游戏才开始,但这一设置之后可能更改)
在使用命令行参数启动之后, 您会发现终端里输出了一些含有type和success之类的东西, 这是我们输出的调试信息,包含ai向逻辑发送的请求和逻辑向ai发送的信息.
最终在游戏结束后, 会出现类似如下的输出:
{"0": 4, "1": 3, "2":2, "1": 1}
这表示0号玩家到3号玩家获得的分数, 其中分数高的玩家获胜。具体含义请参见游戏规则的相关说明文档。
同样的, 按照上面样例指令的含义所示, 在游戏结束后您可以在Judger目录下找到replay.json, 也就是本地测试的录像文件, 使用说明参考播放器的离线模式说明.
由于AI和逻辑的标准输入输出都被重定向到管道了, 因此不建议您本地使用标准输入输出进行调试, 这可能会给您带来不必要的麻烦. 几种可行的输出中间变量的调试方案如下(同样适用于人机):
- 将中间变量的输出输出到文件里, 建议您每次输出到文件后及时进行flush, 否则可能会在AI进程结束后还有部分缓冲区内的消息没有写入文件内.
- 在游戏逻辑的代码里将中间变量输出到文件里, 由于游戏逻辑的代码文件较多, 非常不建议您阅读游戏逻辑源码, 但如您确实需要,请关注源码文件夹下的
GameController.py
文件中的solve
方法. - 将中间变量输出到stderr中。(对于c++使用std::cerr即可)
此外, 还存在修改judger内嵌AI和游戏逻辑, 使用调试工具等等方法来帮助您进行调试, 由于可能需要您做较大的代码改动或者对相关调试工具有一定的了解, 这里就不再赘述.
使用方法二: 使用测试模式
同样的, 在judger目录下, 输入以下指令
python judger.py test_mode
您可以进入简陋的用于测试的测试模式 (非常丑陋, 当时赶工测试赶出来的, 勿喷)
在进入测试模式后, 您可以输入help查看您可以使用的指令说明, 这里仅介绍下如何开启一局本地人机对局
对于启动AI, 您可以输入如下指令格式(格式检查较为严格, 多空格等情况也可能导致无效指令):
0 <index> <command>
其中
0 0 python+../../example/main.py
这里就启动了一个AI编号为0, 即先手
对于人类玩家, 您可以输入如下指令格式(目前无法使用播放器进行本地人机对局,请使用saiblo网站进行人机对战):
1 <index> <ip> <port> <room_id>
对于本地测试而言,
如果是本地对战, 一个样例指令如下:
1 1 127.0.0.1 9010 1
输入指令之前, 请确保您对应的端口号没有被其他进程占用.
之后打开您的播放器, 在online mode里面输入
<ip>:<port>/<room_id>/<name>/<seat>
其中
127.0.0.1:9010/1/Aglove/1
在输入token后点击ok, 你会发现播放器弹出无法decode的错误提示, 您不需要惊慌, 只需要淡定的点下ok就可以继续连接了.
在连接成功后, 您的控制台上会有如下输出
{"type": 1, "index": 1, "success": 1}
这个可以作为连接成功的标志
在双方玩家无论是AI还是人类都已经启动或者连接成功后, 您可以使用如下指令来启动游戏:
4 <command> <config> <replay>
是启动游戏逻辑的指令, 含义跟上面的一样; config是废弃的协议, 随便填写个字符串即可;
一个样例指令是:
4 python+../main.py 2333 replay.bin
在启动游戏后, 对局就开始了, 如果是人人或者人机, 您就可以在本地进行播放器相关的对战操作.
在对局结束后, 你可以在控制台上看到最后的分数, 跟命令行启动的分数含义是一样的. 后面可能会出现一些warning信息关于task的销毁的, 您不用理会他们. 同样的, 对局录像文件也会生成在你指定的路径下.
如果您想通过测试模式启动机机或者人人对战, 通过修改上面的指令也是可以做到的, 您甚至可以通过测试模式和您的好友远程联机, 只需要修改对应的
祝您使用愉快, 如果在使用过程中出现了一些问题, 请先仔细阅读本说明文档, 如果仍然无法解决您的问题, 请在QQ群里提出, 我们会很快为您处理.
如何快速写一个Python AI
我应该从哪里开始写代码?
在下发的AI文件夹中,我们需要在文件 AI_client 的 play 函数中编写我们AI每回合的操作。
我可以用哪些接口?
样例AI中,使用了:
self.get_my_pos()
这是最基础的,获取当前玩家位置
self.get_neighbors(pos)
通过这个函数,你可以获得与pos相邻的节点
self.get_my_num()
获得我的编号一般来说刚开始调一次就够了,但是我懒得记(X
self.get_other_pos(int)
获取别人的位置,注意不能将自己的id输入
self.get_my_hp()
获得我的血量
self.get_other_hp(int)
获得别人的血量(不要说我没警告你不能传什么)
self.view_box(“Box”,”Key”)
self.view_box(“Materials”,”Kit”)
上面两个分别代表掉落物和物资点的拾取,实际游戏中还有更多玩法等你发现
self.use_tool(“Kit”)
拿了东西不用怎么行
self.interact(“KeyMachine”)
从钥匙机获取钥匙,十分有用(除非你是个舔包高手/杀人狂魔)
self.interact(“EscapeCapsule”,1)
开始逃咯!!!
self.move(pos)
移动,如果不调这个函数也能赢,请联系游戏开发者
其余接口见python AI SDK文档。
我该如何快速地写出一个样例AI?
打开样例ai,找到play以及上面的test_move、add、bfs_move,然后复制粘贴就行了(X
我应该如何编译测试本地AI?
这是python AI,不用编译的啦。但可以在本地配合judger进行测试,见judger说明文档。
我应该如何提交AI?
接下来就可以按照saiblo上的指示进行了。
Python SDK 手册
[toc]
选手填写函数
需要选手填写的 SDK 函数为play
一个函数。
play
选手在回合内进行的所有操作。
选手可调用函数
三元组说明(重要)
下文中所有三元组坐标为list[int]
的形式,规定为:[层数, x, y]
。
get_my_num
函数原型
def get_my_num(self): ...
获得自己的编号,返回值为int
。
get_my_pos
函数原型
def get_my_pos(self): ...
获得自己的位置,返回值为三元组。
get_my_hp
函数原型
def get_my_hp(self): ...
获得自己的血量,返回值为int
。
get_other_pos
函数原型
def get_other_pos(self,player_id): ...
获得别人的位置,返回值为三元组。
get_my_hp
函数原型
def get_other_hp(self,player_id): ...
获得别人的血量,返回值为int
。
get_keys
函数原型
def get_keys(self): ...
获得当前拿到的钥匙,返回值为list[int]
。
get_neighbors
函数原型
def get_neighbors(self, pos): ...
获得某节点的相邻点集,pos
为节点坐标三元组,返回值为视野内相邻点集的三元组列表。
move
函数原型
def move(self, pos): ...
移动,pos
为节点坐标三元组,返回值为dict
,格式如下:
{
"type": "action",
"success": true, //操作是否成功
"hp": 100, //剩余血量
}
attack
函数原型
def attack(self, pos, player_id): ...
攻击,pos
为节点坐标三元组,player_id
为攻击目标(int
),无返回值。
interact
函数原型
def interact(self, tool_type): ...
与场景道具(除了门和箱子)交互,tool_type
为道具类型字符串,可取值如下:
"Console"
"EscapeCapsule"
"KeyMachine"
返回值为dict
,格式如下:
{
"type": "action",
"success": true, //操作是否成功
}
view_box
函数原型
def view_box(self, box_type, tool_type):
检视箱子,box_type
为掉落池/物资点字符串,可取值如下:
"Box"
"Materials"
若box_type为“Materials”,则第三个参数为拿取的物品,可取值如下:
"Landmine"
"Kit"
"Transport"
"Sticky"
返回值为dict
,格式如下:
{
"type": "action",
"success": true, //操作是否成功
"content": { "LandMine": 0, "Sticky": 0, "Spine": 0, "Alert": 0, "Kit": 0 } //如果成功,返回内容
}
put_trap
函数原型
def put_trap(self, trap_type):
放置陷阱,trap_type
为陷阱类型字符串,可取值如下:
"LandMine"
"Sticky"
use_tool
函数原型
def use_tool(self, tool_type):
使用道具,tool_type
为道具类型字符串,可取值如下:
"Kit"
"Transport"
end_turn
函数原型
def end_turn(self):
结束回合,无返回值。
get_landmine_pos
函数原型
def get_landmine_pos(self):
获得当前地雷位置坐标,返回值为三元组列表。
get_sticky_pos
函数原型
def get_sticky_pos(self):
获得当前粘弹位置坐标,返回值为三元组列表。
get_spawn_pos
函数原型
def get_spawn_pos(self,x):
获得编号id选手的出生位置坐标,返回值为三元组列表。
get_escape_pos
函数原型
def get_escape_pos(self):
获得逃生舱位置坐标,返回值为三元组列表。
预定义类
Node
地图节点类,用于存储地图中一个节点的信息,其成员变量如下:
pos
-
self.pos: list[int] = ...
- 该节点的坐标,类型为上文提到的坐标三元组。
interprops
-
self.interprops: list[str] = ...
- 该节点存在的交互道具,存储形式为交互道具名称的字符串组成的列表。
player
-
self.player: list[int] = ...
- 该节点上的玩家编号列表。
Edge
地图边类,存储地图中边的信息,其成员变量如下:
stpos
-
self.stpos: list[int] = ...
- 边的起点坐标,类型为上文提到的坐标三元组。
edpos
-
self.edpos: list[int] = ...
- 边的终点坐标,类型为上文提到的坐标三元组。
Tool
工具类,存储工具的相关信息,这里工具包括陷阱和医疗包,其成员变量如下:
landmine_number
-
self.landmine_number: list[int] = ...
- 地雷数目二元列表,第一个元素代表未放置的地雷个数,第二个元素代表已经放置的地雷个数。
landmine_pos
-
self.landmine_pos: list[list[int]] = ...
- 地雷放置的位置列表。列表的每一个元素类型均为坐标三元组,分别代表一个已经放置的地雷的坐标。
sticky_number
-
self.sticky_number: list[int] = ...
- 粘弹数目二元列表,同地雷。
sticky_pos
-
self.sticky_pos: list[list[int]] = ...
- 粘弹放置的位置列表,同地雷。
kit
-
self.kit: int = ...
- 医疗包个数。
-
transport
-
self.transport: int = ...
- 闪现个数。
Player
玩家类,存储一个玩家的相关信息,成员变量如下:
id
-
self.id: int = ...
- 玩家的
id
。
status
-
self.status: int = ...
- 玩家状态,0 代表活着, 1 代表死亡, 2 代表已经逃离。
hp
-
self.hp: int = ...
- 玩家的血量。
keys
-
self.keys: list[int] = ...
- 玩家拿到的钥匙列表,列表中的整数代表一个钥匙对应的玩家的编号。
tools
-
self.tools: Tool = ...
- 玩家的工具包,类型为上边定义的
Tool
类。
View
视野类,存储玩家的视野信息,成员变量如下:
self_nodes
-
self.self_nodes: list[Node] = ...
- 玩家周围的节点信息。
replay格式说明
1. 总体格式
replay文件是json格式,里面存储一个list,list的第一项是一个存储了0-3号玩家出生点位置的list,之后的每一项都是一个存储了一个大回合所有行为的list,list的最后一项是一个score_dict。
对于每个大回合对应的list,其每一项为每个小回合对应所有消息的list,该list可能为空。
replay_example.json给出了一个包含各种情况的replay文件示例。
附: score_dict的格式如下(玩家id:得分,分高的赢):
{
"0": 4,
"1": 3,
"2": 2,
"3": 1
}
2. 消息格式
对于每个消息,其总体格式为,
{
"type" : <str:消息类型>,
"playerid": <int:玩家序号> //序号从0开始,根据消息类型,可能不存在这一项
... //其他消息
}
对坐标有如下规定: 以行数为x,列数为y,规定逃生舱z=0,出生点位于z=1,余下一层z=2。并且以z=0层x-y平面的中心点为原点。用(x,y,z)表示某一点的坐标。
图示如下:
玩家相关
- 移动
{
"type" : "move",
"playerid": 1,
"pos": [1,1,1]
}
- 闪现
{ "type": "flink", "playerid": 1, "pos": [1, 1, 1] }
- detect
{ "type": "detect", "playerid": 1, "tar_pos": [1,1,1] }
- 道具更新
{
"type": "tool_update",
"playerid": 1,
"tools": {
"LandMine": 1, //现在持有可用的道具数量
"Spine": 0,
"Alert": 0,
"Sticky": 0,
"Kit": 1
}
}
- 攻击
{
"type": "attack",
"playerid": 0,
"attack": [[-1,-1,1], [-1,0,1]] //[起点坐标,终点坐标]
}
NOTE: 攻击后必然接血量更新消息,空大不合法。
- 血量更新
{
"type": "hp_update",
"playerid": 3,
"hp": 160 //现在的血量
}
- 放置陷阱
{
"type": "place_trap",
"playerid": 0,
"pos": [-1,-1,1],
"trap_type": "LandMine"
}
NOTE: 陷阱放置行为不再接tool_update消息,请播放器组自行update
- 使用血包
{
"type": "kit",
"playerid": 0,
"hp": 185 //使用过后的血量
}
- 不使用血包的治疗
{ "type": "cure", "playerid": 0, "hp": 160 //使用过后的血量 }
- 死亡
{
"type": "died",
"playerid": 1,
"box": [3,0,1] //如果没这一项,说明原来此位置就有一个掉落池了
}
- 复活
{
"type": "regenerate",
"playerid":1,
"pos": [3,-3,1] //复活位置
}
- 启动钥匙机
{
"type": "keymachine",
"playerid": 0,
"pos": [-3,3,1]
}
NOTE: 一般来说,启动钥匙机的下一回合必然出现获得钥匙消息
- 获得钥匙
{
"type": "getkey",
"playerid": 0,
"keyid": [3] //所取得的钥匙编号list
}
- 与逃生舱交互
{
"type": "escape_capsule",
"playerid": 0,
"to_escape": true, //为true为打开逃生舱,否则为中断逃生倒计时
}
- 逃生
{
"type": "escaped",
"playerid": 0
}
- ai异常
{ "type": "ai_error", "playerid": 1, "error_log": "Run_error" // AI的错误信息,从judger那里传过来的 }
场景相关
- 道具检视
{
"type": "inspect",
"pos": [-1,-1,1], //物资点位置
"playerid": 1,
"interprops": "Materials", //物资点类型,Box 或者 Materials
}
NOTE: 对于Box,必然后接getkey,对于Material,必然后接tool update
- 陷阱触发
{
"type": "map_update",
"args": ["trap_trigger", [-1,-1,1], "LandMine"] // 后两项为位置和陷阱类型
}
- 陷阱destroy
{ "type": "map_update", "args": ["trap_destroy", [-1,-1,1], "LandMine"] // 后两项为位置和陷阱类型 }
- Box消失
{ "type": "map_update", "args": ["box_disappear", [-1,-1,1]] // 后一项为位置 }
通讯格式说明
由于未及时更新,本说明仅供选手参考, 本说明中与实际通讯格式不一致之处请以实际通讯格式为准(由judger输出)!
一. 通讯流程
1. 回合开始阶段
对于每一小回合,逻辑向该回合行动的玩家发送包含自己的血量、武器充能状态、自己的行动力、陷阱状态、道具持有状态、其他人状态情况等内容的回合开始消息:
【后文中的方向总与如下规定一致】
{
"type": "roundbegin",
"state": 111, //当前的大回合数
"inturn": 1, //处于回合中的玩家的id
"status": 0, //自己的状态,0活着,1死了,2逃了,3回合跳过,在为非0的时候回合自动终止,但仍然收到此消息
"hp": 100, //hp
"keys": [1, 2, 3], //拿到的钥匙
"tools": {
"LandMine":[<int:该类陷阱的没放置的个数>, <int:已被放置的陷阱的个数>, pos1, pos2 ... posn],
"Sticky": [...],
"Kit": 1, //持有量
"Transport": 2,
},
"others": [
{ "player_id": 1, "status": 0, "keys": [1], "hp": 200},
//status: 0活着,1死了,2逃了, keys中存储钥匙对应的玩家序号
...
]
}
对于播放器,在上述内容的基础上,还增加了如下信息(注:仍然是一个包,把下面的dict update进上面的dict就行):
{
...
"attack": [0, 1], // 可以attack的玩家序列
"move": [true, false, false, true, true, false, true, false], // 八个方向上的点是否可达
"detect": true, // 是否可以进行detect
"interprops": ["Box", "KeyMachine"] // 当前节点上的交互道具列表
}
其中,8个方向顺序规定为:[(0,1,0), (0,-1,0), (1,0,0), (-1,0,0), (1,-1,0), (-1,1,0), (1,1,0), (-1,-1,0)]
,门的四个方向的顺序就是前四个的顺序。
逻辑向该回合不行动的玩家发送如下信息:
{
"type": "roundbegin",
"state": 111,
"inturn": 2
}
回合中
回合中,用户向逻辑发送行动和询问消息,逻辑对用户消息进行回复。具体格式见 二、用户向逻辑发送信息 回合结束时,用户应当向逻辑发送结束消息。
回合外信息
在别人的回合中,逻辑将给用户发出如下消息:
- 其他玩家死亡消息(包括自己)
- 其他玩家逃离消息
- 其他玩家从视野内经过
- 其他玩家得到钥匙
- 自己的陷阱被触发
这些消息将存储于buffer中,可供ai查询。回合外消息格式见 三、逻辑向玩家发送消息
二、用户向逻辑发送信息
用户行动消息
对于用户回合内所有行动消息,有如下模板:
{
"type": "action",
"action": [<行动名称>, <参数>]
}
行动消息发出之后,逻辑将向用户报告操作是否成功。对于逻辑的回复,有如下模板:
{
"type": "action",
"success": true, //操作是否成功
... //其余内容,未说明则不含其余内容
}
- 移动
{
"type": "action",
"action": ["move", [1, 1, 1]]
//三元组坐标规定为:(x, y, 层数)
}
逻辑的回复为:
{
"type": "action",
"success": true, //操作是否成功
"hp": 100, //如果成功,则hp为多少
"status": 3 // 返回自身状态,如果为3,则该回合自动结束
}
在特殊情况下,会添加如下两种消息(注:仍然是一个包,把下面的dict update进上面的dict就行),并且两种消息可以同时添加。
- 如果出现了上下楼,则在该回复的基础上添加了视野更新信息:
{
// view是list of list,每一项是一个节点的信息:[节点坐标,是否有box,上面的玩家(这项可以没有)]
"view": [ [[1,2,0], true, [1]], ... ]
}
- 如果玩家使用播放器:
{
...
"attack": [0, 1], // 可以attack的玩家序列
"move": [true, false, false, true, true, false, true, false], // 八个方向上的点是否可达,方向规定与回合开始消息一致
"detect": true, // 是否可以进行detect
"interprops": ["Box", "KeyMachine"] // 当前节点上的交互道具列表
}
- 攻击
{
"type": "action",
"action": ["attack", pos, <int:playerid>]
}
- 与场景道具交互 与箱子的交互见4
{
"type": "action",
"action": ["interact", "KeyMachine"]
//格式为["interact", <交互道具类型>],第二项与对应的类名一致
//特别的,如果要与逃生舱交互的话,则格式为 ["interact", "EscapeCapsule", bool],bool为True表示进入逃生倒计时,False表示终止计时
}
回复为:
{
"type": "action",
"success": true //操作是否成功
}
- 与箱子交互 当检视箱子时,发送如下消息:
{
"type": "action",
"action": ["interact", "Materials", <str:type>]
}
其中,第二项为Box则为掉落池,第二项为Materials则为物资点。对于Box,不含第三项,而对于Material,第三项可为Transport, Kit, LandMine, Sticky。
回复如下: 对于Box,回复如下:
{
"type": "action",
"success": true, //操作是否成功
"keys": [1, 2, 3] // 如果成功,且目标为box,则返回现有钥匙
}
{
"type": "action",
"success": true, //操作是否成功
}
- 放置陷阱
{
"type": "action",
"action": ["trap", "Sticky"]
//格式为["trap", <陷阱类型>],第二项与对应类名一致
}
- 使用道具
- 对于医疗包为:
{
"type": "action",
"action": ["tool", "Kit"]
//格式为["tool", <道具类型>]
}
医疗包的返回为:
{
"type": "action",
"success": true, //操作是否成功
"hp": <hp:int> //血量
}
对于闪现为:
{
"type": "action",
"action": ["tool", "Transport", <pos:list>]
}
回复和move
一致
- 使用探查技能
{
"type": "action",
"action": ["detect", pos]
}
回复模板为
{
"type": "action",
"success": true, //操作是否成功
"has_trap": true //是否有陷阱
}
回合结束信息
{
"type": "finish"
}
三、逻辑向玩家发送消息
逻辑向玩家发送消息时,实际上是先将满足如下模板的消息发送个judger,然后再由judger转发给各个玩家,因此上述提到的回复模板为玩家最终接收到的消息,逻辑实际发送的消息如下:
//NOTE:如下消息表示向 player0 发送 消息0, 并向 player1 发送 消息1
// 不是向 player0和1 群发 消息0和1
{
"state": 233, //表示回合数,state>0时表示正常回合
"listen": [0], //本回合只接收0号玩家的信息
"player": [0, 1], //表示发送对象
"content": ["<消息0>", "<消息1>"] //每一个消息都满足上文提到的通信模板
}
回合外消息
对于回合外消息的描述见 一. 通讯流程
回合外消息的总体模板(玩家接收到的消息)如下:
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": [<str:类别>, m1, m2 .. mn]
}
- 其他玩家死亡消息(包括自己)
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["died", 2] //第二个数为谁死了
}
- 其他玩家逃离消息
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的,逃离必在自己的小回合内发生
"content": ["escaped"]
}
- 视野消息
- 玩家相关的视野消息 其他玩家在视野内移动
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["see", "pos_update", p, <int:playerid>] //意味着该玩家的位置更新了,如果走出了视野则 p = [-1, -1, -1]
}
如果是在视野内的其他行为,则为
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["see",<str:行为名>, pos, <int:playerid> ]
//playerid:做出此行为的玩家的序号 行为名: "attack" (在视野内攻击,不知道可不可以知道攻击方向),"regenerate" (在视野内复活)
}
- 场景信息 暂时认为只能看见门的开关和控制台导致的视野变化 关于门的开关:
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["see", "interprops_status_update", pos, <str:场景道具名称>, <str:status>]
//对于门而言 就是 ["see", "interprops_status_update" , [[1,1,1], [1,2,1]], "Door", "open" / "close"] 其中倒数第三项为门所在边的端点
}
- 其他玩家得到钥匙
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的,得到钥匙必须在自己的小回合发生
"content": ["getkey", <list:playerids>] //得到了哪个人的钥匙
}
- 自己的陷阱被触发
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["trap_triggered", <str:陷阱类型>, pos] //pos为陷阱位置
}
- 自己的陷阱被摧毁
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["trap_destoryed", <str:陷阱类型>, pos] //pos为陷阱位置
}
- hp变化
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["hp_update", <hp:int>]
}
- 有玩家到达三层
{
"type": "offround",
"state": 233, //大回合数
"playerid": 1, //是在哪个玩家的小回合中发生的
"content": ["player_enter_top_layer"]
}
回合内状态更新消息
- 其他玩家死亡消息
{
"type": "other_death",
"playerid": 1 //哪个玩家死了
}
- 逃脱
{
"type": "escaped"
}
- 自己死亡
{
"type": "death",
"box": true //掉包则为true,否则为false
}
初始化消息
在游戏开始时,逻辑会告知ai其id和出生点位置。
{
"type": "id",
"id": <id:int>,
"birth_pos": [0, 0] // 自己的出生点坐标,这里略去了层数,是二元组
}
四、逻辑仅向judger发送的信息
初始化信息
//以下设置可能更改
{
"state": 0,
"time": 3, //默认时间限制
"length": 1024 //单条消息的最大长度
}
游戏结算信息
{
"state": -1,
"end_info":{
"0": <score0>,
"1": <score1>,
"2": <score2>,
"3": <score3>
}
}