diff --git a/DevControlBot_garage/AGENTS.md b/DevControlBot_garage/AGENTS.md new file mode 100644 index 0000000..9e1c326 --- /dev/null +++ b/DevControlBot_garage/AGENTS.md @@ -0,0 +1,228 @@ +# DevControlBot — Garage Agent Guide + +> ## ⚠️ READ THIS FIRST — THIS GARAGE IS *INTENTIONALLY* NOT THE ROOT TEMPLATE +> +> **The layout and format described in the repo-root [`../../AGENTS.md`](../../AGENTS.md) +> template do NOT apply to `DevControlBot_garage/`.** The root template documents a +> `src/.nim` / `tests/` / `out/` convention for garages. **This garage deviates +> from it on purpose and must stay that way.** +> +> **Do NOT, even to "fix" a perceived inconsistency:** +> - create `src/`, `tests/`, or `out/` +> - add a `config.nims`, `nimble.paths`, `--path` flags, or a vendor/ directory +> - restructure `DevControlBot/` to match the root template +> +> Builds go to a `mktemp -d` throwaway dir that is removed on exit — nothing is +> ever written here. The root `AGENTS.md` and its folder conventions are for the +> **other** garages; here it is context only (issue tracker, Nim conventions), and +> is **NOT a layout contract for this folder**. If the root template and this file +> disagree about layout, **this file wins.** + +## Start here + +This file is the working rules for the bot. The human-facing description lives in +[README.md](README.md) — read it first. + +Garage-specific rules for `DevControlBot_garage/`. Repo-wide rules live in +[`../../AGENTS.md`](../../AGENTS.md) (issue tracker, Nim conventions, test +framework) — read it for that context, but note again: **its folder-layout +template does not govern this garage.** + +## Conventions + +- This bot's code lives **only** inside `DevControlBot_garage/`. Do not edit other + garages to make this bot work. +- Do not reuse or copy another bot's game logic (movement/scanning/firing, radar + tables, targeting heuristics) without explicit permission from the user. Shared, + bot-agnostic code belongs in `common_libs/`, and only with permission. +- Body, gun and radar are `WHITE` (team convention: that part is not programmed + yet). Colors are set once at initialization, never in the tick loop. Keep it + that way: the tick loop must do nothing but `go()` until real logic is written. + +## Build artifacts / binaries + +- **Rule:** never write a binary, log or build output into this garage. The + `runBot` task builds into a `mktemp -d` throwaway dir and removes it with an + `EXIT`/`INT`/`TERM` trap, so **no `out/` directory is ever created** here. +- **Rule:** never commit binaries or nimble cache dirs (`~/.nimble`, + `nimbledeps/`). `nimbledeps/` only appears with `nimble develop`; ignore it. +- **Rule:** dependencies come from the global nimble store. Do **not** vendor a + copy or add `nimble.paths`/`nimble develop` — `nimble install + robocode_tankroyale_botapi` is the only setup step. (The package was renamed + from `tankroyale_botapi`; the old name is gone.) + Exception: the `runBot` task *derives* `--path:` flags at run time from + `nimble path `, because the compiler's bundled + `nimblepath="$home/.nimble/pkgs2/"` only resolves when `$HOME` is set. That is + resolution, not vendoring; keep it. +- Build/run with the folder task, never raw `nim c`: + +```sh +cd DevControlBot_garage/DevControlBot +nimble runBot +``` + +- Note: the root `.gitignore` claims "all builds go to `*_garage/out/`". That is + no longer true for this garage — do not rely on that rule, and do not add an + `out/` directory just to satisfy it. (Root `.gitignore` is not this garage's + file; leave it alone unless the user asks.) + +## Running the bot + +`DevControlBot/DevControlBot.sh` is the canonical entry point and works from any +cwd — it resolves `SCRIPT_DIR` from `BASH_SOURCE`, `cd`s to the `.nimble` dir, +forwards args and signals to `nimble runBot`, and propagates the exit code. + +```sh +./DevControlBot/DevControlBot.sh # from the garage root: bundled metadata +./DevControlBot/DevControlBot.sh /path.json # alternate metadata (first arg, must end in .json) +./DevControlBot/DevControlBot.sh --debug # classic debug build +./DevControlBot/DevControlBot.sh --help # usage, exits 0 without building +``` + +- **Rule: no arguments means RUN, not usage.** A bare `./DevControlBot.sh` + performs a quiet RELEASE build and runs the bot with the bundled + `DevControlBot.json`, exactly as if that path had been passed. Usage is + printed **only** for an explicit `-h` / `--help`, which exits 0 without + building. (An earlier version printed usage and exited 0 with no arguments — + that was the bug, and it is fixed.) +- There is **no `--json` flag**: the metadata path is the first positional + argument and is used when it ends in `.json`. Anything else is forwarded to + the bot verbatim. + +- **Build mode:** default is **release** (`nim c -d:release`, quiet: no compiler + hints, no dot-progress line, just `[devcontrolbot] build ok (release)`). + `--debug` selects the classic debug build (`nim c`, full hints/diagnostics). + The flag is stripped by the wrapper in any position and reaches the `runBot` + task through the exported env var `$DEVCONTROLBOT_DEBUG` (`debug`/`release`), + never as a bot argument. Compiler errors are shown in **both** modes; a failed + release build replays the captured compiler log, so it is never silent. +- With no arguments (or `--help`) the wrapper prints usage and exits 0 without + building. + +- **Gotcha:** `nimble runBot` must be invoked from `DevControlBot/`, the + directory containing the `.nimble`. From `DevControlBot_garage/` it fails with + `Could not find a file with a .nimble extension inside the specified directory`. +- `compile`/`build` are reserved Nimble builtin names — hence `runBot`. +- Without a Tank Royale server on `ws://localhost:7654` the run fails with + `[start] Cannot connect ... Connection refused` and exit code 1. That is + expected during local verification, not a bug. + +## Configuration = environment variables (the API's own mechanism) + +- **Rule: never add env-reading code to `DevControlBot.nim`.** The bot API + already reads its configuration from the process environment inside + `start()`: `SERVER_URL` and `SERVER_SECRET` + (`robocode_tankroyale_botapi.nim:424-425`, documented at `:396-397`) and + `BOT_NAME` / `BOT_VERSION` / `BOT_AUTHORS` / … in `bot_info.nim:117-130` + (used when no JSON metadata is found). That is the supported mechanism the + official BotLauncher uses; `DevControlBot.nim` calls plain `start(bot, + jsonPath)` and needs no change. +- The `runBot` task makes those variables **present** for local runs (see the + `.env` section). That is all it does; it does not interpret them. + +## Optional `.env` (local runs only) + +`DevControlBot/.env` holds the local configuration (typically `SERVER_URL` and +`SERVER_SECRET`). Rules: + +- **It is optional.** If it is missing the run is unchanged: no error, no + non-zero exit, just one informational line and the API defaults. +- **Loaded in the `runBot` task's shell**, not in `DevControlBot.sh`, so it works + for both `./DevControlBot.sh` and a direct `nimble runBot` and the wrapper + stays a thin wrapper. `$DEVCONTROLBOT_ENV_FILE` overrides the path. +- **Precedence — CRITICAL: the environment always wins.** A variable already + present in the environment is **never** overridden by `.env`, so the official + BotLauncher's values keep winning. It is achieved by *filtering*: a + `NAME=value` line is dropped when `NAME` is already set (`[ -n "${NAME+x}" ]`, + set-but-maybe-empty), and only the still-unset names are `set -a`-exported and + sourced. Do **not** "simplify" this into a plain `set -a; . .env` — sourcing + does the opposite and would let the file beat the launcher. +- Accepts `FOO=bar` and `export FOO=bar`, strips `CR` (CRLF), skips blanks, + `#` comments and any line that is not a plain `NAME=value` identifier. +- **Never print values** and never use `set -x`: report variable *names* only. +- **Never commit it** — it holds secrets. The root `.gitignore` covers + `ModularBot_garage/.env` only and does **not** ignore this one; the rule to + add (ask the user before touching the root file) is + `DevControlBot_garage/DevControlBot/.env`. + +## Failure reporting / exit codes + +- **Rule:** an expected failure must never surface as a Nimble/NimScript + exception. `exec` raises on any non-zero exit, printing a stack trace and an + escaped copy of `runScript`. So the shell always exits 0 and writes the real + code to `$DEVCONTROLBOT_STATUS_FILE`; `DevControlBot.sh` exports that path and + re-exits with the code. Never mask it to 0, never let the shell's code be + swallowed. +- Exit codes (keep them distinct, they are what BotLauncher branches on): + `0` clean, `1` no game server on 7654 (expected), `2` dependency not + installed, `3` compile failure, anything else = the bot's own code. + Each prints its own short, actionable line; compile failures keep the raw + compiler errors. +- Running `nimble runBot` directly (no status file) intentionally falls back to + the legacy behaviour: the shell exits with the real code and Nimble raises. + +## Test organization + +- **There are currently no tests in this garage.** No `tests/` directory, no + `tests/config.nims`, and the `.nimble` has exactly one task (`runBot`) — the old + `test`, `compileBot` and `setupVendor` tasks are gone. `nimble test` does not + exist; do not invoke it and do not document it. +- Convention **when** tests are added (repo-wide, see + [`../../AGENTS.md`](../../AGENTS.md) and + `common_libs/test_framework/README.md`): + - create `DevControlBot_garage/tests/` with `tests/config.nims` containing + `--path:"../../common_libs"` (depth must match the garage location), one + `test.nim` per concern (e.g. `test_basic_battle.nim`); + - add a `test` task to `DevControlBot.nimble` running + `nim c -r --path:../common_libs tests/test_basic_battle.nim`; + - guard first, import second — tests must pass with no Java present: + + ```nim + import std/os + if not existsEnv("TR_SERVER_JAR") or not existsEnv("TR_BATTLE_RUNNER"): + echo "Skipping: TR_SERVER_JAR / TR_BATTLE_RUNNER not set" + quit(0) + + import test_framework/test_framework + ``` + + - shared adversaries: `common_libs/test_framework/adversaries/SittingDuck` + (passive) and `OscillatorBot` (fights back); standard call is + `runBattle(@[myBotDir, adversaryDir], rounds = 10)`. + - env vars: `TR_SERVER_JAR`, `TR_BATTLE_RUNNER`. Failures surface as `OSError` + (bot compile failed), `IOError` (runner non-zero), `TimeoutError` + (server 15s / compile 30s per bot / battle runner). + +## Onboarding — full sequence + +```sh +# 1. dependency (once; already global if nimble install says "already installed") +nimble install robocode_tankroyale_botapi + +# 2. run the bot (compiles to a temp dir, runs, cleans up; nothing left behind) +./DevControlBot/DevControlBot.sh + +# 2b. same thing via the task — must be run from DevControlBot/ +cd DevControlBot && nimble runBot + +# 3. tests — none exist yet; see "Test organization" above +``` + +Checklist after touching build config: +- `cd DevControlBot && nimble runBot` compiles (fails only on "Cannot connect" + without a server) and leaves the garage tree unchanged (`ls DevControlBot_garage` + still shows only `AGENTS.md`, `README.md`, `DevControlBot/`). + +## Layout of this folder + +``` +DevControlBot_garage/ +├── AGENTS.md # this file +├── README.md # human-facing description +└── DevControlBot/ + ├── DevControlBot.nim # bot type + entry point (isMainModule) + ├── DevControlBot.json # bot metadata + ├── DevControlBot.nimble # single task: runBot + ├── DevControlBot.sh # BotLauncher entry point (thin wrapper) + └── .env # OPTIONAL local config; secrets, never commit +``` \ No newline at end of file diff --git a/DevControlBot_garage/DevControlBot/.gitignore b/DevControlBot_garage/DevControlBot/.gitignore new file mode 100644 index 0000000..6e47899 --- /dev/null +++ b/DevControlBot_garage/DevControlBot/.gitignore @@ -0,0 +1,2 @@ +# User-authored environment variables (e.g. SERVER_URL) — never committed +.env \ No newline at end of file diff --git a/DevControlBot_garage/DevControlBot/DevControlBot.json b/DevControlBot_garage/DevControlBot/DevControlBot.json new file mode 100644 index 0000000..4428e73 --- /dev/null +++ b/DevControlBot_garage/DevControlBot/DevControlBot.json @@ -0,0 +1,9 @@ +{ + "name": "DevControlBot", + "version": "0.1.0", + "authors": ["Davide Cappellini"], + "description": "Control skeleton bot — does nothing, bright colors", + "gameTypes": ["classic", "1v1"], + "platform": "Nim", + "programmingLang": "Nim" +} \ No newline at end of file diff --git a/DevControlBot_garage/DevControlBot/DevControlBot.nim b/DevControlBot_garage/DevControlBot/DevControlBot.nim new file mode 100644 index 0000000..9116a3c --- /dev/null +++ b/DevControlBot_garage/DevControlBot/DevControlBot.nim @@ -0,0 +1,32 @@ +## DevControlBot — control skeleton: boots, stands still, does nothing. +## +## Team convention: white means "not programmed yet", so body, gun and radar are +## all plain white. Colors are applied once at initialization, never per tick. +## +## This module is both the bot type and the program entry point (see isMainModule +## below); there is no separate top-level DevControlBot.nim. +## +## API: robocode_tankroyale_botapi 1.0.7 (the renamed tankroyale_botapi package). +import std/os +import robocode_tankroyale_botapi + +type DevControlBot* = ref object of Bot + +proc newDevControlBot*(): DevControlBot = + result = DevControlBot() + setBodyColor(WHITE) + setTurretColor(WHITE) + setGunColor(WHITE) + setRadarColor(WHITE) + +method run*(bot: DevControlBot) = + while isRunning(): + go() + +when isMainModule: + # argv[1] may override the metadata file (used by DevControlBot/DevControlBot.sh); + # otherwise the JSON next to this source, resolved at compile time so any cwd works. + let jsonPath = if paramCount() >= 1: paramStr(1) + else: currentSourcePath().parentDir / "DevControlBot.json" + var bot = newDevControlBot() + start(bot, jsonPath) diff --git a/DevControlBot_garage/DevControlBot/DevControlBot.nimble b/DevControlBot_garage/DevControlBot/DevControlBot.nimble new file mode 100644 index 0000000..8439ec1 --- /dev/null +++ b/DevControlBot_garage/DevControlBot/DevControlBot.nimble @@ -0,0 +1,238 @@ +# Package +version = "0.1.0" +author = "Davide Cappellini" +description = "DevControlBot — control-skeleton bot that does nothing but show up bright" +license = "MIT" +srcDir = "." +bin = @[] + +# Dependencies +# Installed globally with `nimble install`; nothing is vendored locally and +# no nimble.paths / vendor/ is used. `runBot` resolves each package's real +# location at run time with `nimble path` and passes explicit --path: flags, +# so the build does not rely on $HOME or on the compiler's bundled nimblepath. +requires "nim >= 2.0.0" +requires "robocode_tankroyale_botapi >= 1.0.7" +requires "jsony >= 1.1.5" + +# The build-and-run logic lives here, not in DevControlBot.sh. +# +# NimScript cannot install signal handlers, so the temp build dir is owned by a +# small POSIX shell program (below) that Nimble executes via `exec` (which goes +# through the shell). That program traps EXIT/INT/TERM and always removes the +# throwaway build dir, including when compilation fails or the run is +# interrupted. Nothing is ever written to a persistent out/ directory. +# +# `exec` turns ANY non-zero exit into a NimScript exception, which prints a +# stack trace plus an escaped copy of the whole script. That is nonsense for the +# expected "no game server on 7654" outcome. So the shell always exits 0 and +# hands the real code back through the file named by $DEVCONTROLBOT_STATUS_FILE, +# which DevControlBot.sh exports and then re-exits with. Direct `nimble runBot` +# (no such variable) keeps the old behaviour: the shell exits with the real code +# and Nimble raises. +# +# DevControlBot.sh is a thin wrapper: it locates itself, chdirs here so Nimble +# finds this file, forwards caller args, calls `runBot` and forwards signals. +import std/os + +const + runScript = """ +# Resolve the globally-installed dependencies at run time. Plain `nim c` only +# finds them when $HOME is set (the Nim distribution's nim.cfg carries +# `nimblepath="$home/.nimble/pkgs2/"`), which is NOT true in bare environments +# (env -i, nix build sandboxes, CI). `nimble path` is the reliable, +# cwd-independent way to ask for their real location. +# Distinct exit codes, so a caller can tell the failure modes apart. +EXIT_NO_SERVER=1 # bot could not connect to the game server (expected) +EXIT_NO_DEPS=2 # a dependency is not installed +EXIT_COMPILE=3 # compilation failed + +STATUS_FILE="${DEVCONTROLBOT_STATUS_FILE:-}" + +# report : write the real exit code to the status file. Returns 1 when +# there is no status file, i.e. the caller wants it as the shell's own code. +report() { + [ -n "$STATUS_FILE" ] || return 1 + printf '%s\n' "$1" > "$STATUS_FILE" +} + +# die : finish with as the run's real result — quietly (exit 0, +# the status file carries the code) or, with no listener, by exiting with it. +die() { + if report "$1"; then exit 0; else exit "$1"; fi +} + +DEPS="" +for pkg in robocode_tankroyale_botapi jsony; do + pkgdir="$(nimble path "$pkg" 2>/dev/null | head -n 1)" + if [ -z "$pkgdir" ] || [ ! -d "$pkgdir" ]; then + echo "[devcontrolbot] COMPILE BLOCKED: dependency '$pkg' is not installed." >&2 + echo "[devcontrolbot] fix with: nimble install $pkg" >&2 + die $EXIT_NO_DEPS + fi + echo "[devcontrolbot] dependency $pkg -> $pkgdir" + DEPS="$DEPS '--path:$pkgdir'" +done + +# Build mode: $DEVCONTROLBOT_DEBUG is set to "debug" by DevControlBot.sh when +# the caller passed --debug; anything else (including an unset variable, i.e. a +# direct `nimble runBot`) means release. Release is the quiet default. +BUILD_MODE="${DEVCONTROLBOT_DEBUG:-release}" + +BUILD_DIR="$(mktemp -d "${TMPDIR:-/tmp}/devcontrolbot.XXXXXX")" +BINARY="$BUILD_DIR/DevControlBot" + +# ---- optional .env ------------------------------------------------------------ +# Configuration reaches the bot through the ENVIRONMENT, not through any code +# of ours: robocode_tankroyale_botapi's start() reads SERVER_URL and +# SERVER_SECRET (robocode_tankroyale_botapi.nim:424-425, documented at :396-397) +# and loadBotInfo() reads BOT_NAME / BOT_VERSION / ... from the environment when +# the JSON is absent (bot_info.nim:117-130). That is the documented, supported +# mechanism the official BotLauncher uses, so the only thing we do here is make +# those variables PRESENT in the bot's process environment for local runs. +# +# Loaded HERE, in the runBot task, and not in DevControlBot.sh, so it applies both +# to `./DevControlBot.sh` and to a direct `nimble runBot` (one implementation, and +# the wrapper stays a thin wrapper). +# - optional: a missing file is not an error, nothing is printed but a note, +# the bot just uses the API defaults. +# - CRLF-tolerant: CRs are stripped so a Windows-edited file does not end up +# with the value "bar\r". +# - both `FOO=bar` and `export FOO=bar` styles. +# - never echoed, never `set -x`: only the file NAME is reported. +# - $DEVCONTROLBOT_ENV_FILE overrides the default path (".env" next to the +# .nimble, which is the cwd because DevControlBot.sh chdirs there). +# +# PRECEDENCE — CRITICAL. The .env file must NEVER override a variable that is +# already present in the environment: the official BotLauncher's values (and the +# caller's explicit `FOO=bar ./DevControlBot.sh`) must always win, otherwise this +# convenience file could silently break a BotLauncher run. Sourcing the file +# directly would do the opposite, because an assignment in a sourced file +# overwrites an already-exported variable of the same name. So the file is +# FILTERED line by line: a `NAME=value` line is dropped when NAME is already set +# in the environment (tested with `[ -n "${NAME+x}" ]`, i.e. set-but-maybe-empty, +# not non-empty), and only the remaining, still-unset names are sourced with +# `set -a`. The filtering is what implements the precedence, not the source order. +ENV_FILE="${DEVCONTROLBOT_ENV_FILE:-.env}" +if [ -f "$ENV_FILE" ]; then + ENV_RAW="$BUILD_DIR/env.raw" + ENV_KEEP="$BUILD_DIR/env.keep" + ENV_ADDED="" + # CRLF tolerance: drop the CRs, keep everything else verbatim. + tr -d '\r' < "$ENV_FILE" > "$ENV_RAW" + : > "$ENV_KEEP" + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|\#*) continue ;; # blank / comment + export\ *|export=*) line="${line#export }" ;; + esac + name="${line%%=*}" + # Only plain NAME=value assignments; anything else is ignored, so the file + # can never smuggle in commands. `name` is validated as an identifier + # BEFORE being used in the eval below. + printf '%s' "$name" | grep -q '^[A-Za-z_][A-Za-z_0-9]*$' || continue + # PRECEDENCE: skip if the variable already exists in the environment. + if eval "[ -n \"\${$name+x}\" ]"; then + continue + fi + printf '%s\n' "$line" >> "$ENV_KEEP" + ENV_ADDED="$ENV_ADDED $name" + done < "$ENV_RAW" + if [ -n "$ENV_ADDED" ]; then + set -a + . "$ENV_KEEP" + set +a + fi + rm -f "$ENV_RAW" "$ENV_KEEP" + # Variable NAMES only, never values. + echo "[devcontrolbot] env file loaded: $ENV_FILE (names added:${ENV_ADDED:- none}; values never printed)" + echo "[devcontrolbot] variables already in the environment win and were left untouched" +else + echo "[devcontrolbot] no env file at $ENV_FILE (optional, using API defaults)" +fi + +cleanup() { + rc=$? + trap - EXIT INT TERM + rm -rf "$BUILD_DIR" + if [ -d "$BUILD_DIR" ]; then + echo "[devcontrolbot] cleanup failed: $BUILD_DIR still present" >&2 + exit 1 + fi + exit $rc +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +# Release: hints and the progress/dot lines are noise, so the compiler output is +# captured to a log and only shown if the build fails (errors are ALWAYS shown). +# Debug: classic build, full compiler output (config-file hints, diagnostics). +LOG="$BUILD_DIR/build.log" +if [ "$BUILD_MODE" = "debug" ]; then + echo "[devcontrolbot] compiling DevControlBot.nim (DEBUG BUILD) -> $BINARY" + # `eval` re-parses $DEPS so the embedded quotes do the word splitting: the + # dependency paths survive spaces, without relying on glob or brace expansion. + # No pipe: POSIX sh has no PIPESTATUS, and `nim | tee` would report tee's + # status, not the compiler's. Buffer first, then replay the log verbatim. + eval nim c $DEPS --out:"$BINARY" DevControlBot.nim >"$LOG" 2>&1 + compile_rc=$? + cat "$LOG" +else + echo "[devcontrolbot] compiling DevControlBot.nim -> $BINARY" + eval nim c -d:release --hints:off $DEPS --out:"$BINARY" DevControlBot.nim >"$LOG" 2>&1 + compile_rc=$? +fi +if [ $compile_rc -ne 0 ]; then + if [ "$BUILD_MODE" != "debug" ]; then + echo "[devcontrolbot] --- compiler output ---" >&2 + cat "$LOG" >&2 + echo "[devcontrolbot] --- end compiler output ---" >&2 + fi + echo "[devcontrolbot] COMPILE FAILED (nim c exited $compile_rc) — errors above." >&2 + die $EXIT_COMPILE +fi +rm -f "$LOG" +if [ "$BUILD_MODE" = "debug" ]; then + echo "[devcontrolbot] build ok (debug)" +else + echo "[devcontrolbot] build ok (release)" +fi + +# The bot reads argv[1] as its metadata JSON path. If the caller already +# supplied a JSON, use that instead of the bundled one. +if [ -n "${1:-}" ] && [ "${1##*.}" = "json" ]; then + echo "[devcontrolbot] using caller-supplied metadata: $1" + "$BINARY" "$@" +else + # NOTE: no `exec` here: the shell must survive so the EXIT trap can clean up. + echo "[devcontrolbot] running $BINARY DevControlBot.json $*" + "$BINARY" DevControlBot.json "$@" +fi +bot_rc=$? +if [ $bot_rc -ne 0 ]; then + echo "[devcontrolbot] bot exited with code $bot_rc." >&2 + if [ $bot_rc -eq $EXIT_NO_SERVER ]; then + # The Tank Royale client answers a refused connection with this exact + # exit code, so it means "no server", not "broken bot". + echo "[devcontrolbot] No game server on ${SERVER_URL:-ws://localhost:7654} (SERVER_URL)." >&2 + echo "[devcontrolbot] Start the Tank Royale server, or check it is listening on port 7654." >&2 + else + echo "[devcontrolbot] The bot ran but failed at runtime (crash or bad metadata)." >&2 + fi +fi +die $bot_rc +""" + +task runBot, "Compile, run and clean up the bot": + # Nimble passes every CLI argument here (its own flags, then the task name, + # then the caller's arguments). Ours are the ones after the task name. + var args = "" + var seen = false + for a in commandLineParams(): + if seen: + if args != "": args = args & " " + args = args & quoteShell(a) + elif a == "runBot": + seen = true + exec "sh -c " & quoteShell(runScript) & " devcontrolbot " & args diff --git a/DevControlBot_garage/DevControlBot/DevControlBot.sh b/DevControlBot_garage/DevControlBot/DevControlBot.sh new file mode 100755 index 0000000..29227a3 --- /dev/null +++ b/DevControlBot_garage/DevControlBot/DevControlBot.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# BotLauncher entry point for DevControlBot. +# +# Thin wrapper: all build/run/cleanup logic lives in the `runBot` nimble task +# of DevControlBot.nimble. This script only locates itself, moves next to the +# .nimble so Nimble finds it, forwards caller arguments, calls the task and +# propagates the real exit code. Signals are forwarded to Nimble so the task's +# own EXIT/INT/TERM cleanup always runs. +# +# Usage: DevControlBot.sh [--debug] [] [extra args for the bot] +# +# (no args) RELEASE build + run with the bundled DevControlBot.json +# (no flag) RELEASE build (nim c -d:release), quiet output +# --debug classic DEBUG build (plain nim c): compiler hints + diagnostics +# +# --debug is a build-mode flag for this wrapper only: it is stripped here and +# never reaches the bot binary or nimble's task arguments. It travels to the +# runBot task as $DEVCONTROLBOT_DEBUG (exported), because an env var cannot be +# confused with a bot argument, cannot collide with the status file mechanism +# and keeps the task's argument parsing untouched. Repeated --debug is harmless. +# +# Configuration (SERVER_URL, SERVER_SECRET, ...) is NOT handled here at all: the +# API's start() reads those from the environment, and the optional .env file is +# loaded by the runBot task, so it also works with a direct `nimble runBot`. +set -uo pipefail +set -m # job control: the background job gets its own process group + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +usage() { + cat <] [extra args for the bot] + + --debug classic debug build: shows compiler hints and diagnostics + (default is a quiet RELEASE build, nim c -d:release) + -h,--help show this help + +Examples: + $(basename "$0") # release build + run, bundled DevControlBot.json + $(basename "$0") my.json # release build + run with custom metadata + $(basename "$0") -h # this help + $(basename "$0") --debug # debug build + $(basename "$0") --debug my.json # debug build with custom metadata +EOF +} + +# Split caller args: everything except --debug is forwarded verbatim. +BUILD_MODE=release +FORWARDED=() +for a in "$@"; do + case "$a" in + --debug) BUILD_MODE=debug ;; + *) FORWARDED+=("$a") ;; + esac +done +set -- ${FORWARDED+"${FORWARDED[@]}"} + +# No arguments is the normal case: build and run with the bundled metadata. +# Usage is printed ONLY for an explicit -h/--help. +case "${1:-}" in + -h|--help) usage; exit 0 ;; +esac + +export DEVCONTROLBOT_DEBUG="$BUILD_MODE" + +# The runBot task's shell always exits 0 (otherwise Nimble raises a NimScript +# exception with a stack trace and a dump of the whole script) and writes the +# bot's real exit code here instead. We read it back and exit with it, so +# BotLauncher still sees the faithful status: 1 = no game server on 7654, +# 2 = a dependency is missing, 3 = compile failure, anything else = the bot's +# own code, 0 = clean run. +STATUS_FILE="$(mktemp "${TMPDIR:-/tmp}/devcontrolbot.status.XXXXXX")" +export DEVCONTROLBOT_STATUS_FILE="$STATUS_FILE" +trap 'rm -f "$STATUS_FILE"' EXIT + +# Forward to the whole job process group (nimble + the shell it spawned) so the +# task's own EXIT/INT/TERM cleanup runs. No cleanup logic here. +forward() { kill -s "$1" -- "-$NIMBLE_PID" 2>/dev/null; } +trap 'forward TERM' TERM +trap 'forward INT' INT + +nimble runBot ${1+"$@"} & +NIMBLE_PID=$! +wait "$NIMBLE_PID" +rc=$? +trap - TERM INT +# Prefer the code the run actually produced; fall back to Nimble's own status if +# the task died before it could report (e.g. Nimble itself failed). +real_rc="$(head -n 1 "$STATUS_FILE" 2>/dev/null | tr -d '[:space:]')" +[ -n "$real_rc" ] || real_rc=$rc +exit "$real_rc" diff --git a/DevControlBot_garage/PLAN.md b/DevControlBot_garage/PLAN.md new file mode 100644 index 0000000..c44eaf5 --- /dev/null +++ b/DevControlBot_garage/PLAN.md @@ -0,0 +1,6 @@ +1. Create a tidy Bot garage folder +2. Make inside a folder ready to be exported and shared with others +3. Create a basic bot with all white colors and does nothing else +4. make a nice runnable in bash script fro the RT official launcher +5. add nice radars: 1vs1 and melee +5. decide the gun to develop \ No newline at end of file diff --git a/DevControlBot_garage/README.md b/DevControlBot_garage/README.md new file mode 100644 index 0000000..030528e --- /dev/null +++ b/DevControlBot_garage/README.md @@ -0,0 +1,213 @@ +# DevControlBot + +Control skeleton bot: it boots, participates in the battle, and does **nothing** +per tick except `go()` — no movement logic, no scanning, no firing. + +Body, gun and radar are plain **white**, which by team convention means "this part +is not programmed yet". Colors are set **once at initialization** (`newDevControlBot`), +never inside the tick loop. + +## Layout + +``` +DevControlBot_garage/ +├── AGENTS.md +├── README.md +└── DevControlBot/ + ├── DevControlBot.nim # bot type + entry point (isMainModule) + ├── DevControlBot.json # bot metadata + ├── DevControlBot.nimble # the single `runBot` task + ├── DevControlBot.sh # BotLauncher entry point + └── .env # OPTIONAL local config (secrets; never commit) +``` + +There is no `src/`, no `tests/`, no `out/`, no `config.nim`, no `nimble.paths` +and no vendor directory. `DevControlBot.nim` is both the bot implementation and +the program entry point. + +## Dependencies + +Only two, both installed **globally** with Nimble and resolved from the global +package store — nothing vendored, no `nimble.paths`, no `nimble develop`: + +- `robocode_tankroyale_botapi >= 1.0.7` (the renamed `tankroyale_botapi`) +- `jsony >= 1.1.5` + +```sh +nimble install robocode_tankroyale_botapi +``` + +The `runBot` task asks Nimble where each of those packages actually lives +(`nimble path `) and passes explicit `--path:` flags to `nim c`. This is +required because the compiler's own `nimblepath="$home/.nimble/pkgs2/"` only +works when `$HOME` is set — with an empty environment (`env -i`, Nix build +sandbox, CI) the packages are otherwise not on the search path. If a package is +not installed, the build stops with `run: nimble install ` instead of a +compiler error. + +## Run + +```sh +./DevControlBot/DevControlBot.sh +``` + +`DevControlBot.sh` is the **BotLauncher entry point**. It is a thin wrapper: it +locates itself, `chdir`s to the directory holding the `.nimble` file, forwards +arguments and signals to `nimble runBot`, and propagates the real exit code. +All build/run/cleanup logic lives in the `runBot` task of `DevControlBot.nimble`, +which creates a throwaway `mktemp -d` build dir, compiles there, runs, and always +removes the dir via an `EXIT`/`INT`/`TERM` trap. **Nothing is left behind** — this +bot never creates an `out/` directory. + +`runBot` also accepts a metadata path: **as the first positional argument**. If it +ends in `.json` it is used instead of the bundled `DevControlBot.json`; there is +no `--json` flag (an argument that does not end in `.json` is forwarded to the bot +as-is). + +```sh +./DevControlBot/DevControlBot.sh # bundled metadata (default) +./DevControlBot/DevControlBot.sh /path/to/my.json # alt metadata +./DevControlBot/DevControlBot.sh --debug /path/to/my.json # alt metadata, debug build +``` + +With **no arguments at all** the wrapper does *not* print usage: it performs a +quiet RELEASE build and runs the bot with the bundled `DevControlBot.json`, +exactly as if that path had been passed. Usage is printed **only** for an +explicit `-h` / `--help` (exit 0, nothing is built). + +## Configuration comes from the environment — the API's own mechanism + +No configuration code exists in `DevControlBot.nim`, and none is needed. The +bot API reads its own environment variables inside `start()`: + +| variable | read at | default | +|---|---|---| +| `SERVER_URL` | `robocode_tankroyale_botapi.nim:424` (documented at `:396`) | `ws://localhost:7654` | +| `SERVER_SECRET` | `robocode_tankroyale_botapi.nim:425` | `""` | +| `BOT_NAME`, `BOT_VERSION`, `BOT_AUTHORS`, … | `bot_info.nim:117-130`, used only when no JSON is found | see that file | + +This is the supported mechanism the official BotLauncher uses: it puts those +variables in the bot's process environment before starting the binary. So the +only thing the build/run wrapper does is make sure the variables are *present* +in the bot's environment for local runs. + +## Optional `.env` (a convenience for local runs only) + +`DevControlBot/.env` is **optional**. If it is absent nothing happens: no error, +no warning beyond one informational line, the run behaves exactly as before and +the API defaults apply. It is not required, not generated, and not committed. + +When present it is loaded by the **`runBot` task**, not by `DevControlBot.sh`, so +it applies identically to `./DevControlBot.sh` and to a direct `nimble runBot` +and the wrapper stays a thin wrapper. Properties: + +- **Format:** plain `NAME=value` and `export NAME=value` lines; blank lines and + `#` comments ignored; anything that is not a simple `NAME=value` identifier + assignment is ignored (a `.env` can never smuggle in a command). +- **CRLF tolerant:** `CR` characters are stripped, so a Windows-edited file does + not turn the value into `bar\r`. +- **Nothing is ever printed.** Only the *names* taken from the file are reported, + never a value, and `set -x` is not used. +- **Overridable path:** `$DEVCONTROLBOT_ENV_FILE` points the loader at a + different file (useful for testing). + +### Precedence: the environment always wins + +A variable **already present in the environment is never overridden** by the +`.env` file. This matters because the official BotLauncher's values must win: +if a `.env` sitting in the checkout could overwrite them, this convenience would +silently break a real BotLauncher run. + +The `.env` file is therefore **not** simply sourced — sourcing it would do the +opposite, because an assignment in a sourced file overwrites an already-exported +variable of the same name. Instead the file is **filtered line by line**: a +`NAME=value` line is dropped when `NAME` is already set in the environment +(tested with `[ -n "${NAME+x}" ]`, i.e. *set but possibly empty*, not merely +non-empty), and only the still-unset names are then exported with `set -a` and +sourced. The filtering is what implements the precedence. + +```sh +SERVER_URL=ws://host:1234 ./DevControlBot.sh # shell wins, .env ignored for SERVER_URL +./DevControlBot.sh # .env supplies SERVER_URL +``` + +`.env` contains secrets, so it must never be committed; see the note in +[AGENTS.md](AGENTS.md) about the root `.gitignore`. + +## Build modes: release by default, `--debug` for the classic build + +| invocation | build | output | +|---|---|---| +| `DevControlBot.sh …` (default) | `nim c -d:release --hints:off` | quiet: dependency lines, one `build ok (release)`, one `running` line | +| `DevControlBot.sh --debug …` | plain `nim c` | classic diagnostics: `Hint: used config file …`, dot-progress line, `DEBUG BUILD` hint | + +`--debug` is stripped by the wrapper wherever it appears in the arguments (a +repeated `--debug` is harmless) and is handed to the `runBot` task via the +exported env var `$DEVCONTROLBOT_DEBUG` (`debug` / `release`) rather than as a +task argument: an env var can never be mistaken for a bot argument, cannot +collide with the `$DEVCONTROLBOT_STATUS_FILE` exit-code mechanism, and leaves the +task's argument parsing untouched. Every other argument is forwarded verbatim. + +In release mode the compiler's stdout/stderr is captured to a log and replayed +**only if the build fails**, so a failed build is always loud in both modes while +a successful release build stays short (10 lines including the no-server report, +vs 16 with `--debug`). `--help` / `-h` prints usage and exits 0 **without +building**; no arguments does the opposite — it builds and runs. + +The bot needs a running Tank Royale server on `ws://localhost:7654`; without one +the run fails with `[start] Cannot connect to ws://localhost:7654` and exits 1 +(expected during local verification). + +## Failure reporting and exit codes + +Nimble's `exec` raises a NimScript exception — stack trace, escaped copy of the +whole script — for *any* non-zero exit, which made "no game server" look like a +catastrophic build crash. The `runBot` shell therefore always exits 0 and writes +the real code to the status file named by `$DEVCONTROLBOT_STATUS_FILE`, which +`DevControlBot.sh` exports and then re-exits with. The real code is preserved, +not masked. + +| exit | meaning | +|---|---| +| `0` | clean run | +| `1` | bot could not connect to the game server on `ws://localhost:7654` (expected locally) | +| `2` | a dependency is not installed — `nimble install ` | +| `3` | compilation failed — the compiler's own errors are printed above | +| other | the bot's own exit code (runtime crash, bad metadata) | + +Each mode prints its own short line, e.g. + +``` +[start] Cannot connect to ws://localhost:7654: Connection refused +[devcontrolbot] bot exited with code 1. +[devcontrolbot] No game server on ws://localhost:7654. +[devcontrolbot] Start the Tank Royale server, or check it is listening on port 7654. +``` + +No `Exception raised during nimble script execution`, no stack trace, no source +dump. `nimble runBot` run directly (no status file) keeps the old behaviour and +still raises. + +Interrupt handling is unchanged: SIGINT/SIGTERM are forwarded, the build dir is +removed by the `EXIT` trap and the wrapper exits 143 (SIGTERM) / 130 (SIGINT). + +## Gotcha: `nimble runBot` must be run from `DevControlBot/` + +The `.nimble` file lives in `DevControlBot/`, so the task is only visible from +there: + +```sh +cd DevControlBot && nimble runBot # works +cd DevControlBot_garage && nimble runBot +# Error: Could not find a file with a .nimble extension inside the specified +# directory: .../DevControlBot_garage +``` + +`DevControlBot.sh` handles this for you by `cd`-ing itself. Note also that +`compile`/`build` are reserved Nimble builtin names — hence the `runBot` name. + +## Test + +There are **no tests** in this garage. The `test` task and the `tests/` directory +were removed along with the old build layout; see +[AGENTS.md](AGENTS.md) for the convention to follow when tests are added. \ No newline at end of file