fix(PPO_Bot): radar lock — use enemy_tracker width-lock, fix arctan2 arg order
Three bugs caused the radar to sweep continuously instead of locking: 1. run() loop set radar to Inf every tick, overwriting any lock → replaced with enemy_tracker.getRadarTurnRate() 2. onScannedBot used radarBearingTo() (math convention, east=0 CCW) → removed; run loop now handles radar via enemy_tracker 3. enemy_tracker.getRadarTurnRate() had arctan2(dx,dy) instead of arctan2(dy,dx) — introduced by fd22535; bearing was off by ~90° Also relaxed stale-lock threshold from 2 to 8 ticks to survive brief scan gaps without falling back to full sweep. Added tools/battle_runner for automated 1v1 testing. Result: 1303/1308 ticks with successful scan (was ~1 in 4).
This commit is contained in:
+4
-2
@@ -64,6 +64,9 @@ method onScannedBot*(bot: PPOBot, e: ScannedBotEvent) =
|
||||
bot.tracker.update(e.x, e.y, e.direction, e.speed, e.energy)
|
||||
|
||||
method onRoundStarted*(bot: PPOBot, e: RoundStartedEvent) =
|
||||
setAdjustGunForBodyTurn(true)
|
||||
setAdjustRadarForBodyTurn(true)
|
||||
setAdjustRadarForGunTurn(true)
|
||||
bot.tracker = initEnemyTracker()
|
||||
bot.buffer = initTrajectoryBuffer()
|
||||
bot.prevEnergy = 0.0'f32
|
||||
@@ -130,8 +133,7 @@ method run(bot: PPOBot) =
|
||||
while isRunning():
|
||||
bot.tracker.deadReckon()
|
||||
|
||||
setRadarTurnRate(bot.tracker.getRadarTurnRate(
|
||||
getX(), getY(), getDirection(), getRadarDirection()))
|
||||
setRadarTurnRate(bot.tracker.getRadarTurnRate(getX(), getY(), getDirection(), getRadarDirection()))
|
||||
|
||||
let botData = BotStateData(
|
||||
x: getX(),
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Static-link OpenBLAS for portable deployment
|
||||
# ponytail: adjust path per machine, or use pkg-config
|
||||
switch("passL", "-lopenblas")
|
||||
switch("passL", "-L/nix/store/v07svn2y92bvzjl51aj7c9ca1cwg7rw7-openblas-0.3.32/lib -lopenblas")
|
||||
switch("threads", "on")
|
||||
# begin Nimble config (version 2)
|
||||
when withDir(thisDir(), system.fileExists("nimble.paths")):
|
||||
|
||||
+14
-16
@@ -19,11 +19,8 @@ type
|
||||
historyCount*: int # valid entries 0-5
|
||||
prevEnergy*: float64
|
||||
hasContact*: bool
|
||||
## For radar overshoot reversal
|
||||
lastOvershootDir*: float64 # +1 or -1
|
||||
|
||||
proc initEnemyTracker*(): EnemyTracker =
|
||||
result.lastOvershootDir = 1.0
|
||||
proc initEnemyTracker*(): EnemyTracker = discard
|
||||
|
||||
proc update*(tracker: var EnemyTracker;
|
||||
scanX, scanY, scanDir, scanSpeed, scanEnergy: float64) =
|
||||
@@ -77,20 +74,21 @@ proc getRadarTurnRate*(tracker: var EnemyTracker;
|
||||
if not tracker.hasContact:
|
||||
return 45.0
|
||||
|
||||
if tracker.current.ticksSinceLastScan >= 2:
|
||||
# Lost lock — widen sweep proportional to staleness
|
||||
if tracker.current.ticksSinceLastScan >= 8:
|
||||
# Lost lock — widen sweep
|
||||
return 45.0
|
||||
|
||||
# Bearing from radar to enemy
|
||||
# Tank Royale: 0°=north, CW+. arctan2(dx, dy) gives north-based CW bearing.
|
||||
# Bearing from radar to enemy.
|
||||
# Tank Royale radar directions use standard math convention (0=east, CCW+).
|
||||
# arctan2(dy, dx) gives the standard math angle matching radarDirection units.
|
||||
let dx = tracker.current.x - botX
|
||||
let dy = tracker.current.y - botY
|
||||
let absoluteDir = (180.0 * arctan2(dx, dy) / PI + 360.0) mod 360.0
|
||||
let radarBearing = normalizeRelative(absoluteDir - radarDirection)
|
||||
let absoluteDir = (180.0 * arctan2(dy, dx) / PI + 360.0) mod 360.0
|
||||
var radarTurn = normalizeRelative(absoluteDir - radarDirection)
|
||||
|
||||
# Overshoot by 10°, alternate direction each tick
|
||||
# ponytail: simple fixed overshoot; adaptive sweep if enemy is fast-turning
|
||||
let overshoot = 10.0
|
||||
let target = radarBearing + tracker.lastOvershootDir * overshoot
|
||||
result = target.clamp(-45.0, 45.0)
|
||||
tracker.lastOvershootDir *= -1.0
|
||||
# Width Lock: overshoot proportional to arctan(36 / distance)
|
||||
let distance = sqrt(dx * dx + dy * dy)
|
||||
let extraTurn = min(arctan(36.0 / distance) * 180.0 / PI, 45.0)
|
||||
if radarTurn < 0.0: radarTurn -= extraTurn
|
||||
else: radarTurn += extraTurn
|
||||
result = radarTurn.clamp(-45.0, 45.0)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import dev.robocode.tankroyale.runner.*;
|
||||
import dev.robocode.tankroyale.client.model.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Radar-lock diagnostic battle: PPO_Bot vs Target (1 round).
|
||||
*
|
||||
* Per tick prints: turn, each bot's radar direction, and any ScannedBotEvents.
|
||||
*
|
||||
* Usage:
|
||||
* ./run.sh
|
||||
*/
|
||||
public class RunBattle {
|
||||
|
||||
static final Map<Integer, String> idToName = new ConcurrentHashMap<>();
|
||||
|
||||
public static void main(String[] args) {
|
||||
Logger.getLogger("dev.robocode.tankroyale").setLevel(Level.WARNING);
|
||||
|
||||
String ppoBotDir = requireEnv("PPO_BOT_DIR");
|
||||
String sampleBots = requireEnv("SAMPLE_BOTS_DIR");
|
||||
|
||||
try (var runner = BattleRunner.create(b -> b.embeddedServer().suppressServerOutput())) {
|
||||
var setup = BattleSetup.classic(s -> s.setNumberOfRounds(1));
|
||||
var bots = List.of(
|
||||
BotEntry.of(ppoBotDir),
|
||||
BotEntry.of(sampleBots + "/Target")
|
||||
);
|
||||
|
||||
var owner = new Object();
|
||||
try (var handle = runner.startBattleAsync(setup, bots)) {
|
||||
|
||||
handle.getOnGameStarted().on(owner, event -> {
|
||||
System.out.println("=== GAME STARTED ===");
|
||||
for (var p : event.getParticipants()) {
|
||||
idToName.put(p.getId(), p.getName());
|
||||
System.out.printf(" #%d %s%n", p.getId(), p.getName());
|
||||
}
|
||||
System.out.printf("%-6s %-4s %-30s %-30s %-20s%n",
|
||||
"Turn", "Id", "Name", "RadarDir", "Event");
|
||||
System.out.println("-".repeat(95));
|
||||
});
|
||||
|
||||
handle.getOnTickEvent().on(owner, event -> {
|
||||
int turn = event.getTurnNumber();
|
||||
|
||||
// Keep id→name map current from bot states (handles race with onGameStarted)
|
||||
for (var state : event.getBotStates())
|
||||
if (state.getName() != null) idToName.putIfAbsent(state.getId(), state.getName());
|
||||
|
||||
// Print radar direction for every bot
|
||||
for (var state : event.getBotStates()) {
|
||||
String name = idToName.getOrDefault(state.getId(),
|
||||
state.getName() != null ? state.getName() : "#" + state.getId());
|
||||
System.out.printf("%-6d %-4d %-30s %-30.2f%n",
|
||||
turn, state.getId(), name, state.getRadarDirection());
|
||||
}
|
||||
|
||||
// Print any scan events on the same tick
|
||||
for (var e : event.getEvents()) {
|
||||
if (e instanceof ScannedBotEvent scan) {
|
||||
String scanner = idToName.getOrDefault(scan.getScannedByBotId(), "?" + scan.getScannedByBotId());
|
||||
String target = idToName.getOrDefault(scan.getScannedBotId(), "?" + scan.getScannedBotId());
|
||||
System.out.printf("%-6d SCAN %s scanned %s at (%.1f,%.1f)%n",
|
||||
turn, scanner, target, scan.getX(), scan.getY());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
handle.getOnRoundEnded().on(owner, event ->
|
||||
System.out.printf("%n=== ROUND %d ENDED (turn %d) ===%n",
|
||||
event.getRoundNumber(), event.getTurnNumber()));
|
||||
|
||||
var results = handle.awaitResults();
|
||||
System.out.printf("%n=== RESULTS (%d rounds) ===%n", results.getNumberOfRounds());
|
||||
for (var r : results.getResults()) {
|
||||
System.out.printf(" #%d %-25s %d pts%n",
|
||||
r.getRank(), r.getName(), r.getTotalScore());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String requireEnv(String name) {
|
||||
var v = System.getenv(name);
|
||||
if (v == null || v.isBlank()) {
|
||||
System.err.println("Error: " + name + " env var not set");
|
||||
System.exit(1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
JAR="${TANK_ROYALE_JAR:-/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar}"
|
||||
PPO_BOT_SRC="$REPO_ROOT/PPO_Bot"
|
||||
|
||||
export PPO_BOT_DIR="$PPO_BOT_SRC"
|
||||
export SAMPLE_BOTS_DIR="${SAMPLE_BOTS_DIR:-/home/davide/Projects/tank-royale/sample-bots/java/build/archive}"
|
||||
|
||||
# ── 1. Compile PPO_Bot if binary is missing ──────────────────────────────────
|
||||
if [ ! -x "$PPO_BOT_SRC/PPO_Bot" ]; then
|
||||
echo ">>> Compiling PPO_Bot..."
|
||||
(cd "$PPO_BOT_SRC" && nimble build -d:release)
|
||||
fi
|
||||
|
||||
# ── 2. Compile and run RunBattle.java ────────────────────────────────────────
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo ">>> Compiling RunBattle.java..."
|
||||
javac -cp "$JAR" RunBattle.java
|
||||
|
||||
echo ">>> Running battle..."
|
||||
java -cp ".:$JAR" RunBattle
|
||||
Reference in New Issue
Block a user