Split The code is public; the app answers only to the crew.
Is a gun on the floor worth walking to? The arithmetic in `plan.ts`, tested without a world #222
Check · Review · Land · Live done — Landed and live in production.
scout/cs:scout/215-is-a-gun-on-the-floor-worth-walking-to-t → main 1 commit fork
2 files
changed +58
−2
src/client/plan.test.ts +29 −2
| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | import { expect, test } from "bun:test"; |
| 2 | 2 | import { COMPETITIVE, type Team } from "./match.ts"; |
| 3 | 3 | import { |
| 4 | CALL_LIFE, CALLS, callLabel, COMMIT_MARGIN, HUNT_AT, isCall, liveCall, mustHunt, mustPush, | |
| 5 | ordersFor, otherSite, PUSH_WINDOW, roundPlan, siteFor, timeToPlant, WALK_SPEED, type RadioCall, | |
| 4 | CALL_LIFE, CALLS, callLabel, COMMIT_MARGIN, GUN_DETOUR_MAX, HUNT_AT, isCall, liveCall, mustHunt, | |
| 5 | mustPush, ordersFor, otherSite, PUSH_WINDOW, roundPlan, siteFor, timeToPlant, WALK_SPEED, | |
| 6 | worthTheDetour, type RadioCall, | |
| 6 | 7 | } from "./plan.ts"; |
| 7 | 8 | |
| 8 | 9 | test("a side gets one site for the round, and every slot on it reads the same one", () => { |
| @@ -81,6 +82,32 @@ test("the counter-terrorists stop holding at one number, whatever the walk", () | ||
| 81 | 82 | expect(HUNT_AT).toBeGreaterThan(COMPETITIVE.plantTime + COMPETITIVE.bombTime / 2); |
| 82 | 83 | }); |
| 83 | 84 | |
| 85 | // --- pickups ---------------------------------------------------------------- | |
| 86 | ||
| 87 | test("a gun is worth the detour up to a bound in seconds of extra walking, and never past it", () => { | |
| 88 | const goal = 1000; | |
| 89 | // Straight on the way: no extra distance over walking to the goal at all. | |
| 90 | expect(worthTheDetour(400, goal - 400, goal)).toBe(true); | |
| 91 | // A stride off the direct line costs one stride there and one back. | |
| 92 | const stride = 65; | |
| 93 | expect(worthTheDetour(400 + stride, goal - 400 + stride, goal)).toBe(true); | |
| 94 | // Clear across the map: nothing on the way to the goal was saved by it. | |
| 95 | expect(worthTheDetour(4000, 4000, goal)).toBe(false); | |
| 96 | ||
| 97 | // The bound itself, checked from both sides. | |
| 98 | const atBound = goal + GUN_DETOUR_MAX * WALK_SPEED; | |
| 99 | expect(worthTheDetour(atBound, 0, goal)).toBe(true); | |
| 100 | expect(worthTheDetour(atBound + 1, 0, goal)).toBe(false); | |
| 101 | }); | |
| 102 | ||
| 103 | test("a non-finite or negative distance is never worth a detour", () => { | |
| 104 | for (const bad of [NaN, Infinity, -Infinity, -1, -0.001]) { | |
| 105 | expect(worthTheDetour(bad, 100, 100)).toBe(false); | |
| 106 | expect(worthTheDetour(100, bad, 100)).toBe(false); | |
| 107 | expect(worthTheDetour(100, 100, bad)).toBe(false); | |
| 108 | } | |
| 109 | }); | |
| 110 | ||
| 84 | 111 | // --- the radio ------------------------------------------------------------ |
| 85 | 112 | |
| 86 | 113 | const CALLED = (call: "A" | "B" | "hold", at = 10): RadioCall => ({ team: "ct", call, at }); |
src/client/plan.ts +29
| @@ -187,3 +187,32 @@ export const HUNT_AT = 42; | ||
| 187 | 187 | |
| 188 | 188 | /** True when what is left of the round is too little to spend standing still. */ |
| 189 | 189 | export const mustHunt = (secondsLeft: number): boolean => secondsLeft <= HUNT_AT; |
| 190 | ||
| 191 | // --- pickups ---------------------------------------------------------------- | |
| 192 | // | |
| 193 | // `updatePickups` (world.ts) already hands a gun to any player who walks | |
| 194 | // within pickup range of it — the same mechanism a person's own automatic | |
| 195 | // pickup uses. So a bot never needs a take action, only a goal near the gun: | |
| 196 | // the only decision left is whether the site it was already walking to is | |
| 197 | // worth delaying for. | |
| 198 | ||
| 199 | /** | |
| 200 | * Seconds of extra walking a gun on the floor may cost before it is left for | |
| 201 | * whoever passes closer. Two seconds is long enough to step off the direct | |
| 202 | * line for one sitting in a doorway on the way, short enough that one across | |
| 203 | * the site — or across the map — is not worth the detour. | |
| 204 | */ | |
| 205 | export const GUN_DETOUR_MAX = 2; | |
| 206 | ||
| 207 | /** | |
| 208 | * True when walking `toGun` to the gun and `gunToGoal` on from there costs at | |
| 209 | * most `GUN_DETOUR_MAX` seconds more than walking `toGoal` straight to the | |
| 210 | * goal. All three are distances in world units. A non-finite or negative one | |
| 211 | * is never worth it, rather than sending a bot toward NaN. | |
| 212 | */ | |
| 213 | export function worthTheDetour(toGun: number, gunToGoal: number, toGoal: number): boolean { | |
| 214 | if (![toGun, gunToGoal, toGoal].every(Number.isFinite)) return false; | |
| 215 | if (toGun < 0 || gunToGoal < 0 || toGoal < 0) return false; | |
| 216 | const detour = (toGun + gunToGoal - toGoal) / WALK_SPEED; | |
| 217 | return detour <= GUN_DETOUR_MAX; | |
| 218 | } |