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:
@@ -17,12 +17,13 @@ import guns/tsetlin
|
|||||||
import guns/guess_factor
|
import guns/guess_factor
|
||||||
import guns/pattern_matcher
|
import guns/pattern_matcher
|
||||||
import guns/anti_surfer
|
import guns/anti_surfer
|
||||||
|
import guns/wall_bounce
|
||||||
import movements/phantom_meteor
|
import movements/phantom_meteor
|
||||||
|
|
||||||
const botJsonPath = currentSourcePath().parentDir / "ModularBot.json"
|
const botJsonPath = currentSourcePath().parentDir / "ModularBot.json"
|
||||||
const DebugVBullets = false
|
const DebugVBullets = false
|
||||||
const DebugCircular = false
|
const DebugCircular = false
|
||||||
const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf"]
|
const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce"]
|
||||||
|
|
||||||
type
|
type
|
||||||
ModularBot = ref object of Bot
|
ModularBot = ref object of Bot
|
||||||
@@ -38,6 +39,7 @@ type
|
|||||||
guessFactor: GFGun
|
guessFactor: GFGun
|
||||||
patternMatcher: PatternMatcherGun
|
patternMatcher: PatternMatcherGun
|
||||||
antiSurfer: AntiSurferGun
|
antiSurfer: AntiSurferGun
|
||||||
|
wallBounce: WallBounceGun
|
||||||
mover: PhantomMeteorModule
|
mover: PhantomMeteorModule
|
||||||
virtualHits: int
|
virtualHits: int
|
||||||
virtualMiss: int
|
virtualMiss: int
|
||||||
@@ -75,6 +77,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
|
|||||||
var gfPreds: array[len(PowerBins), GunPrediction]
|
var gfPreds: array[len(PowerBins), GunPrediction]
|
||||||
var pmPreds: array[len(PowerBins), GunPrediction]
|
var pmPreds: array[len(PowerBins), GunPrediction]
|
||||||
var asPreds: array[len(PowerBins), GunPrediction]
|
var asPreds: array[len(PowerBins), GunPrediction]
|
||||||
|
var wbPreds: array[len(PowerBins), GunPrediction]
|
||||||
for i in 0..<len(PowerBins):
|
for i in 0..<len(PowerBins):
|
||||||
headsUp[i] = bot.headOn.predict(bot.lastState, bulletSpeed(PowerBins[i]))
|
headsUp[i] = bot.headOn.predict(bot.lastState, bulletSpeed(PowerBins[i]))
|
||||||
linPreds[i] = bot.linear.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]))
|
gfPreds[i] = bot.guessFactor.predict(bot.lastState, bulletSpeed(PowerBins[i]))
|
||||||
pmPreds[i] = bot.patternMatcher.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]))
|
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(0, headsUp, bot.lastState)
|
||||||
bot.tracker.spawnBullets(1, linPreds, 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(4, gfPreds, bot.lastState)
|
||||||
bot.tracker.spawnBullets(5, pmPreds, bot.lastState)
|
bot.tracker.spawnBullets(5, pmPreds, bot.lastState)
|
||||||
bot.tracker.spawnBullets(6, asPreds, bot.lastState)
|
bot.tracker.spawnBullets(6, asPreds, bot.lastState)
|
||||||
|
bot.tracker.spawnBullets(7, wbPreds, bot.lastState)
|
||||||
|
|
||||||
# Resolve bullets that have travelled far enough
|
# Resolve bullets that have travelled far enough
|
||||||
let st = bot.lastState
|
let st = bot.lastState
|
||||||
@@ -110,6 +115,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
|
|||||||
of 4: bot.guessFactor.onResult(fe)
|
of 4: bot.guessFactor.onResult(fe)
|
||||||
of 5: bot.patternMatcher.onResult(fe)
|
of 5: bot.patternMatcher.onResult(fe)
|
||||||
of 6: bot.antiSurfer.onResult(fe)
|
of 6: bot.antiSurfer.onResult(fe)
|
||||||
|
of 7: bot.wallBounce.onResult(fe)
|
||||||
else: discard
|
else: discard
|
||||||
if fe.hit: inc bot.virtualHits else: inc bot.virtualMiss
|
if fe.hit: inc bot.virtualHits else: inc bot.virtualMiss
|
||||||
when DebugVBullets:
|
when DebugVBullets:
|
||||||
@@ -133,6 +139,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
|
|||||||
of 4: setTurretColor("#FFCC00"); setBulletColor("#FFE066") # GuessFactor: yellow
|
of 4: setTurretColor("#FFCC00"); setBulletColor("#FFE066") # GuessFactor: yellow
|
||||||
of 5: setTurretColor("#FF6600"); setBulletColor("#FF9944") # Pattern: orange
|
of 5: setTurretColor("#FF6600"); setBulletColor("#FF9944") # Pattern: orange
|
||||||
of 6: setTurretColor("#00CCFF"); setBulletColor("#66DDFF") # AntiSurf: cyan
|
of 6: setTurretColor("#00CCFF"); setBulletColor("#66DDFF") # AntiSurf: cyan
|
||||||
|
of 7: setTurretColor("#CCCCCC"); setBulletColor("#EEEEEE") # WallBounce: silver/white
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
let pred = case selectedGun
|
let pred = case selectedGun
|
||||||
@@ -142,6 +149,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
|
|||||||
of 4: bot.guessFactor.predict(bot.lastState, bulletSpeed(power))
|
of 4: bot.guessFactor.predict(bot.lastState, bulletSpeed(power))
|
||||||
of 5: bot.patternMatcher.predict(bot.lastState, bulletSpeed(power))
|
of 5: bot.patternMatcher.predict(bot.lastState, bulletSpeed(power))
|
||||||
of 6: bot.antiSurfer.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))
|
else: bot.headOn.predict(bot.lastState, bulletSpeed(power))
|
||||||
let target = aimAngle(getX(), getY(), pred.x, pred.y)
|
let target = aimAngle(getX(), getY(), pred.x, pred.y)
|
||||||
|
|
||||||
@@ -201,7 +209,7 @@ method run*(bot: ModularBot) =
|
|||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
var bot = ModularBot(
|
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(),
|
headOn: HeadOnGun(),
|
||||||
linear: LinearGun(),
|
linear: LinearGun(),
|
||||||
circular: CircularGun(),
|
circular: CircularGun(),
|
||||||
@@ -209,6 +217,7 @@ when isMainModule:
|
|||||||
guessFactor: initGFGun(),
|
guessFactor: initGFGun(),
|
||||||
patternMatcher: PatternMatcherGun(),
|
patternMatcher: PatternMatcherGun(),
|
||||||
antiSurfer: initAntiSurferGun(),
|
antiSurfer: initAntiSurferGun(),
|
||||||
|
wallBounce: initWallBounceGun(),
|
||||||
radar: RadarLockModule(),
|
radar: RadarLockModule(),
|
||||||
mover: initPhantomMeteor(),
|
mover: initPhantomMeteor(),
|
||||||
currentGun: -1,
|
currentGun: -1,
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user