diff --git a/libs/radar_lock/radar_lock.nim b/libs/radar_lock/radar_lock.nim new file mode 100644 index 0000000..9fa9722 --- /dev/null +++ b/libs/radar_lock/radar_lock.nim @@ -0,0 +1,38 @@ +## Standalone radar lock module for Robocode Tank Royale (1v1). +## No bot-specific imports — takes plain floats, returns radarTurnRate. +## +## Tank Royale radar uses standard math convention: 0° = east, CCW positive. +## Angles are in degrees. + +import std/math + +const + MaxRadarTurn* = 45.0 + DefaultOvershootDeg* = 5.0 + +var overShootDeg* = DefaultOvershootDeg + +proc init*() = + ## Reset module to defaults. + ## In your bot's constructor set: + ## adjustRadarForBodyTurn = true + ## adjustRadarForGunTurn = true + overShootDeg = DefaultOvershootDeg + +proc normalizeRelative(angle: float64): float64 {.inline.} = + result = angle mod 360.0 + if result >= 180.0: result -= 360.0 + elif result < -180.0: result += 360.0 + +proc doRadar*(currentRadarHeading, enemyBearing: float64): float64 = + ## Returns radarTurnRate (degrees/tick, positive = clockwise). + ## + ## currentRadarHeading: current radar direction in degrees (0=east, CCW+). + ## enemyBearing: absolute bearing to enemy in same coordinate system. + ## + ## Handles angle wrapping, clamps to [-45, +45], adds overshoot sweep. + var turn = normalizeRelative(enemyBearing - currentRadarHeading) + # Add overshoot in the same direction as the turn to maintain lock + if turn < 0.0: turn -= overShootDeg + else: turn += overShootDeg + result = turn.clamp(-MaxRadarTurn, MaxRadarTurn) diff --git a/libs/radar_lock/radar_lock.nimble b/libs/radar_lock/radar_lock.nimble new file mode 100644 index 0000000..042f591 --- /dev/null +++ b/libs/radar_lock/radar_lock.nimble @@ -0,0 +1,8 @@ +# Package +version = "1.0.0" +author = "Davide Cappellini" +description = "Standalone radar lock module for Robocode Tank Royale" +license = "Apache-2.0" + +# Dependencies +requires "nim >= 2.0.0" diff --git a/libs/radar_lock/tests/test_radar_lock b/libs/radar_lock/tests/test_radar_lock new file mode 100755 index 0000000..cd2bd34 Binary files /dev/null and b/libs/radar_lock/tests/test_radar_lock differ diff --git a/libs/radar_lock/tests/test_radar_lock.nim b/libs/radar_lock/tests/test_radar_lock.nim new file mode 100644 index 0000000..17e26e2 --- /dev/null +++ b/libs/radar_lock/tests/test_radar_lock.nim @@ -0,0 +1,74 @@ +import std/unittest +import std/math +import ../radar_lock + +suite "radar_lock": + + setup: + init() + + # Basic lock: radar already on enemy — overshoot pushes it slightly + test "radar on enemy — returns overshoot only": + let rate = doRadar(90.0, 90.0) + check rate == DefaultOvershootDeg + + # Cardinal directions + test "lock from 0 degrees": + let rate = doRadar(0.0, 0.0) + check rate == DefaultOvershootDeg + + test "lock from 90 degrees": + let rate = doRadar(90.0, 90.0) + check rate == DefaultOvershootDeg + + test "lock from 180 degrees": + let rate = doRadar(180.0, 180.0) + check rate == DefaultOvershootDeg + + test "lock from 270 degrees": + let rate = doRadar(270.0, 270.0) + check rate == DefaultOvershootDeg + + # Angle wrapping: radar at 350°, enemy at 10° → shortest path is +20° + test "wrapping 350 to 10 — turns right 20 + overshoot": + let rate = doRadar(350.0, 10.0) + check abs(rate - (20.0 + DefaultOvershootDeg)) < 1e-9 + + # Angle wrapping: radar at 10°, enemy at 350° → shortest path is -20° + test "wrapping 10 to 350 — turns left 20 + overshoot": + let rate = doRadar(10.0, 350.0) + check abs(rate - (-20.0 - DefaultOvershootDeg)) < 1e-9 + + # Clamping: enemy 100° away → raw turn+overshoot > 45°, must clamp + test "clamp positive — large gap": + let rate = doRadar(0.0, 100.0) + check rate == 45.0 + + test "clamp negative — large gap": + let rate = doRadar(100.0, 0.0) + check rate == -45.0 + + # Overshoot direction: turn right → positive overshoot + test "overshoot direction right": + let rate = doRadar(0.0, 30.0) # needs +30°, overshoot adds +5° + check abs(rate - 35.0) < 1e-9 + + # Overshoot direction: turn left → negative overshoot + test "overshoot direction left": + let rate = doRadar(30.0, 0.0) # needs -30°, overshoot adds -5° + check abs(rate - (-35.0)) < 1e-9 + + # Output never exceeds ±45 + test "output bounded above": + let rate = doRadar(0.0, 179.0) + check rate <= 45.0 + + test "output bounded below": + let rate = doRadar(179.0, 0.0) + check rate >= -45.0 + + # init() resets overShootDeg + test "init resets overshoot": + overShootDeg = 20.0 + init() + check overShootDeg == DefaultOvershootDeg