feat(ModularBot): wall-bounce gun, 8 guns total

- Wall-bounce gun: linear extrapolation with arena wall reflection
- Simulates enemy tick-by-tick, bouncing off walls (billiard-ball reflection)
- 4-iteration convergence on ticksToArrive like circular gun
- 8 guns: head-on, linear, circular, tsetlin, guess-factor, pattern, anti-surfer, wall-bounce
- Turret: #CCCCCC (silver), bullet: #EEEEEE (white)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 01:30:21 +02:00
parent ebe5b1b7f1
commit a3b4f26b69
2 changed files with 67 additions and 2 deletions
+11 -2
View File
@@ -17,12 +17,13 @@ import guns/tsetlin
import guns/guess_factor
import guns/pattern_matcher
import guns/anti_surfer
import guns/wall_bounce
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"]
const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce"]
type
ModularBot = ref object of Bot
@@ -38,6 +39,7 @@ type
guessFactor: GFGun
patternMatcher: PatternMatcherGun
antiSurfer: AntiSurferGun
wallBounce: WallBounceGun
mover: PhantomMeteorModule
virtualHits: int
virtualMiss: int
@@ -75,6 +77,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
var gfPreds: array[len(PowerBins), GunPrediction]
var pmPreds: array[len(PowerBins), GunPrediction]
var asPreds: array[len(PowerBins), GunPrediction]
var wbPreds: array[len(PowerBins), GunPrediction]
for i in 0..<len(PowerBins):
headsUp[i] = bot.headOn.predict(bot.lastState, bulletSpeed(PowerBins[i]))
linPreds[i] = bot.linear.predict(bot.lastState, bulletSpeed(PowerBins[i]))
@@ -83,6 +86,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
gfPreds[i] = bot.guessFactor.predict(bot.lastState, bulletSpeed(PowerBins[i]))
pmPreds[i] = bot.patternMatcher.predict(bot.lastState, bulletSpeed(PowerBins[i]))
asPreds[i] = bot.antiSurfer.predict(bot.lastState, bulletSpeed(PowerBins[i]))
wbPreds[i] = bot.wallBounce.predict(bot.lastState, bulletSpeed(PowerBins[i]))
bot.tracker.spawnBullets(0, headsUp, bot.lastState)
bot.tracker.spawnBullets(1, linPreds, bot.lastState)
@@ -93,6 +97,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
bot.tracker.spawnBullets(4, gfPreds, bot.lastState)
bot.tracker.spawnBullets(5, pmPreds, bot.lastState)
bot.tracker.spawnBullets(6, asPreds, bot.lastState)
bot.tracker.spawnBullets(7, wbPreds, bot.lastState)
# Resolve bullets that have travelled far enough
let st = bot.lastState
@@ -110,6 +115,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
of 4: bot.guessFactor.onResult(fe)
of 5: bot.patternMatcher.onResult(fe)
of 6: bot.antiSurfer.onResult(fe)
of 7: bot.wallBounce.onResult(fe)
else: discard
if fe.hit: inc bot.virtualHits else: inc bot.virtualMiss
when DebugVBullets:
@@ -133,6 +139,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
of 4: setTurretColor("#FFCC00"); setBulletColor("#FFE066") # GuessFactor: yellow
of 5: setTurretColor("#FF6600"); setBulletColor("#FF9944") # Pattern: orange
of 6: setTurretColor("#00CCFF"); setBulletColor("#66DDFF") # AntiSurf: cyan
of 7: setTurretColor("#CCCCCC"); setBulletColor("#EEEEEE") # WallBounce: silver/white
else: discard
let pred = case selectedGun
@@ -142,6 +149,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
of 4: bot.guessFactor.predict(bot.lastState, bulletSpeed(power))
of 5: bot.patternMatcher.predict(bot.lastState, bulletSpeed(power))
of 6: bot.antiSurfer.predict(bot.lastState, bulletSpeed(power))
of 7: bot.wallBounce.predict(bot.lastState, bulletSpeed(power))
else: bot.headOn.predict(bot.lastState, bulletSpeed(power))
let target = aimAngle(getX(), getY(), pred.x, pred.y)
@@ -201,7 +209,7 @@ method run*(bot: ModularBot) =
when isMainModule:
var bot = ModularBot(
tracker: vb.initTracker(7), # 0: HeadOn, 1: Linear, 2: Tsetlin, 3: Circular, 4: GuessFactor, 5: Pattern, 6: AntiSurf
tracker: vb.initTracker(8), # 0: HeadOn, 1: Linear, 2: Tsetlin, 3: Circular, 4: GuessFactor, 5: Pattern, 6: AntiSurf, 7: WallBounce
headOn: HeadOnGun(),
linear: LinearGun(),
circular: CircularGun(),
@@ -209,6 +217,7 @@ when isMainModule:
guessFactor: initGFGun(),
patternMatcher: PatternMatcherGun(),
antiSurfer: initAntiSurferGun(),
wallBounce: initWallBounceGun(),
radar: RadarLockModule(),
mover: initPhantomMeteor(),
currentGun: -1,
+56
View File
@@ -0,0 +1,56 @@
## Wall-bounce gun: linear extrapolation with arena wall reflection.
## Simulates enemy trajectory tick-by-tick, bouncing off walls like a billiard ball.
## Coordinate system: 0° = East, CCW positive (Tank Royale standard).
import std/math
import gun_harness/gun_interface
type WallBounceGun* = object
discard
proc initWallBounceGun*(): WallBounceGun = WallBounceGun()
proc simulateBounce(ex, ey, heading, speed: float, ticks: int,
arenaW, arenaH: float): (float, float) =
## Simulate tick-by-tick with wall reflection. Returns final (x, y).
const margin = BotRadius
var x = ex
var y = ey
var h = heading # radians
for _ in 0..<ticks:
var nx = x + speed * cos(h)
var ny = y + speed * sin(h)
if nx < margin:
nx = margin
h = PI - h
elif nx > arenaW - margin:
nx = arenaW - margin
h = PI - h
if ny < margin:
ny = margin
h = -h
elif ny > arenaH - margin:
ny = arenaH - margin
h = -h
x = nx
y = ny
(x, y)
proc predict*(g: var WallBounceGun, state: WorldState, bulletSpeed: float): GunPrediction =
let dist = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY)
# Iterate ticks estimate 4x to converge
var ticks = dist / bulletSpeed
for _ in 0..<4:
let (px, py) = simulateBounce(
state.enemyX, state.enemyY,
degToRad(state.enemyHeading), state.enemySpeed,
ticks.int, state.arenaWidth, state.arenaHeight)
ticks = hypot(px - state.selfX, py - state.selfY) / bulletSpeed
let (fx, fy) = simulateBounce(
state.enemyX, state.enemyY,
degToRad(state.enemyHeading), state.enemySpeed,
ticks.int, state.arenaWidth, state.arenaHeight)
GunPrediction(x: fx, y: fy)
proc onResult*(g: var WallBounceGun, e: FeedbackEvent) =
discard # analytical gun — no learning