AI 对局竞技场接入规范

本文档详细阐述「坦克动荡」物理对战沙箱的交互规范、Lua 状态数据结构、动作指令集、HTTP API 提交与任务轮询协议。

1. 快速开始

在对局进行期间,引擎按固定拍率(tick)持续演算。在每个拍次中,引擎将当前战局快照以只读表 s 传入玩家实现的 function act(s) 入口函数中。

脚本必须在限定时间内返回一条合法的行动指令字符串(或通过组合拼接)。以下是一个最小可运行且具备自动瞄准、距离逼近与开火逻辑的完整脚本:

function act(s)
  local my, foe = s.my, s.foe
  local dx, dy = foe.x - my.x, foe.y - my.y
  local ang = math.atan(dy, dx)
  if ang < 0 then ang = ang + 2 * math.pi end
  local want = math.floor(ang / (2 * math.pi) * 256 + 0.5) % 256
  local diff = (want - my.dir + 128) % 256 - 128
  if math.abs(diff) > 8 then
    if diff > 0 then return "FWD+RIGHT" end
    return "FWD+LEFT"
  end
  return "FWD+FIRE"
end
逻辑原理解析
  1. 向量计算:通过 math.atan(dy, dx) 计算己方朝向敌方坦克的方位角弧度值,并归一化至 [0, 2π) 区间。
  2. 离散化映射:将角度线性映射到引擎标准的 0 ~ 255 离散方向刻度(全圆 256 等分)。
  3. 最短旋转差角:通过模运算 (want - my.dir + 128) % 256 - 128 算出夹角差值,自动规避 0 与 255 的边界跳跃,求出最优旋转方向。
  4. 阈值对准与开火:当角差大于 8 档(约 11.25°)时持续边前进边微调转向;角差收敛后立即执行 FWD+FIRE 逼近并射击。

2. 状态对象 s 字段表

每拍传给 act(s) 的参数 s 为只读数据表。包含地图规格、自身与敌方物理属性、障碍墙一维数据及弹道快照:

字段路径 类型 单位 / 范围 详细说明
s.me integer 1 或 2 当前自身机器人在本场比赛中的玩家槽位编号。
s.w integer 固定 11 竞技场地水平横向网格数。
s.h integer 固定 7 竞技场地垂直纵向网格数。
s.tile integer 固定 15360 单个地图网格所对应的内部高精单位尺寸。整图尺寸为 168960 × 107520。
s.tick integer ≥ 0 当前对局已进行的拍数(每局从 0 开始逐拍自增)。
s.my.x, s.my.y integer 内部物理单位 己方坦克中心坐标(0 ~ 11×15360, 0 ~ 7×15360)。
s.my.dir integer 0 ~ 255 己方车身朝向与炮口角度。0 为正东(+X),64 为正南(+Y),128 为正西(-X),192 为正北(-Y)。
s.my.cooldown integer ≥ 0 开火冷却剩余拍数。当且仅当值为 0 时允许成功开火。
s.my.alive boolean true / false 己方坦克是否存活。
s.foe.x, s.foe.y integer 内部物理单位 敌方坦克中心坐标。
s.foe.dir integer 0 ~ 255 敌方车身朝向与炮口角度。
s.foe.alive boolean true / false 敌方坦克是否存活(敌方冷却 cooldown 为隐藏信息,不予透露)。
s.walls array<integer> 长度 77,值 0 或 1 11×7 竞技场网格一维数组,下标从 1 开始。1 表示实心障碍墙,0 表示空地通道。
映射换算公式:第 r 行(1~7)、第 c 列(1~11)的索引为 (r - 1) * 11 + c。
s.bullets array<table> 活跃子弹列表 场上所有存活子弹的数组。每颗子弹包含 x、y、dir、bounces、ttl 五个字段。
bullet.bounces integer 0 ~ 3 该子弹已发生的反弹次数。最多允许反弹 3 次,第 4 次撞墙时自动销毁。
bullet.ttl integer ≥ 0 子弹剩余存活拍数(Time-To-Live)。超时后未命中目标亦会自动销毁。

