## wave_surfer.nim — Wave-surfing movement module. ## Detects enemy fire via energy drops, maintains a danger histogram (31 GF bins), ## and steers toward the lowest-danger GF bin on approaching waves. ## All angles in radians internally; output degrees for bot API. import std/[math] import gun_harness/gun_interface import movement_harness/movement_interface const WS_BINS = 31 type WSWave = object originX, originY: float64 bearing: float64 ## direction from enemy to us at fire time (radians) speed: float64 radius: float64 startDist: float64 WaveSurferModule* = object bins: array[WS_BINS, float64] waves: seq[WSWave] prevEnergy: float64 strafeDir: float64 ## +1.0 or -1.0 proc initWaveSurfer*(): WaveSurferModule = var m = WaveSurferModule(prevEnergy: 100.0, strafeDir: 1.0) for i in 0.. bestRatio: bestRatio = ratio result = i proc computeMove*(m: var WaveSurferModule, ws: WorldState): MoveCommand = let botX = ws.selfX let botY = ws.selfY let enemyX = ws.enemyX let enemyY = ws.enemyY # Fire detection let drop = m.prevEnergy - ws.enemyEnergy m.prevEnergy = ws.enemyEnergy if drop >= 0.1 and drop <= 3.0: let bspeed = 20.0 - 3.0 * drop let bearing = arctan2(botY - enemyY, botX - enemyX) let d = sqrt((botX - enemyX)^2 + (botY - enemyY)^2) m.waves.add WSWave( originX: enemyX, originY: enemyY, bearing: bearing, speed: bspeed, radius: 0.0, startDist: d, ) # Advance waves; collect hits var i = 0 while i < m.waves.len: m.waves[i].radius += m.waves[i].speed let d = sqrt((botX - m.waves[i].originX)^2 + (botY - m.waves[i].originY)^2) if m.waves[i].radius >= d: let toBot = arctan2(botY - m.waves[i].originY, botX - m.waves[i].originX) var off = toBot - m.waves[i].bearing while off > PI: off -= 2.0*PI while off < -PI: off += 2.0*PI let maxA = mea(m.waves[i].speed) if maxA >= 1e-9: let gf = clamp(off / maxA, -1.0, 1.0) m.bins[gfToBin(gf)] += 1.0 m.waves.del(i) else: inc i # Dodge logic: find nearest wave, pick safest GF bin direction let nearest = m.nearestWave(botX, botY) if nearest >= 0: let w = m.waves[nearest] let toBot = arctan2(botY - w.originY, botX - w.originX) var off = toBot - w.bearing while off > PI: off -= 2.0*PI while off < -PI: off += 2.0*PI let maxA = mea(w.speed) let curGF = if maxA >= 1e-9: clamp(off / maxA, -1.0, 1.0) else: 0.0 let curBin = gfToBin(curGF) # Average danger var total = 0.0 for b in m.bins: total += b let avg = total / float64(WS_BINS) if m.bins[curBin] > avg: # Find lowest-danger bin var bestBin = 0 for j in 1..= curGF: 1.0 else: -1.0 # Perpendicular strafe (same body trick as phantom_meteor) let enemyBearingRad = arctan2(enemyY - botY, enemyX - botX) # Perpendicular in chosen strafe direction let perpAngle = if m.strafeDir >= 0.0: enemyBearingRad + PI * 0.5 else: enemyBearingRad - PI * 0.5 let desiredDeg = radToDeg(perpAngle) var delta = desiredDeg - ws.selfHeading while delta > 180.0: delta -= 360.0 while delta < -180.0: delta += 360.0 let goForward = abs(delta) <= 90.0 if not goForward: delta = if delta >= 0.0: delta - 180.0 else: delta + 180.0 (speed: if goForward: 8.0 else: -8.0, turnRate: delta.clamp(-10.0, 10.0))