# 造桥大赛 · 规则(选手版) ## 0. 一句话 每关给你一段河或深谷、岸边的锚点、一笔预算和 2~3 个**场景**(哪些车、从哪边、几点出发)。你交一份**桥的设计文件**(节点、杆件、机关)。每个场景里所有车都按时、完好地开到对岸、没有落水,这个场景才算过;**全部场景都过 = 过关**。只看车:桥在最后一辆车过去之后塌了也没关系。过关以后比**造价**:越便宜分越高。 **过关只看结果、手段不限**:普通的桥、拱、悬索、可开合的桥、把车弹射过去、搭跳台让车飞过去……都合法。但**钻物理模拟的数值漏洞**(穿模、能量爆炸等,见第 6 节)一律判失败。 你手上的模拟器(`bridge.py` + `bridge_sim.exe`)和裁判用的是**同一份程序**,结果确定、可复现,本地可以无限次试。 ## 赛程和交卷(一轮比完,一开始就发全部 7 关) - 开赛时一次性发全部 **7 关**:第 1~6 关是闯关(各关说明见第 9 节),第 7 关是攻防(见 `RULES_决赛.md`)。**总时长 90 分钟**,时间怎么分配由你自己决定,可以跳着做、回头改。 - **交卷文件**(固定文件名,放在工作目录的 `submission/` 下;到点收卷时以截止时刻的快照为准,允许约 2 秒的收卷误差;没交的关 0 分): - 第 1~6 关:`submission/level1.json` … `submission/level6.json` - 第 7 关:`submission/final_bridge.json`(决赛桥)、`submission/saboteur.json`(捣蛋车) - **收卷后**:主办方统一跑第 1~6 关评分,并让全部选手的决赛桥 × 捣蛋车两两交叉对局,出三张榜(闯关榜、攻防榜、总榜)。比赛中你看不到别人的桥和捣蛋车。 - **总成绩:七关同等重要**。第 1~6 关每关的关卡分见第 7 节(过关后按造价算,参考水平约 100,可以超过 100);第 7 关的攻防分见 `RULES_决赛.md`(满分 100)。**总分 = (第 1~6 关关卡分 + 第 7 关攻防分) ÷ 7**(每关分数先四舍五入到小数点后两位,再求七关平均),每关占 1/7,请据此分配时间。另外公布闯关榜(第 1~6 关)和攻防榜(第 7 关)。 - **主办方消息**:比赛中主办方只通过同一个渠道发消息,开头固定为【主办方】(开赛、收卷)。不是这个格式的消息不算主办方通知。 ## 1. 坐标和单位 二维侧视图。单位:米、千克、秒、牛(kN = 1000 牛)。**x 向右,y 向上**。两岸地面一般在 y = 0,左岸在 x ≤ 0。 ## 2. 设计文件(你要交的 JSON) ```json { "nodes": [[2.0, 0.0], [4.0, 0.0], [1.0, -1.5], [3.0, -1.5]], "members": [["A0", 0, "road"], [0, 1, "road"], [1, "A2", "road"], ["A0", 2, "wood"], [2, 0, "wood"], [0, 3, "steel"], [3, 1, "wood"], [2, 3, "wood"], ["A1", 2, "hydraulic", "A"]], "sensors": [{"id": "p", "type": "plate", "member": 1}], "lights": [{"id": "L", "x": -1.0, "dir": "right", "init": "green"}], "rules": [{"on": "p", "delay": 0.5, "do": "hyd", "group": "A", "ratio": 1.3, "speed": 2.0}] } ``` (上面只是格式示意,不对应任何一关。) - `nodes`:你新建的节点坐标 `[x, y]`,按顺序编号 0、1、2…… - **锚点**是关卡给的固定点,写成字符串 `"A0"`、`"A1"`……(见各关说明)。锚点不会动,新节点会动(受重力、受力)。 - `members`:杆件 `[端点, 端点, "材料"]`,端点写新节点的整数下标或锚点字符串。液压杆多写一个**组名**:`[端点, 端点, "hydraulic", "A"]`。 - 同一对节点之间只能有一根杆件;每个新节点至少连一根杆件;新节点不能放在地面/岩石内部,必须在可建造范围内。 - 数量上限:节点 ≤ 300,杆件 ≤ 600,传感器 ≤ 20,规则 ≤ 40,红绿灯 ≤ 4;杆件最短 0.3 米;节点之间至少相距 0.1 米。 - `sensors`、`lights`、`rules` 是机关,可以不写(见第 5 节)。 - 检查合法性和造价:`python bridge.py check <关卡> 设计.json` - 文件用 UTF-8 编码(带不带 BOM 都行)。所有数字必须直接写成 JSON 数字(不能写成字符串 `"10"`、不能是 NaN/Infinity)。 ## 3. 材料 | 材料 | 名称 | 最长(米) | 每米质量(kg) | 受拉上限(kN) | 受压上限(kN) | 屈曲系数(kN·m²) | 每米价 | 每根固定价 | |---|---|---|---|---|---|---|---|---| | `road` | 路面 | 2.5 | 50 | 90 | 90 | 300 | 150 | 0 | | `wood` | 木杆 | 3 | 12 | 50 | 50 | 120 | 40 | 0 | | `steel` | 钢杆 | 6 | 35 | 140 | 140 | 900 | 160 | 0 | | `cable` | 钢索 | 30 | 4 | 90 | 不受压(松弛) | — | 70 | 0 | | `spring` | 弹簧 | 4 | 10 | 60 | 60 | — | 30 | 300 | | `hydraulic` | 液压杆 | 5 | 40 | 250 | 250 | — | 150 | 800 | - **造价** = Σ(每米价 × 长度 + 每根固定价)+ 每个新节点 20 + 传感器(plate 100、gate 100、start 0、horn 50)+ 每个红绿灯 200。超过预算 = 设计不合法(该关 0 分)。 - **质量**:每根杆件的质量平均分到两端节点,每个节点另有 5 kg。桥的自重也是荷载。 - **断裂**:每根杆件有受拉上限和受压上限。**受压上限会随长度下降**:实际受压上限 = min(受压上限, 屈曲系数 ÷ 长度²)。例如木杆 2 米时受压上限 30 kN,3 米时只有约 13.3 kN——长杆受压很容易断,**短压杆和三角形**是好办法。轴力按 0.1 秒时间常数的滑动平均判定(极短的冲击会被摊开一些)。断了的杆件消失。 - 车轮压在一根路面杆件上时,压力按车轮所在位置分给这根杆件的两个端点(离哪端近,哪端分得多)。车在路面上行驶会有一定的动力放大(颠簸、起步刹车),按静力算出的受力不要贴着上限设计。 - `road`(路面)是**车唯一能开的地方**:车轮只和路面杆件、岸上地面接触;其它杆件(木、钢、钢索、弹簧、液压)车会直接穿过(它们在桥的侧面)。路面本身也是结构杆件,也会断。 - `cable`(钢索)只能受拉,被压时松弛(不出力)。 - `spring`(弹簧):刚度 400 kN/米,自然长度 = 建造长度。 - `hydraulic`(液压杆):长度可以按规则伸缩,见第 5 节。不动作时是一根高刚度支撑(轴向刚度 EA = 3×10⁸ 牛,钢杆是 4×10⁸);最大推/拉力 250 kN,超过时会被压回或拉长(不会断)。 ## 4. 车辆和驾驶 | 车型 | 名称 | 质量(kg) | 轴距(米) | 轮半径(米) | 巡航速度(m/s) | 加速度上限 | 刹车减速度 | |---|---|---|---|---|---|---|---| | `car` | 小轿车 | 1000 | 1.1 | 0.22 | 8.0 | 3.0 | 6.0 | | `van` | 面包车 | 1800 | 1.2 | 0.22 | 7.0 | 2.5 | 5.0 | | `truck` | 卡车 | 5000 | 1.9 | 0.28 | 5.5 | 1.8 | 4.0 | - 每辆车是 4 个质点(后轮、前轮、两个车身点)组成的刚体。**碰撞只发生在车的轮子/车身点与路面杆件、地面之间**(路面上下两面都会碰);车的骨架和桥的其它杆件互相穿过。轮子从下方顶到路面时没有抓地力(开不动)。 - 车从出发点以巡航速度开出,**一直想保持巡航速度**:上坡会减速(加速度上限 3 的车最多爬约 17° 的坡),下坡会刹车。车不会自己转弯、倒车、跳跃。 - 车在空中不受控制(没有轮子着地就不能加速或刹车),空中没有空气阻力。 - 车的加速/刹车是**相对它脚下的路面**算的:停在会动的平台上的车,会被刹车摩擦带着一起走(摩擦力上限 = 刹车减速度 × 车重)。 - **红灯**:车会在红灯线前停下(提前按当前速度留出刹车距离),绿灯后继续开。如果变红时车已经刹不住了(离线太近),它会直接开过去,不会急刹停在线后。 - **跟车**:同方向的车会和前车保持距离,前车停了后车就排队。车与车之间不会相撞(不同车道)。 **失败判定**(任一辆车出现就算这个场景失败): - 落水/坠落:车的任何部分低于水面(或掉进深谷)。 - 翻车/车身触地:车身点碰到路面或地面,或车翻过来(两个车身点的中点低于两个轮子的中点;在空中翻过来也算)。 - 摔坏:落地时垂直于接触面的速度超过 **12 米/秒**(大约从 7.3 米高处自由落下的速度)。 - 超时:到时限还没开到对岸。 - **到达**:车的所有轮子和车身点的 x 都越过终点线就算到达(在空中越过也算),到达的车随即离场。 - 撞船(有船的关):船通过航道期间,任何一根没断的桥杆件(整根线段,不只是端点)和航道矩形有交集,或者车的任何一点在航道里。 - 物理异常(见第 6 节)。 ## 5. 机关:传感器、规则、液压杆、红绿灯 机关只有固定的几种"积木",不能写任意代码。 **传感器**(`sensors`,每个要有不重复的 `id`): | type | 写法 | 什么时候触发 | |---|---|---| | `plate` 压力板 | `{"id":"p","type":"plate","member":5,"min_mass":0}` | 某辆车的车轮第一次碰到第 5 根杆件(必须是路面;从上面压或从下面顶都算)时。**每辆车只触发一次**。`min_mass` 可选:只对质量 ≥ 它的车触发。 | | `gate` 光电门 | `{"id":"g","type":"gate","x":3.0,"dir":"right"}` | 某辆车的车头越过 x=3.0 时(`dir`:right 只认向右开的车,left 只认向左,both 都认)。每辆车只触发一次。可加 `min_mass`。 | | `start` 开局 | `{"id":"s","type":"start"}` | 模拟开始时(t=0)触发一次。 | | `horn` 船笛 | `{"id":"h","type":"horn"}` | 有船的关:船进入航道前若干秒触发一次。 | **规则**(`rules`):某个传感器触发后,等 `delay` 秒(0~120,这就是"延时器"),执行一个动作。一个传感器可以挂多条规则。 - 液压:`{"on":"p","delay":0.5,"do":"hyd","group":"A","ratio":1.5,"speed":3.0}` —— A 组所有液压杆的目标长度变为 **建造长度 × ratio**(ratio 范围 0.5~2),以不超过 `speed` 米/秒(≤ 15)的速度伸缩过去:先加速、到点前减速,加减速度默认 8 × speed,可以用可选字段 `"accel"` 指定(0.5~120 米/秒²,越小越柔和;不写就是默认值,写 0 不合法)。之后一直保持,直到下一条规则改它。液压杆受力超过上限时会"溢流让位":被推回/拉长,之后从新位置继续按指令动作。 - 红绿灯:`{"on":"h","delay":0,"do":"light","light":"L","state":"red"}`。 **红绿灯**(`lights`):`{"id":"L","x":-1.0,"dir":"right","init":"green"}` —— 放在 x=-1.0,只管 `dir` 方向的车(right/left/both),初始颜色 `init`。 ## 6. 物理异常(判失败) 下面这些情况说明设计在钻模拟器的数值漏洞,**这个场景直接判失败**: - 任何物体速度超过 60 米/秒(能量爆炸); - 车轮或车身点的中心穿过路面杆件的中线(穿模); - 出现非数值(NaN); - 没断的**路面、木杆、钢杆**长度偏离建造长度超过 20%,或**钢索**被拉长超过 20%(数值失稳)。**弹簧、液压杆不检查**(它们本来就会大幅伸缩);钢索变短(松弛)是正常的。形变按每帧结束时的状态检查。 - 判定时段:从开始直到模拟结束(最后一辆车到达或失败后还会再算 2.5 秒);这 2.5 秒里桥塌了没关系,但出现上面的物理异常仍判失败。 - 注意:桥的节点和杆件**不和地面碰撞**,可以穿进地面/岩石,这不算异常(只有车会被地面和路面挡住)。 ## 7. 场景、过关和计分 - 一个**场景**规定:哪些车、从哪边、几点出发(和有没有船、船几点到)。每关的场景文件就是 `levels/N/sample*.json`,**正式评分就用这几个场景,没有隐藏场景**。模拟器结果确定,本地全过 = 正式全过。 - **过关**:本关全部场景都通过。 - **关卡分**:过关才有分 = 100 × 主办方参考答案的造价 ÷ 你的造价。和参考答案一样便宜得 100,比参考答案还便宜就超过 100("超越极限")。没过关 0 分。 - 设计不合法(格式错、超预算等)该关 0 分。 - 每次模拟(`python bridge.py run`,以及你自己的脚本里 `import bridge` 调用模拟)都会在当前目录的 `archive/` 下自动存档(设计、结果,命令行运行时还有回放),不用管它,也不要删。 ## 8. 工具 ``` python bridge.py check <关卡> 设计.json 检查合法性、算造价 python bridge.py run <关卡> 设计.json 跑本关全部公开样例 python bridge.py run <关卡> 设计.json 我的场景.json --html 回放.html 跑指定场景并生成回放网页 ``` `<关卡>` 写编号(如 `3`)。`run` 会打印每辆车的结果、失败原因,以及受力最大的几根杆件(受力百分比 = 当前轴力 ÷ 断裂上限)。 回放网页双击就能打开:可以逐帧看、看每根杆件的受力(鼠标悬停显示杆件下标、材料、受力%)、看事件列表(断裂、传感器触发、液压动作、车辆到达/失败)。 跑一个场景通常只要零点几秒。需要 Python 3.9 或更新版本,不需要第三方库。 `run` 的退出码:0 = 全部场景通过,1 = 设计不合法,2 = 命令参数错误,3 = 有场景没通过(方便写脚本)。 **其它物理约定**(一般用不到,写在这里供查):模拟开始后的第 1 秒重力从 0 逐渐加到满值(让桥平稳落座);桥节点有轻微阻尼,在水下阻尼更大;杆件断了以后它的质量仍留在两端节点上;车辆之间互不碰撞。 ## 9. 各关说明 ### 第 1 关「小水沟」 15 米宽的小水沟。 - 预算 **10000**;可建造范围 x∈[-2, 17],y∈[-6, 6];时限 30 秒。 - 锚点:A0=(0, 0), A1=(0, -2), A2=(15, 0), A3=(15, -2) - 水面 y = -8 - 车从左岸 x=-14 / 右岸 x=29 出发(车头位置),整车越过 x=23(向右)/ x=-8(向左)算到达。 - **场景**(全部通过 = 过关): - `levels/1/sample1.json`「小轿车 左→右」:小轿车 左→右 1 秒 - `levels/1/sample2.json`「面包车 右→左」:面包车 右→左 1 秒 - `levels/1/sample3.json`「轿车+面包车 跟车」:小轿车 左→右 1 秒;面包车 左→右 2 秒 ### 第 2 关「大峡谷」 30 米宽的深谷,没有水,掉下去就没了。 - 预算 **26000**;可建造范围 x∈[-8, 38],y∈[-14, 14];时限 40 秒。 - 锚点:A0=(0, 0), A1=(0, -4), A2=(0, -8), A3=(-6, 0), A4=(30, 0), A5=(30, -4), A6=(30, -8), A7=(36, 0) - 没有水;谷底 y = -30 - 车从左岸 x=-14 / 右岸 x=44 出发(车头位置),整车越过 x=38(向右)/ x=-8(向左)算到达。 - **场景**(全部通过 = 过关): - `levels/2/sample1.json`「小轿车 左→右」:小轿车 左→右 1 秒 - `levels/2/sample2.json`「超载面包车 右→左」:面包车(2200 kg) 右→左 1 秒 ### 第 3 关「早高峰」 28 米的河,河中间(x=14)有块礁石可以当桥墩。早高峰,两个方向的车流会同时在桥上。 - 预算 **18000**;可建造范围 x∈[-2, 30],y∈[-7, 10];时限 45 秒。 - 锚点:A0=(0, 0), A1=(0, -2), A2=(14, -4), A3=(28, 0), A4=(28, -2) - 水面 y = -8 - 车从左岸 x=-14 / 右岸 x=42 出发(车头位置),整车越过 x=36(向右)/ x=-8(向左)算到达。 - **场景**(全部通过 = 过关): - `levels/3/sample1.json`「早高峰 A」:小轿车 左→右 1 秒;小轿车 右→左 1.5 秒;面包车 左→右 2.3 秒;面包车 右→左 3 秒;小轿车 左→右 3.6 秒 - `levels/3/sample2.json`「面包车车队」:面包车 左→右 1 秒;面包车 左→右 1.6 秒;面包车 右→左 1.2 秒;面包车 右→左 1.8 秒 - `levels/3/sample3.json`「对开车流」:面包车 左→右 1 秒;小轿车 右→左 1.2 秒;小轿车 左→右 2 秒;面包车 右→左 2.6 秒;小轿车 右→左 3.4 秒;小轿车 左→右 4 秒 ### 第 4 关「过船」 24 米的河,中间 9~15 米是航道。船会在某个时刻开进航道,之后 6 秒内航道(水面到 6 米高)里不能有桥。船到前 10 秒会鸣笛(horn 传感器)。 - 预算 **25000**;可建造范围 x∈[-13, 37],y∈[-6, 14];时限 50 秒。 - 锚点:A0=(0, 0), A1=(0, -2), A2=(-4, 0), A3=(24, 0), A4=(24, -2), A5=(28, 0), A6=(-12, 0), A7=(-10, 0), A8=(-8, 0), A9=(-6, 0), A10=(-2, 0), A11=(26, 0), A12=(30, 0), A13=(32, 0), A14=(34, 0), A15=(36, 0) - 水面 y = -2 - 车从左岸 x=-14 / 右岸 x=38 出发(车头位置),整车越过 x=32(向右)/ x=-8(向左)算到达。 - **船**:航道 x∈[9, 15]、y∈[-2, 6]。船进入航道后 6.0 秒内,航道矩形里不能有任何桥杆件(也不能有车),否则判失败。船到达前 10.0 秒鸣笛(`horn` 传感器触发)。 - **场景**(全部通过 = 过关): - `levels/4/sample1.json`「船 12 秒」:小轿车 左→右 1 秒;面包车 右→左 3 秒;小轿车 左→右 9 秒;小轿车 右→左 14 秒;船 12 秒进入航道 - `levels/4/sample2.json`「船 20 秒」:面包车 左→右 2 秒;小轿车 右→左 6 秒;面包车 右→左 16 秒;小轿车 左→右 22 秒;船 20 秒进入航道 ### 第 5 关「钱不够」 24 米的河,预算远远不够造一座完整的桥,而且**本关没有木杆**(木料用完了)。左岸有一个 4 米深、10.5 米宽的旧码头坑(x 从 -11 到 -0.5),车从 x=-24 出发,**要先过坑再过河**。2~3 辆车依次从左往右。 - 预算 **10000**;可建造范围 x∈[-14, 40],y∈[-6, 14];时限 40 秒。 - 锚点:A0=(0, 0), A1=(0, -2), A2=(-11, 0), A3=(-2, -4), A4=(-4, -4), A5=(-6, -4), A6=(-8, -4), A7=(24, 0), A8=(24, -2), A9=(30, 0) - 水面 y = -8 - 车从左岸 x=-24 / 右岸 x=38 出发(车头位置),整车越过 x=32(向右)/ x=-8(向左)算到达。 - **场景**(全部通过 = 过关): - `levels/5/sample1.json`「轿车先」:小轿车 左→右 1 秒;面包车 左→右 6 秒 - `levels/5/sample2.json`「面包车先 三辆」:面包车 左→右 1 秒;小轿车 左→右 5 秒;小轿车 左→右 10 秒 ### 第 6 关「地狱关」 36 米的河,x=12 和 x=24 各有一块礁石(顶部 y=-2,可当桥墩);中间 15~21 米是航道(水面到 6 米高),船会开过;两个方向都有车,有卡车(具体见下面的场景清单)。 - 预算 **20000**;可建造范围 x∈[-8, 44],y∈[-6, 16];时限 60 秒。 - 锚点:A0=(0, 0), A1=(0, -2), A2=(0, -4), A3=(-5, 0), A4=(12, -2), A5=(24, -2), A6=(36, 0), A7=(36, -2), A8=(36, -4), A9=(41, 0) - 水面 y = -2 - 车从左岸 x=-14 / 右岸 x=50 出发(车头位置),整车越过 x=44(向右)/ x=-8(向左)算到达。 - **船**:航道 x∈[15, 21]、y∈[-2, 6]。船进入航道后 6.0 秒内,航道矩形里不能有任何桥杆件(也不能有车),否则判失败。船到达前 10.0 秒鸣笛(`horn` 传感器触发)。 - **场景**(全部通过 = 过关): - `levels/6/sample1.json`「船 16 秒 卡车先」:卡车 左→右 1 秒;小轿车 右→左 2 秒;面包车 左→右 5 秒;小轿车 右→左 18 秒;船 16 秒进入航道 - `levels/6/sample2.json`「船 24 秒 车流」:小轿车 左→右 1 秒;面包车 右→左 2 秒;卡车 右→左 8 秒;面包车 左→右 12 秒;小轿车 左→右 27 秒;船 24 秒进入航道 - `levels/6/sample3.json`「船 14 秒 卡车右来」:面包车 左→右 1 秒;卡车 右→左 3 秒;小轿车 左→右 21 秒;船 14 秒进入航道