diff --git a/ModularBot_garage/src/ModularBot.nim b/ModularBot_garage/src/ModularBot.nim index 88d92d5..de63f44 100644 --- a/ModularBot_garage/src/ModularBot.nim +++ b/ModularBot_garage/src/ModularBot.nim @@ -1,5 +1,5 @@ ## ModularBot — plugin gun architecture tracer bullet. -## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), AntiSurferGun (6) via GunHarness. +## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), AntiSurferGun (6), WallBounceGun (7), AccelGun (8) via GunHarness. ## Radar: RadarLockModule via radar harness. ## Movement: OscillatorModule (perpendicular strafing). @@ -18,12 +18,13 @@ import guns/guess_factor import guns/pattern_matcher import guns/anti_surfer import guns/wall_bounce +import guns/accel_predictor import movements/phantom_meteor const botJsonPath = currentSourcePath().parentDir / "ModularBot.json" const DebugVBullets = false const DebugCircular = false -const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce"] +const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce", "Accel"] type ModularBot = ref object of Bot @@ -40,6 +41,7 @@ type patternMatcher: PatternMatcherGun antiSurfer: AntiSurferGun wallBounce: WallBounceGun + accelGun: AccelGun mover: PhantomMeteorModule virtualHits: int virtualMiss: int @@ -78,6 +80,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) = var pmPreds: array[len(PowerBins), GunPrediction] var asPreds: array[len(PowerBins), GunPrediction] var wbPreds: array[len(PowerBins), GunPrediction] + var acPreds: array[len(PowerBins), GunPrediction] for i in 0.. enScore: "ModularBot" else: enemyName + echo fmt" ModularBot: {mbScore} {enemyName}: {enScore} → {winner} wins" + echo fmt" Rounds: {r.rounds.len}" + +runAndReport("Battle 1/3: vs Crazy", "Crazy", crazyDir) +runAndReport("Battle 2/3: vs RandomMover", "RandomMover", randomMoverDir) +runAndReport("Battle 3/3: vs Walls", "Walls", wallsDir) +echo "\nAccelGun gauntlet complete." diff --git a/common_libs/guns/accel_predictor.nim b/common_libs/guns/accel_predictor.nim new file mode 100644 index 0000000..338f256 --- /dev/null +++ b/common_libs/guns/accel_predictor.nim @@ -0,0 +1,92 @@ +## Acceleration predictor gun: tracks velocity and heading changes across frames, +## extrapolates with accel+turn for bots that speed up/slow down. +## Needs 3+ frames for stable accel; falls back to linear (2 frames) or head-on (1 frame). +## Coordinate system: 0° = East, CCW positive (Tank Royale standard). + +import std/math +import gun_harness/gun_interface + +const MaxSpeed = 8.0 ## TR bot max speed (px/tick) + +type AccelGun* = object + prevSpeed: float + prevHeading: float + prevPrevSpeed: float + prevTick: int + frames: int ## how many observations we have + cachedAccel: float ## accel (px/tick²) cached per tick + cachedTurnRate: float ## turn rate (deg/tick) cached per tick + cachedTick: int + +proc initAccelGun*(): AccelGun = + AccelGun() + +proc predict*(g: var AccelGun, state: WorldState, bulletSpeed: float): GunPrediction = + if bulletSpeed <= 0.0: + return GunPrediction(x: state.enemyX, y: state.enemyY) + + # Update observation window on new tick + if state.tick > g.cachedTick: + if g.frames >= 1: + # Compute turn rate (normalised to [-180, 180]) + var tr = state.enemyHeading - g.prevHeading + if tr > 180.0: tr -= 360.0 + elif tr < -180.0: tr += 360.0 + let tickDelta = max(1, state.tick - g.prevTick).float + g.cachedTurnRate = tr / tickDelta + + if g.frames >= 2: + # Compute acceleration + g.cachedAccel = (state.enemySpeed - g.prevSpeed) / tickDelta + else: + g.cachedAccel = 0.0 + else: + g.cachedTurnRate = 0.0 + g.cachedAccel = 0.0 + + # Advance window + g.prevPrevSpeed = g.prevSpeed + g.prevSpeed = state.enemySpeed + g.prevHeading = state.enemyHeading + g.prevTick = state.tick + g.cachedTick = state.tick + inc g.frames + + # fallback: not enough history + if g.frames < 2: + return GunPrediction(x: state.enemyX, y: state.enemyY) + + let dx = state.enemyX - state.selfX + let dy = state.enemyY - state.selfY + let dist = sqrt(dx*dx + dy*dy) + var t = dist / bulletSpeed # initial ticks estimate + + let accel = g.cachedAccel + let turnRate = g.cachedTurnRate # deg/tick + + # Iterative convergence (same pattern as circular gun) + var ex = state.enemyX + var ey = state.enemyY + for _ in 0..4: + var vx = state.enemySpeed + var hd = state.enemyHeading # degrees + var px = state.enemyX + var py = state.enemyY + let iters = int(t + 0.5) + for _ in 0..