3. 指令系统与动力学

act(s) 每拍返回值决定坦克在该拍实施的动作。支持单个指令,亦支持通过加号 + 进行组合输入:

指令代码 动作意图 动力学效果与说明
FWD 前进 沿坦克当前 dir 朝向施加推进力。受惯性加速度平滑控制。
BACK 后退 沿坦克当前 dir 反方向施加制动力或倒车动力。
LEFT 左转 车身逆时针旋转,dir 步进减小。角速度恒定为 每拍 6 档。
RIGHT 右转 车身顺时针旋转,dir 步进增大。角速度恒定为 每拍 6 档。
FIRE 开火 若 s.my.cooldown == 0,从炮口沿 dir 射出一枚子弹,并重置冷却。
NONE 静止 本拍不施加机动或开火指令,车体在阻尼下维持自然惯性滑动。
动力学与跳弹关键规则
  • 连续物理而非网格瞬移:坦克与子弹在连续坐标系中计算位移与碰撞盒,转弯带有角动量与滑动惯性,无法瞬间停止。
  • 转向速率:每拍固定转动 6 档角度单位(6 / 256 ≈ 8.44°/拍),旋转整周需要约 43 拍。
  • 组合指令书写:可以用加号连接,例如 "FWD+RIGHT"、"FWD+FIRE"、"LEFT+FIRE"。同时返回互斥指令(如 "FWD+BACK"、"LEFT+RIGHT")将被引擎内部抵消或以最后一个为准。
  • 子弹同等杀伤:子弹反弹严格遵循镜面反射(入射角等于反射角)。子弹不认敌我,撞上自身坦克同样判定阵亡,请务必防止被自身反射跳弹误杀。

4. 提交接口

系统提供标准 HTTP REST 接口供开发者自动化提交或集成 CI/CD:

接口地址:POST https://aigameground.com/api/submit

请求头(Headers) 值 / 格式 是否必须 说明
content-type application/json 必须 载荷为标准 JSON 字符串。
x-arena-token <提交码> 必须 服务器访问凭据。若无该请求头或提交码错误,服务端返回 403 Forbidden。提交码请联系站主申请。

请求体(JSON Body):

{
  "name": "smith2",
  "source": "function act(s)\n  return \"FWD+FIRE\"\nend"
}

注:若是更新已有名称的机器人,还需在 Body 中携带 "secret": "..." 字段,详见第 6 节。

成功响应(HTTP 202 Accepted):

{
  "ok": true,
  "job": "d7f4a28b-90c1-4b72-8f90-1c3e4a5d6e7f",
  "position": 1
}

返回 202 Accepted 表示代码通过基础沙箱与语法校验,已进入后台执行队列。开发者应依据返回的 job 任务号发起状态轮询。

5. 轮询结果

接口地址:GET https://aigameground.com/api/job/<任务号>

返回任务的实时推进状态。状态流转如下:

  • queued:排队中。position 字段代表当前排在第几位。
  • running:跑局器正在执行天梯匹配对决。
  • done:多轮对决结束,积分结算完成,返回 result。
  • error:跑局异常(如超时、语法中断),错误信息载于 error 字段。

完成响应样例(status = done):

{
  "job": "d7f4a28b-90c1-4b72-8f90-1c3e4a5d6e7f",
  "status": "done",
  "position": 0,
  "error": null,
  "result": {
    "bot": {
      "name": "smith2",
      "rating": 1248,
      "matches": 12,
      "wins": 8,
      "losses": 4
    },
    "log": "vs baseline: win (+16)\nvs charger: win (+14)...",
    "secret": "9a38f7d612e45b80c98f1234567890abcdef1234567890abcdef1234567890ab"
  }
}

cURL 提交与轮询实操示例:

# 1. 发起提交任务
curl -s -X POST https://aigameground.com/api/submit \
  -H "content-type: application/json" \
  -H "x-arena-token: 你的提交码" \
  -d '{"name":"smith2","source":"function act(s) return \"FWD+FIRE\" end"}'

# 2. 轮询对战进度与天梯积分
curl -s https://aigameground.com/api/job/d7f4a28b-90c1-4b72-8f90-1c3e4a5d6e7f

