diff --git a/BNNBot_garage/BNNBot b/BNNBot_garage/BNNBot new file mode 100755 index 0000000..c46d981 Binary files /dev/null and b/BNNBot_garage/BNNBot differ diff --git a/BNNBot_garage/BNNBot.nimble b/BNNBot_garage/BNNBot.nimble new file mode 100644 index 0000000..1f879f0 --- /dev/null +++ b/BNNBot_garage/BNNBot.nimble @@ -0,0 +1,12 @@ +# Package +version = "0.1.0" +author = "Davide Cappellini" +description = "Binary Neural Network Bot — learns aiming with pure binary operations" +license = "MIT" +srcDir = "src" +bin = @["BNNBot"] + +# Dependencies +requires "nim >= 2.0.0" +requires "robocode_tankroyale_botapi >= 1.0.7" +# radar_lock is vendored in-tree (common_libs/) and wired via config.nims --path diff --git a/BNNBot_garage/config.nims b/BNNBot_garage/config.nims new file mode 100644 index 0000000..c95874e --- /dev/null +++ b/BNNBot_garage/config.nims @@ -0,0 +1,7 @@ +--path:"../common_libs" +switch("outdir", "out") +switch("path", thisDir() & "/src") +# begin Nimble config (version 2) +when withDir(thisDir(), system.fileExists("nimble.paths")): + include "nimble.paths" +# end Nimble config diff --git a/BNNBot_garage/src/BNNBot.json b/BNNBot_garage/src/BNNBot.json new file mode 100644 index 0000000..63005eb --- /dev/null +++ b/BNNBot_garage/src/BNNBot.json @@ -0,0 +1,11 @@ +{ + "name": "BNNBot", + "version": "0.1.0", + "authors": ["Davide Cappellini"], + "description": "Binary Neural Network Bot — learns aiming with pure binary operations", + "homepage": "", + "countryCodes": ["IT"], + "gameTypes": ["classic", "1v1"], + "platform": "Nim", + "programmingLang": "Nim" +} diff --git a/BNNBot_garage/src/BNNBot.nim b/BNNBot_garage/src/BNNBot.nim new file mode 100644 index 0000000..9555b79 --- /dev/null +++ b/BNNBot_garage/src/BNNBot.nim @@ -0,0 +1,96 @@ +# BNNBot — binary encoding data collector. +# Radar lock on enemy, converts each scan to a 112-bit BinaryVector, logs + prints it. +# No aiming, no firing — pure scan visualization for BNN research. + +import std/[math, strformat, os] +import robocode_tankroyale_botapi +import radar_lock/radar_lock as radar_lock +import binary_encoding + +const botJsonPath = currentSourcePath().parentDir / "BNNBot.json" +const scanLog = "/tmp/bnnbot_scan.log" + +proc appendLog(s: string) = + try: + let f = open(scanLog, fmAppend) + f.writeLine(s) + f.close() + except: + discard + +type + BNNBot = ref object of Bot + hasContact: bool + enemyBearing: float + lastEnemyX: float + lastEnemyY: float + hasLastPos: bool + velocity: float # signed velocity (from heading + speed) + heading: float # enemy heading (degrees) + speed: float # enemy speed (units/tick) + distance: float + tick: int + +method onScannedBot*(bot: BNNBot, e: ScannedBotEvent) = + let bx = getX(); let by = getY() + bot.enemyBearing = directionTo(bx, by, e.x, e.y) + bot.distance = distanceTo(bx, by, e.x, e.y) + bot.heading = e.direction + bot.speed = abs(e.speed) + bot.velocity = e.speed # signed: positive = forward, negative = reverse + if bot.hasLastPos: + discard # velocity from e.speed is more accurate; position delta kept for future use + bot.lastEnemyX = e.x + bot.lastEnemyY = e.y + bot.hasLastPos = true + bot.hasContact = true + + let scan = ScanData( + bearing: bot.enemyBearing, + distance: bot.distance, + velocity: bot.velocity, + heading: bot.heading, + speed: bot.speed, + ) + let vec = encode(scan) + let fmt = formatBinary(vec) + let line = fmt"t={bot.tick} bear={bot.enemyBearing:.1f} dist={bot.distance:.0f} vel={bot.velocity:.2f} head={bot.heading:.1f} spd={bot.speed:.2f} pop={popcount(vec)} {fmt}" + echo line + appendLog(line) + +method onRoundStarted*(bot: BNNBot, e: RoundStartedEvent) = + setAdjustGunForBodyTurn(true) + setAdjustRadarForBodyTurn(true) + setAdjustRadarForGunTurn(true) + radar_lock.init() + bot.hasContact = false + bot.hasLastPos = false + bot.tick = 0 + setTargetSpeed(0.0) + setTurnRate(0.0) + +method onGameStarted*(bot: BNNBot, e: GameStartedEventForBot) = + # Truncate log on game start + try: + let f = open(scanLog, fmWrite) + f.close() + except: + discard + +method run*(bot: BNNBot) = + while isRunning(): + inc bot.tick + setTargetSpeed(0.0) + setTurnRate(0.0) + + if not bot.hasContact: + setRadarTurnRate(45.0) + go() + continue + + setRadarTurnRate(radar_lock.doRadar(getRadarDirection(), bot.enemyBearing)) + go() + +when isMainModule: + var bot = BNNBot() + start(bot, botJsonPath) diff --git a/BNNBot_garage/src/binary_encoding.nim b/BNNBot_garage/src/binary_encoding.nim new file mode 100644 index 0000000..458e03e --- /dev/null +++ b/BNNBot_garage/src/binary_encoding.nim @@ -0,0 +1,127 @@ +## binary_encoding — scan data → binary vector for BNNBot. +## Encoding scheme: +## bearing/heading: population code (2 adjacent bits, triangular interpolation) +## distance/speed: thermometer (all bits below threshold active) +## velocity: signed thermometer (first half = negative, second half = positive) + +import std/math + +const + BEARING_BITS* = 36 # 360° / 10° per bit + DISTANCE_BITS* = 16 # 0-2000 units, ~125 per bit (thermometer) + VELOCITY_BITS* = 16 # -8 to +8, signed thermometer + HEADING_BITS* = 36 # 360° / 10° per bit + SPEED_BITS* = 8 # 0-8, 1 per bit (thermometer) + TOTAL_BITS* = BEARING_BITS + DISTANCE_BITS + VELOCITY_BITS + HEADING_BITS + SPEED_BITS # 112 + +type + BinaryVector* = array[TOTAL_BITS, uint8] # 0 or 1 + + ScanData* = object + bearing*: float # absolute bearing to enemy (degrees) + distance*: float # distance to enemy + velocity*: float # enemy velocity (negative = moving backward) + heading*: float # enemy heading (degrees) + speed*: float # enemy speed (always positive) + +proc encodeBearing*(degrees: float, bits: int): seq[uint8] = + ## Population code: 2 adjacent bits active via triangular interpolation. + result = newSeq[uint8](bits) + let norm = ((degrees mod 360.0) + 360.0) mod 360.0 + let bandWidth = 360.0 / float(bits) + let idx = norm / bandWidth + let lo = int(idx) mod bits + let hi = (lo + 1) mod bits + result[lo] = 1 + result[hi] = 1 + +proc encodeThermometer*(value, minVal, maxVal: float, bits: int): seq[uint8] = + ## Thermometer code: all bits below threshold are 1. + result = newSeq[uint8](bits) + let clamped = clamp(value, minVal, maxVal) + let norm = (clamped - minVal) / (maxVal - minVal) + let active = int(norm * float(bits)) + for i in 0..= 0: + let norm = clamped / maxAbs + let active = int(norm * float(halfBits)) + for i in 0..