j135 draw-only geometry overlay (TR_GEO_DEBUG) + global debug-draw switch (TR_DEBUG_DRAW)
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
## Deterministic, offline check of the geometry overlay (`geo_overlay`).
|
||||
##
|
||||
## NO battle, NO Java, NO server: fixed positions/headings, then assert the
|
||||
## circle/diameter property, the two line-circle intersections per heading line,
|
||||
## and the hand-computed "other" points. Also pins the two env switches and the
|
||||
## SVG emission (2 line segments + 5 circles: 1 outline + 2 hollow self marks +
|
||||
## 2 filled other marks).
|
||||
##
|
||||
## Run: nim c -r ModularBot_garage/tests/test_geo_overlay.nim
|
||||
|
||||
import std/[math, os, strutils]
|
||||
import robocode_tankroyale_botapi
|
||||
import geo_overlay
|
||||
|
||||
var failures = 0
|
||||
proc check(name: string, ok: bool) =
|
||||
if ok: echo "PASS: ", name
|
||||
else: echo "FAIL: ", name; inc failures
|
||||
|
||||
proc approx(a, b: float, eps = 1e-6): bool = abs(a - b) < eps
|
||||
|
||||
proc crossOnLine(px, py, headingDeg, qx, qy: float): float =
|
||||
## |(q - p) x direction| / |q - p| == perpendicular distance of q from the
|
||||
## line through p along `headingDeg`; 0 when q is on the line.
|
||||
let h = headingDeg * PI / 180.0
|
||||
let dx = cos(h)
|
||||
let dy = sin(h)
|
||||
let vx = qx - px
|
||||
let vy = qy - py
|
||||
let len = hypot(vx, vy)
|
||||
if len < 1e-9: return 0.0
|
||||
abs(vx * dy - vy * dx)
|
||||
|
||||
# ── the env switches ─────────────────────────────────────────────────────────
|
||||
|
||||
proc testEnvSwitches() =
|
||||
delEnv(GeoDebugEnv)
|
||||
check "TR_GEO_DEBUG defaults OFF", not geoDebugEnabled()
|
||||
putEnv(GeoDebugEnv, "1")
|
||||
check "TR_GEO_DEBUG=1 is ON", geoDebugEnabled()
|
||||
putEnv(GeoDebugEnv, "0")
|
||||
check "TR_GEO_DEBUG=0 is OFF", not geoDebugEnabled()
|
||||
delEnv(GeoDebugEnv)
|
||||
|
||||
delEnv(DebugDrawEnv)
|
||||
check "TR_DEBUG_DRAW defaults ON", debugDrawEnabled()
|
||||
putEnv(DebugDrawEnv, "0")
|
||||
check "TR_DEBUG_DRAW=0 is OFF", not debugDrawEnabled()
|
||||
putEnv(DebugDrawEnv, "false")
|
||||
check "TR_DEBUG_DRAW=false is OFF", not debugDrawEnabled()
|
||||
putEnv(DebugDrawEnv, "1")
|
||||
check "TR_DEBUG_DRAW=1 is ON", debugDrawEnabled()
|
||||
delEnv(DebugDrawEnv)
|
||||
|
||||
# ── hand-computed case A: heading along the diameter ─────────────────────────
|
||||
|
||||
proc testCaseA() =
|
||||
let selfX = 100.0; let selfY = 100.0; let selfH = 0.0
|
||||
let enX = 300.0; let enY = 100.0; let enH = 180.0
|
||||
let c = makeDiameterCircle(selfX, selfY, enX, enY)
|
||||
check "A: centre is the midpoint (200,100)",
|
||||
approx(c.cx, 200.0) and approx(c.cy, 100.0)
|
||||
check "A: radius is distance/2 (=100)", approx(c.r, 100.0)
|
||||
check "A: our bot lies on the circle",
|
||||
onCircle(GeomPoint(x: selfX, y: selfY), c)
|
||||
check "A: enemy lies on the circle",
|
||||
onCircle(GeomPoint(x: enX, y: enY), c)
|
||||
|
||||
let ipSelf = lineCircleIntersections(selfX, selfY, selfH, c)
|
||||
check "A: our line yields exactly two intersections", ipSelf.len == 2
|
||||
let soSelf = pickSelfOther(ipSelf, selfX, selfY)
|
||||
check "A: our own point is an intersection (100,100)",
|
||||
approx(soSelf.self.x, 100.0) and approx(soSelf.self.y, 100.0)
|
||||
check "A: our OTHER point is hand-computed (300,100)",
|
||||
soSelf.hasOther and approx(soSelf.other.x, 300.0) and
|
||||
approx(soSelf.other.y, 100.0)
|
||||
|
||||
let ipEnemy = lineCircleIntersections(enX, enY, enH, c)
|
||||
check "A: enemy line yields exactly two intersections", ipEnemy.len == 2
|
||||
let soEnemy = pickSelfOther(ipEnemy, enX, enY)
|
||||
check "A: enemy's own point is an intersection (300,100)",
|
||||
approx(soEnemy.self.x, 300.0) and approx(soEnemy.self.y, 100.0)
|
||||
check "A: enemy's OTHER point is hand-computed (100,100)",
|
||||
soEnemy.hasOther and approx(soEnemy.other.x, 100.0) and
|
||||
approx(soEnemy.other.y, 100.0)
|
||||
|
||||
# ── hand-computed case B: headings not along the diameter ────────────────────
|
||||
|
||||
proc testCaseB() =
|
||||
let selfX = 0.0; let selfY = 0.0; let selfH = 45.0
|
||||
let enX = 100.0; let enY = 0.0; let enH = 0.0
|
||||
let c = makeDiameterCircle(selfX, selfY, enX, enY)
|
||||
check "B: centre (50,0) r=50", approx(c.cx, 50.0) and approx(c.r, 50.0)
|
||||
check "B: both bots on the circle",
|
||||
onCircle(GeomPoint(x: selfX, y: selfY), c) and
|
||||
onCircle(GeomPoint(x: enX, y: enY), c)
|
||||
|
||||
let ipSelf = lineCircleIntersections(selfX, selfY, selfH, c)
|
||||
check "B: our line yields exactly two intersections", ipSelf.len == 2
|
||||
let soSelf = pickSelfOther(ipSelf, selfX, selfY)
|
||||
check "B: our own point is (0,0)",
|
||||
approx(soSelf.self.x, 0.0) and approx(soSelf.self.y, 0.0)
|
||||
check "B: our OTHER point is hand-computed (50,50)",
|
||||
soSelf.hasOther and approx(soSelf.other.x, 50.0) and
|
||||
approx(soSelf.other.y, 50.0)
|
||||
check "B: the other point lies on BOTH the line and the circle",
|
||||
crossOnLine(selfX, selfY, selfH, soSelf.other.x, soSelf.other.y) < 1e-6 and
|
||||
onCircle(soSelf.other, c)
|
||||
|
||||
let ipEnemy = lineCircleIntersections(enX, enY, enH, c)
|
||||
check "B: enemy line yields exactly two intersections", ipEnemy.len == 2
|
||||
let soEnemy = pickSelfOther(ipEnemy, enX, enY)
|
||||
check "B: enemy's other point is (0,0)",
|
||||
soEnemy.hasOther and approx(soEnemy.other.x, 0.0) and
|
||||
approx(soEnemy.other.y, 0.0)
|
||||
check "B: enemy's other point lies on BOTH line and circle",
|
||||
crossOnLine(enX, enY, enH, soEnemy.other.x, soEnemy.other.y) < 1e-6 and
|
||||
onCircle(soEnemy.other, c)
|
||||
|
||||
# ── case C: tangent line (two points coincide) ───────────────────────────────
|
||||
|
||||
proc testCaseC() =
|
||||
let selfX = 0.0; let selfY = 0.0; let selfH = 90.0
|
||||
let enX = 100.0; let enY = 0.0; let enH = 0.0
|
||||
let c = makeDiameterCircle(selfX, selfY, enX, enY)
|
||||
let ipSelf = lineCircleIntersections(selfX, selfY, selfH, c)
|
||||
check "C: tangent line still gives two (coincident) points", ipSelf.len == 2
|
||||
check "C: both tangent points are the bot itself",
|
||||
approx(ipSelf[0].x, 0.0) and approx(ipSelf[0].y, 0.0) and
|
||||
approx(ipSelf[1].x, 0.0) and approx(ipSelf[1].y, 0.0)
|
||||
|
||||
# ── degenerate case: bots coincide ───────────────────────────────────────────
|
||||
|
||||
proc testDegenerate() =
|
||||
let prims = collectGeoPrims(50.0, 50.0, 0.0, 50.0, 50.0, 180.0)
|
||||
check "coincident bots: nothing drawn (no divide by zero)", prims.len == 0
|
||||
|
||||
# ── primitive collection + emission ──────────────────────────────────────────
|
||||
|
||||
proc testPrimsAndEmit() =
|
||||
let prims = collectGeoPrims(100.0, 100.0, 0.0, 300.0, 100.0, 180.0)
|
||||
check "non-degenerate case draws 1 circle + 2 lines + 4 marks = 7",
|
||||
prims.len == 7
|
||||
var circles, lines, selfMarks, otherMarks = 0
|
||||
for p in prims:
|
||||
case p.kind
|
||||
of gpCircle: inc circles
|
||||
of gpLineSelf, gpLineEnemy: inc lines
|
||||
of gpMarkSelf: inc selfMarks
|
||||
of gpMarkOther: inc otherMarks
|
||||
check "primitive mix is (1 circle, 2 lines, 2 self marks, 2 other marks)",
|
||||
circles == 1 and lines == 2 and selfMarks == 2 and otherMarks == 2
|
||||
check "self and other marks use distinct colours",
|
||||
GeoSelfMarkColor != GeoOtherMarkColor
|
||||
|
||||
clearGraphics()
|
||||
let n = drawGeoOverlay(100.0, 100.0, 0.0, 300.0, 100.0, 180.0)
|
||||
let svg = svgOutput()
|
||||
check "drawGeoOverlay returns the primitive count (7)", n == 7
|
||||
check "SVG carries 2 line segments", svg.count("<line") == 2
|
||||
check "SVG carries 5 circles (1 outline + 2 hollow + 2 filled)",
|
||||
svg.count("<circle") == 5
|
||||
check "SVG is non-empty and wrapped", svg.startsWith("<svg") and
|
||||
svg.endsWith("</svg>")
|
||||
|
||||
testEnvSwitches()
|
||||
testCaseA()
|
||||
testCaseB()
|
||||
testCaseC()
|
||||
testDegenerate()
|
||||
testPrimsAndEmit()
|
||||
|
||||
if failures > 0:
|
||||
echo "\n", failures, " check(s) FAILED"
|
||||
quit(1)
|
||||
echo "\nAll geo-overlay checks passed."
|
||||
Reference in New Issue
Block a user