## RulesProbe — measures Tank Royale server physics empirically. ## ## Stationary bot that aims at a stationary adversary and cycles through a list ## of firepowers, logging everything needed to read bullet damage, bullet speed, ## gun heat / cooling rate and the round-result score breakdown straight off the ## server's events. Used to compare server 0.35.5 against 1.3.1. ## ## Env: ## PROBE_LOG output JSONL path (default /tmp/probe_log.jsonl) ## PROBE_POWERS comma-separated firepowers to cycle (default 0.1,0.5,1,2,3) import std/[os, strutils, json, math, tables] import robocode_tankroyale_botapi const botJsonPath = currentSourcePath().parentDir / "RulesProbe.json" let logPath = getEnv("PROBE_LOG", "/tmp/probe_log.jsonl") let powerList = block: var s: seq[float] for tok in getEnv("PROBE_POWERS", "0.1,0.5,1,2,3").split(','): let t = tok.strip() if t.len > 0: try: s.add parseFloat(t) except ValueError: discard if s.len == 0: s = @[1.0] s var gLog: File var gReady = false proc logRow(row: JsonNode) = if not gReady: try: gLog = open(logPath, fmAppend) gReady = true except CatchableError: return try: gLog.writeLine($row) gLog.flushFile() except CatchableError: discard type RulesProbe = ref object of Bot powerIdx: int targetX, targetY: float hasTarget: bool fireTick: Table[int, int] fireX, fireY: Table[int, float] fireDir: Table[int, float] firePower: Table[int, float] scanX, scanY: Table[int, float] method onScannedBot*(bot: RulesProbe, e: ScannedBotEvent) = bot.targetX = e.x bot.targetY = e.y bot.hasTarget = true bot.scanX[e.scannedBotId] = e.x bot.scanY[e.scannedBotId] = e.y logRow(%*{"ev": "scan", "t": e.turnNumber, "id": e.scannedBotId, "x": e.x, "y": e.y, "e": e.energy}) method onBulletFired*(bot: RulesProbe, e: BulletFiredEvent) = let b = e.bullet bot.fireTick[b.bulletId] = e.turnNumber bot.fireX[b.bulletId] = b.x bot.fireY[b.bulletId] = b.y bot.fireDir[b.bulletId] = b.direction bot.firePower[b.bulletId] = b.power logRow(%*{"ev": "fire", "t": e.turnNumber, "bid": b.bulletId, "p": b.power, "x": b.x, "y": b.y, "dir": b.direction}) method onBulletHit*(bot: RulesProbe, e: BulletHitBotEvent) = let b = e.bullet let dist = if b.bulletId in bot.fireX: hypot(bot.scanX.getOrDefault(e.victimId, 0.0) - bot.fireX[b.bulletId], bot.scanY.getOrDefault(e.victimId, 0.0) - bot.fireY[b.bulletId]) else: -1.0 logRow(%*{"ev": "hitbot", "t": e.turnNumber, "bid": b.bulletId, "p": b.power, "dmg": e.damage, "e": e.energy, "victim": e.victimId, "ft": bot.fireTick.getOrDefault(b.bulletId, -1), "dist": dist}) method onBulletHitWall*(bot: RulesProbe, e: BulletHitWallEvent) = let b = e.bullet logRow(%*{"ev": "hitwall", "t": e.turnNumber, "bid": b.bulletId, "p": b.power, "x": b.x, "y": b.y, "dir": b.direction, "ft": bot.fireTick.getOrDefault(b.bulletId, -1), "fx": bot.fireX.getOrDefault(b.bulletId, 0.0), "fy": bot.fireY.getOrDefault(b.bulletId, 0.0), "fdir": bot.fireDir.getOrDefault(b.bulletId, 0.0)}) method onHitByBullet*(bot: RulesProbe, e: HitByBulletEvent) = logRow(%*{"ev": "hitby", "t": e.turnNumber, "p": e.bullet.power, "dmg": e.damage, "e": e.energy}) method onRoundEnded*(bot: RulesProbe, e: RoundEndedEventForBot) = let r = e.results logRow(%*{"ev": "roundend", "t": e.turnNumber, "round": e.roundNumber, "rank": r.rank, "survival": r.survival, "lastSurvivorBonus": r.lastSurvivorBonus, "bulletDamage": r.bulletDamage, "bulletKillBonus": r.bulletKillBonus, "ramDamage": r.ramDamage, "ramKillBonus": r.ramKillBonus, "totalScore": r.totalScore, "firstPlaces": r.firstPlaces, "secondPlaces": r.secondPlaces, "thirdPlaces": r.thirdPlaces}) method onGameEnded*(bot: RulesProbe, e: GameEndedEventForBot) = let r = e.results logRow(%*{"ev": "gameend", "rounds": e.numberOfRounds, "rank": r.rank, "survival": r.survival, "lastSurvivorBonus": r.lastSurvivorBonus, "bulletDamage": r.bulletDamage, "bulletKillBonus": r.bulletKillBonus, "ramDamage": r.ramDamage, "ramKillBonus": r.ramKillBonus, "totalScore": r.totalScore}) method run*(bot: RulesProbe) = setAdjustGunForBodyTurn(true) setAdjustRadarForBodyTurn(true) setAdjustRadarForGunTurn(true) setTargetSpeed(0.0) setTurnRate(0.0) logRow(%*{"ev": "start", "turn": getTurn(), "arenaW": getArenaWidth(), "arenaH": getArenaHeight(), "cooling": getGunCoolingRate(), "powers": powerList}) while isRunning(): let t = getTurn() logRow(%*{"ev": "tick", "t": t, "heat": getGunHeat(), "e": getEnergy(), "x": getX(), "y": getY(), "gd": getGunDirection(), "rd": getRadarDirection()}) setRadarTurnRate(45.0) if bot.hasTarget: let bearing = directionTo(getX(), getY(), bot.targetX, bot.targetY) let delta = calcDeltaAngle(bearing, getGunDirection()) setGunTurnRate(delta) let p = powerList[bot.powerIdx mod powerList.len] if abs(delta) < 1.0 and getGunHeat() <= 0.0 and getEnergy() > p: if setFire(p): logRow(%*{"ev": "firecmd", "t": t, "p": p, "heatBefore": getGunHeat()}) inc bot.powerIdx go() when isMainModule: var bot = RulesProbe() start(bot, botJsonPath)