最小轮询循环脚本伪代码(Python):

import time, requests

BASE_URL = "https://aigameground.com"
HEADERS = {"x-arena-token": "YOUR_TOKEN", "content-type": "application/json"}

# 1. 提交
res = requests.post(f"{BASE_URL}/api/submit", headers=HEADERS, json={
    "name": "smith2",
    "source": open("bot.lua").read(),
    "secret": "可选_已有名字密钥"
})
job_id = res.json()["job"]

# 2. 轮询结果
while True:
    status_data = requests.get(f"{BASE_URL}/api/job/{job_id}").json()
    st = status_data["status"]
    if st == "done":
        print("天梯分:", status_data["result"]["bot"]["rating"])
        print("密钥:", status_data["result"]["secret"])
        break
    elif st == "error":
        print("错误:", status_data.get("error"))
        break
    time.sleep(2)

6. 名字与密钥

为了保障开发者的劳动成果与天梯排位不被恶意覆写,竞技场实行名字先到先得所有权制度:

  • 首次登记下发密钥:当某个 name 首次提交时,系统在后台为该名称生成唯一的 64 字节密钥,并在任务状态变更为 done 时通过 result.secret 返回。
  • 二次更新必须携带密钥:下一次为同一个 name 提交新源码以迭代策略时,请求体必须附加该 secret 字段:
    {
      "name": "smith2",
      "source": "-- 优化后的 Lua 源码...",
      "secret": "9a38f7d612e45b80c98f1234567890abcdef1234567890abcdef1234567890ab"
    }
  • 未提供或密钥不符:如果名字已被占用,而请求未附带或附带了错误的 secret,服务器将直接返回 400 Bad Request(错误信息明确告知名字已被占用)。
  • 保留名称:系统内置基准机器人(例如 baseline)属于系统保留名字,禁止被外部开发者提交注册或替换。

7. 限额与沙箱约束

因服务器资源有限且对局实时并发演算,系统设立了以下硬性限流与安全沙箱规则:

限制维度 限额指标 触发后果与处理
IP 提交频率 单 IP 20 秒限提交 1 次 返回 429 Too Many Requests,须等待冷却后重试。
IP 每日配额 单 IP 每日最多提交 40 次 当日不再接收该 IP 提交请求,次日自动重置计数。
名称冷却期 同一 Bot 名称 60 秒冷却 防止脚本针对单一名字频繁打榜造成积分扰动。
脚本源码体积 下限 20 字节,上限 200 KB 超出限制的请求将被前置拒绝,防止上传大包恶意文件。
单局对抗超时 30 秒硬超时(hard timeout) 若两车消极规避无伤亡,30 秒后强制结束并裁定平局。
单拍决策超时 5 毫秒(ms)硬时限 超过 5ms 或执行语法报错,该拍动作降级为 NONE,不中断对局。
Lua 沙箱权限 仅开放 _G、math、string、table 禁用 os、io、debug、package 等,禁止网络与文件访问。

8. 排行榜与回放的公开接口

以下查询接口完全公开,无需携带 x-arena-token 凭证:

8.1 获取当前排行榜

GET https://aigameground.com/api/leaderboard
返回全服所有注册参赛机器人的当前 Elo 评分、历史总场次及胜负统计。

{
  "bots": [
    { "name": "smith2", "rating": 1248, "matches": 42, "wins": 29, "losses": 13 },
    { "name": "charger", "rating": 1190, "matches": 38, "wins": 20, "losses": 18 }
  ]
}

8.2 查询指定单局回放数据

GET https://aigameground.com/api/replay?a=名字&b=对手&seed=7
按参数调用跑局器生成两台机器人在特定随机数种子下的逐帧轨迹数据(带有 2 秒 IP 防刷频控)。

8.3 单局分享落地页

对局页面支持直达分享链接,任何人可直接在浏览器中观看 Canvas 动画回放:
URL 格式:https://aigameground.com/r/<选手A>/<选手B>/<随机种子>
真实范例:/r/smith2/charger/7