diff --git a/.gitignore b/.gitignore index 7b60c7e..1aba0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ Thumbs.db # Logs *.log +error-log.txt # Project planning and internal docs .claude/* diff --git a/demos/battle-city-tank-arena/README.md b/demos/battle-city-tank-arena/README.md new file mode 100644 index 0000000..2a0c463 --- /dev/null +++ b/demos/battle-city-tank-arena/README.md @@ -0,0 +1,59 @@ +# Battle City Tank Arena + +A NetLogo LLM extension demo that replicates the key finding from [BattleAgentBench](https://arxiv.org/abs/2408.15971): **communication between LLM agents hurts most models — only strong models benefit from messaging**. + +## Hypothesis + +When LLM-controlled tanks can send tactical messages to allies: +- **Weak models** (gpt-4o-mini, etc.) perform *worse* due to message noise, hallucinated coordination, and context overload +- **Strong models** (gpt-4o, claude-3.5-sonnet, etc.) perform *better* by leveraging ally intel for coordinated tactics + +## Stages + +| Stage | Setup | What It Tests | +|-------|-------|---------------| +| 1: Solo Navigation | 1 LLM tank reaches a flag | Spatial reasoning with `llm:choose` | +| 2: Team Battle | 2 LLM tanks vs 2 bots (no comms) | Implicit cooperation via `llm:choose` | +| 3: Team + Comms | Same + messaging channel | Communication paradox via `llm:choose` + `llm:chat-with-template` + `llm:set-history` | + +## Setup + +1. Install the LLM extension in NetLogo 7.0.3+ +2. Edit `config.txt` with your provider and API key: + ``` + provider=openai + model=gpt-4o-mini + temperature=0.3 + api_key=YOUR_KEY_HERE + ``` +3. Open `battle-city-tank-arena.nlogox` in NetLogo +4. Select a stage, click Setup, then Go + +## Running the Experiment + +1. Set stage to "2: Team Battle", click Setup → Go. Note the final score. +2. Set stage to "3: Team + Comms", click Setup → Go. Compare scores. +3. Toggle `comms-override?` mid-run to see the effect live. +4. Change `config.txt` to a stronger model (e.g., `gpt-4o`) and repeat. + +## Expected Results + +- **gpt-4o-mini**: Stage 3 (with comms) performs *equal or worse* than Stage 2 +- **gpt-4o / claude-3.5-sonnet**: Stage 3 performs *better* than Stage 2 +- `show-thinking?` reveals the reasoning chain differences + +## LLM Primitives Used + +- `llm:load-config` — Load provider/model settings +- `llm:choose` — Select action from fixed options given observations +- `llm:clear-history` — Fresh context each decision tick +- `llm:chat-with-template` — Generate tactical messages (Stage 3) +- `llm:set-history` — Inject ally messages into decision context +- `llm:chat-with-thinking` — Expose reasoning chains (show-thinking mode) + +## Files + +- `battle-city-tank-arena.nlogox` — Main model +- `config.txt` — LLM provider configuration +- `action-template.yaml` — Action decision prompt (thinking mode) +- `message-template.yaml` — Communication prompt (Stage 3) diff --git a/demos/battle-city-tank-arena/action-template.yaml b/demos/battle-city-tank-arena/action-template.yaml new file mode 100644 index 0000000..fcb1ef6 --- /dev/null +++ b/demos/battle-city-tank-arena/action-template.yaml @@ -0,0 +1,9 @@ +system: | + You are a tank commander in a Battle City arena. Analyze your situation and pick the best action. + You MUST respond with EXACTLY one of the allowed actions, nothing else. + Allowed actions: move-forward, turn-left, turn-right, fire, stay + +template: | + {observation} + + Choose your action. Respond with EXACTLY one action name from: move-forward, turn-left, turn-right, fire, stay diff --git a/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox new file mode 100644 index 0000000..f3e6c46 --- /dev/null +++ b/demos/battle-city-tank-arena/battle-city-tank-arena.nlogox @@ -0,0 +1,1941 @@ + + + + let col-idx 0 + while [col-idx < 31 and col-idx < length row-str] [ + let ch item col-idx row-str + let px col-idx - 15 + let py 15 - row-idx + + if px >= min-pxcor and px <= max-pxcor and py >= min-pycor and py <= max-pycor [ + ask patch px py [ + if ch = "." [ + set terrain-type "open" + set pcolor gray - 3 + ] + if ch = "W" [ + set terrain-type "brick" + set wall-hp 2 + set pcolor brown + 1 + ] + if ch = "S" [ + set terrain-type "steel" + set wall-hp -1 ;; indestructible + set pcolor gray - 1 + ] + if ch = "R" [ + set terrain-type "base-red" + set base-team 1 + set base-hp 3 + set pcolor red - 2 + ] + if ch = "B" [ + set terrain-type "base-blue" + set base-team 2 + set base-hp 3 + set pcolor blue - 2 + ] + if ch = "~" [ + set terrain-type "water" + set pcolor cyan - 1 + ] + ] + ] + set col-idx col-idx + 1 + ] + set row-idx row-idx + 1 + ] + + ;; Store base HP in globals + set red-base-hp 3 + set blue-base-hp 3 +end + +;; ============================================================ +;; STAGE SETUP PROCEDURES +;; ============================================================ +to setup-stage-1 + ;; Solo navigation: 1 LLM tank (blue) must reach the flag + ;; Place flag near center-top + ask patch 0 10 [ + set terrain-type "flag" + set pcolor green + 1 + ] + create-flags 1 [ + setxy 0 10 + set shape "flag" + set color green + set size 2 + ] + + ;; Spawn 1 LLM tank at red base area (bottom) + create-tanks 1 [ + set team-id 2 + set color blue + 1 + set shape "arrow" + set size 1.8 + set heading 0 + set tank-direction 0 + set tank-health 3 + set is-llm? true + set last-action "none" + set my-observation "" + set ally-message "" + set my-message "" + set message-timer 0 + set fire-cooldown 0 + set last-move-failed? false + setxy 0 -13 + set spawn-x 0 + set spawn-y -13 + ] +end + +to setup-stage-2 + ;; Team battle: 2 LLM tanks (blue) vs 2 scripted bots (red), no comms + ;; Blue LLM tanks + create-tanks 1 [ + setup-tank-defaults 2 true + setxy -3 -13 + set spawn-x -3 + set spawn-y -13 + ] + create-tanks 1 [ + setup-tank-defaults 2 true + setxy 3 -13 + set spawn-x 3 + set spawn-y -13 + ] + + ;; Red bot tanks + create-tanks 1 [ + setup-tank-defaults 1 false + setxy -3 13 + set spawn-x -3 + set spawn-y 13 + set heading 180 + set tank-direction 180 + ] + create-tanks 1 [ + setup-tank-defaults 1 false + setxy 3 13 + set spawn-x 3 + set spawn-y 13 + set heading 180 + set tank-direction 180 + ] +end + +to setup-stage-3 + ;; Same as stage 2 but communication enabled + setup-stage-2 + set communication-enabled? comms-override? +end + +to setup-tank-defaults [team llm?] + set team-id team + ifelse team = 1 + [ set color red + 1 ] + [ set color blue + 1 ] + set shape "arrow" + set size 1.8 + set heading 0 + set tank-direction 0 + set tank-health 3 + set is-llm? llm? + set last-action "none" + set my-observation "" + set ally-message "" + set my-message "" + set message-timer 0 + set fire-cooldown 0 + set last-move-failed? false +end + +;; ============================================================ +;; GO — Main tick loop +;; ============================================================ +to go + if game-over? [ stop ] + if ticks >= max-ticks [ resolve-timeout stop ] + + ;; Update communication toggle live + if current-stage = 3 [ + set communication-enabled? comms-override? + ] + + ;; 1. Decrease cooldowns + ask tanks with [tank-health > 0] [ + if fire-cooldown > 0 [ set fire-cooldown fire-cooldown - 1 ] + if message-timer > 0 [ + set message-timer message-timer - 1 + if message-timer = 0 [ set label "" ] + ] + ] + + ;; 2. Build observations for LLM tanks + ask tanks with [is-llm? and tank-health > 0] [ + set my-observation build-observation + ] + + ;; 3. Communicate (Stage 3 only) + if current-stage = 3 and communication-enabled? [ + ask tanks with [is-llm? and tank-health > 0] [ generate-message ] + deliver-messages + ] + + ;; 4. Decide actions + ask tanks with [is-llm? and tank-health > 0] [ + choose-action + ] + ask tanks with [not is-llm? and tank-health > 0] [ + bot-decide + ] + + ;; 5. Execute actions + ask tanks with [tank-health > 0] [ + execute-action last-action + ] + + ;; 6. Move bullets and resolve collisions + move-bullets + check-collisions + + ;; 7. Update explosions + update-explosions + + ;; 8. Check win condition + check-win-condition + + tick +end + +;; ============================================================ +;; OBSERVATION BUILDER +;; ============================================================ +to-report build-observation + let obs "" + + ;; Ego info + set obs (word obs "You are at (" xcor "," ycor ") facing " direction-name tank-direction + " with " tank-health " HP.\n") + + ;; Terrain ahead (5 patches) + set obs (word obs "Ahead: ") + let i 1 + while [i <= 5] [ + let p patch-ahead i + if p != nobody [ + set obs (word obs "[" i ":" [terrain-type] of p "]") + ] + set i i + 1 + ] + set obs (word obs "\n") + + ;; 7x7 ASCII minimap centered on self + set obs (word obs "Map (7x7, you=X):\n") + let my-x pxcor + let my-y pycor + let map-y 3 + while [map-y >= -3] [ + let map-x -3 + while [map-x <= 3] [ + let p patch-at map-x map-y + ifelse p = nobody [ + set obs (word obs "#") + ] [ + ifelse map-x = 0 and map-y = 0 [ + set obs (word obs "X") + ] [ + let ch map-char p + set obs (word obs ch) + ] + ] + set map-x map-x + 1 + ] + set obs (word obs "\n") + set map-y map-y - 1 + ] + + ;; Visible enemies (in-cone 8 90) + let visible-enemies other tanks with [tank-health > 0 and team-id != [team-id] of myself] in-cone 8 90 + ifelse any? visible-enemies [ + set obs (word obs "Enemies visible: ") + ask visible-enemies [ + set obs (word obs "(" xcor "," ycor ") ") + ] + set obs (word obs "\n") + ] [ + set obs (word obs "No enemies visible.\n") + ] + + ;; Ally info (stages 2-3) + if current-stage >= 2 [ + let allies other tanks with [tank-health > 0 and team-id = [team-id] of myself] + if any? allies [ + set obs (word obs "Allies: ") + ask allies [ + set obs (word obs "(" xcor "," ycor " dir=" direction-name tank-direction " hp=" tank-health ") ") + ] + set obs (word obs "\n") + ] + ] + + ;; Ally message (stage 3) + if current-stage = 3 and communication-enabled? and ally-message != "" [ + set obs (word obs "Ally says: \"" ally-message "\"\n") + ] + + ;; Stage-specific objective + if current-stage = 1 [ + ;; Distance to flag + let flag-target one-of flags + if flag-target != nobody [ + set obs (word obs "Objective: Reach the flag at (" [xcor] of flag-target "," [ycor] of flag-target "). " + "Distance: " precision distance flag-target 1 "\n") + ] + ] + if current-stage >= 2 [ + set obs (word obs "Objective: Destroy enemy base and tanks. Your base HP: " + (ifelse-value (team-id = 1) [red-base-hp] [blue-base-hp]) + " Enemy base HP: " + (ifelse-value (team-id = 1) [blue-base-hp] [red-base-hp]) "\n") + ] + + ;; Last action result with move-failure feedback + if last-action != "none" [ + ifelse last-action = "move-forward" and last-move-failed? [ + set obs (word obs "Last action: move-forward FAILED (blocked by wall/obstacle)\n") + ] [ + set obs (word obs "Last action: " last-action "\n") + ] + ] + + ;; Open directions — report which cardinal directions are passable + let open-dirs [] + let north-patch patch my-x (my-y + 1) + let east-patch patch (my-x + 1) my-y + let south-patch patch my-x (my-y - 1) + let west-patch patch (my-x - 1) my-y + if north-patch != nobody and (member? [terrain-type] of north-patch (list "open" "base-red" "base-blue" "flag")) [ + set open-dirs lput "North" open-dirs + ] + if east-patch != nobody and (member? [terrain-type] of east-patch (list "open" "base-red" "base-blue" "flag")) [ + set open-dirs lput "East" open-dirs + ] + if south-patch != nobody and (member? [terrain-type] of south-patch (list "open" "base-red" "base-blue" "flag")) [ + set open-dirs lput "South" open-dirs + ] + if west-patch != nobody and (member? [terrain-type] of west-patch (list "open" "base-red" "base-blue" "flag")) [ + set open-dirs lput "West" open-dirs + ] + if not empty? open-dirs [ + set obs (word obs "Open directions: " (reduce [[a b] -> (word a ", " b)] open-dirs) "\n") + ] + + report obs +end + +;; Map character for ASCII minimap +to-report map-char [p] + let tt [terrain-type] of p + if tt = "brick" [ report "W" ] + if tt = "steel" [ report "S" ] + if tt = "water" [ report "~" ] + if tt = "base-red" [ report "R" ] + if tt = "base-blue" [ report "B" ] + if tt = "flag" [ report "F" ] + + ;; Check for tanks/bullets on patch + if any? (tanks-on p) with [tank-health > 0 and team-id != [team-id] of myself] [ report "E" ] + if any? (tanks-on p) with [tank-health > 0 and team-id = [team-id] of myself and self != myself] [ report "A" ] + + report "." +end + +;; Direction name helper +to-report direction-name [dir] + if dir = 0 [ report "North" ] + if dir = 90 [ report "East" ] + if dir = 180 [ report "South" ] + if dir = 270 [ report "West" ] + report "North" +end + +;; ============================================================ +;; ACTION SELECTION — LLM via llm:choose (or llm:chat-with-thinking) +;; ============================================================ +to choose-action + set total-llm-calls total-llm-calls + 1 + carefully [ + ifelse show-thinking? [ + ;; Use chat-with-thinking to expose reasoning chain + let prompt (word my-observation "\nChoose EXACTLY one action: move-forward, turn-left, turn-right, fire, stay") + let response llm:chat-with-thinking prompt + let action-text item 0 response + let thinking-text item 1 response + if thinking-text != "" [ + output-print (word "--- Tank " who " thinking ---") + output-print thinking-text + output-print "---" + ] + set last-action parse-action-response action-text + ] [ + ;; Standard llm:choose + let action llm:choose my-observation ["move-forward" "turn-left" "turn-right" "fire" "stay"] + set last-action action + ] + + if show-observations? [ + output-print (word "Tank " who " chose: " last-action) + ] + ] [ + set last-action "stay" + output-print (word "LLM error for tank " who ": " error-message) + ] +end + +;; Parse action from free-text response (for show-thinking mode) +to-report parse-action-response [response] + ;; NetLogo has no built-in lower-case, so match common casings + let r (word response) + if position "move-forward" r != false [ report "move-forward" ] + if position "Move-forward" r != false [ report "move-forward" ] + if position "turn-left" r != false [ report "turn-left" ] + if position "Turn-left" r != false [ report "turn-left" ] + if position "turn-right" r != false [ report "turn-right" ] + if position "Turn-right" r != false [ report "turn-right" ] + if position "fire" r != false [ report "fire" ] + if position "Fire" r != false [ report "fire" ] + if position "stay" r != false [ report "stay" ] + if position "Stay" r != false [ report "stay" ] + report "stay" +end + +;; ============================================================ +;; MESSAGE GENERATION — Stage 3 communication via llm:chat-with-template +;; ============================================================ +to generate-message + set total-llm-calls total-llm-calls + 1 + carefully [ + llm:clear-history + let vars (list + (list "observation" my-observation) + ) + let msg llm:chat-with-template "message-template.yaml" vars + ;; Truncate to keep messages brief + if length msg > 80 [ set msg substring msg 0 80 ] + set my-message msg + set label msg + set message-timer 3 + if show-observations? [ + output-print (word "Tank " who " says: " msg) + ] + ] [ + set my-message "" + output-print (word "Message error for tank " who ": " error-message) + ] +end + +to deliver-messages + ;; Swap messages between allied LLM tanks + let blue-llm-tanks sort tanks with [is-llm? and team-id = 2 and tank-health > 0] + if length blue-llm-tanks = 2 [ + let t1 item 0 blue-llm-tanks + let t2 item 1 blue-llm-tanks + let msg1 [my-message] of t1 + let msg2 [my-message] of t2 + ask t1 [ + set ally-message msg2 + if msg2 != "" [ set message-timer 3 ] + ] + ask t2 [ + set ally-message msg1 + if msg1 != "" [ set message-timer 3 ] + ] + ] +end + +;; ============================================================ +;; BOT AI — State machine: patrol → chase → attack → retreat +;; ============================================================ +to bot-decide + ;; Retreat if low health + if tank-health = 1 [ + bot-retreat + stop + ] + + ;; Attack: enemy aligned on same row/col within 6 patches + let target-enemy nobody + ;; Check in current facing direction + let ahead-patches patches-ahead-n 6 + let enemies-ahead tanks with [tank-health > 0 and team-id != [team-id] of myself and member? patch-here ahead-patches] + if any? enemies-ahead [ + set last-action "fire" + stop + ] + + ;; Chase: enemy visible in cone + let visible-enemies other tanks with [tank-health > 0 and team-id != [team-id] of myself] in-cone 8 120 + if any? visible-enemies [ + let nearest min-one-of visible-enemies [distance myself] + bot-chase nearest + stop + ] + + ;; Default: patrol + bot-patrol +end + +to bot-retreat + ;; Move toward own base + let target-x spawn-x + let target-y spawn-y + let desired-heading towardsxy target-x target-y + let desired-dir round-to-cardinal desired-heading + ifelse desired-dir != tank-direction [ + set last-action ifelse-value (turn-direction-for desired-dir = "left") ["turn-left"] ["turn-right"] + ] [ + ifelse can-move-forward? [ + set last-action "move-forward" + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + ] +end + +to bot-chase [enemy] + let desired-heading towards enemy + let desired-dir round-to-cardinal desired-heading + ifelse desired-dir != tank-direction [ + set last-action ifelse-value (turn-direction-for desired-dir = "left") ["turn-left"] ["turn-right"] + ] [ + ;; If aligned, fire; otherwise move forward + ifelse aligned-with? enemy [ + set last-action "fire" + ] [ + ifelse can-move-forward? [ + set last-action "move-forward" + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + ] + ] +end + +to bot-patrol + ;; Simple patrol: move forward, turn randomly when blocked + ifelse can-move-forward? [ + ;; Occasionally turn for variety + ifelse random 100 < 15 [ + set last-action one-of ["turn-left" "turn-right"] + ] [ + set last-action "move-forward" + ] + ] [ + set last-action one-of ["turn-left" "turn-right"] + ] + + ;; Occasionally fire while patrolling + if random 100 < 10 [ + set last-action "fire" + ] +end + +;; Check if enemy is aligned on same row/col in facing direction +to-report aligned-with? [enemy] + let ex [xcor] of enemy + let ey [ycor] of enemy + if tank-direction = 0 and pxcor = [pxcor] of enemy and ey > ycor [ report true ] + if tank-direction = 180 and pxcor = [pxcor] of enemy and ey < ycor [ report true ] + if tank-direction = 90 and pycor = [pycor] of enemy and ex > xcor [ report true ] + if tank-direction = 270 and pycor = [pycor] of enemy and ex < xcor [ report true ] + report false +end + +;; Patches ahead in facing direction, up to n +to-report patches-ahead-n [n] + let result patch-set nobody + let i 1 + while [i <= n] [ + let p patch-ahead i + if p != nobody [ set result (patch-set result p) ] + set i i + 1 + ] + report result +end + +;; Round heading to nearest cardinal direction +to-report round-to-cardinal [h] + ;; Normalize to 0-359 + set h h mod 360 + if h < 0 [ set h h + 360 ] + if h >= 315 or h < 45 [ report 0 ] + if h >= 45 and h < 135 [ report 90 ] + if h >= 135 and h < 225 [ report 180 ] + report 270 +end + +;; Determine whether to turn left or right to reach desired direction +to-report turn-direction-for [desired-dir] + let diff (desired-dir - tank-direction) mod 360 + if diff < 0 [ set diff diff + 360 ] + ifelse diff <= 180 + [ report "right" ] + [ report "left" ] +end + +;; Check if tank can move one patch forward +to-report can-move-forward? + let p patch-ahead 1 + if p = nobody [ report false ] + if [terrain-type] of p = "brick" [ report false ] + if [terrain-type] of p = "steel" [ report false ] + if [terrain-type] of p = "water" [ report false ] + if any? other (tanks-on p) with [tank-health > 0] [ report false ] + report true +end + +;; ============================================================ +;; ACTION EXECUTION +;; ============================================================ +to execute-action [action-name] + if action-name = "move-forward" [ + ifelse can-move-forward? [ + fd 1 + set last-move-failed? false + ] [ + set last-move-failed? true + ] + stop + ] + set last-move-failed? false + if action-name = "turn-left" [ + set tank-direction (tank-direction - 90) mod 360 + if tank-direction < 0 [ set tank-direction tank-direction + 360 ] + set heading tank-direction + stop + ] + if action-name = "turn-right" [ + set tank-direction (tank-direction + 90) mod 360 + set heading tank-direction + stop + ] + if action-name = "fire" [ + if fire-cooldown <= 0 [ + fire-bullet + set fire-cooldown 3 + ] + stop + ] + ;; "stay" or unknown: do nothing +end + +;; ============================================================ +;; BULLET MECHANICS +;; ============================================================ +to fire-bullet + hatch-bullets 1 [ + set shape "circle" + set size 0.5 + set color yellow + set heading [tank-direction] of myself + set bullet-team [team-id] of myself + set bullet-direction [tank-direction] of myself + set bullet-distance 0 + ] +end + +to move-bullets + ask bullets [ + ;; Bullets move 2 patches per tick + let steps 0 + while [steps < 2] [ + let next-p patch-ahead 1 + ifelse next-p = nobody [ + die ;; hit world edge + stop + ] [ + ;; Check for wall collision + let tt [terrain-type] of next-p + if tt = "steel" [ + ;; Bounce off steel, destroy bullet + spawn-explosion xcor ycor + die + stop + ] + if tt = "brick" [ + ;; Damage brick wall + ask next-p [ + set wall-hp wall-hp - 1 + ifelse wall-hp <= 0 [ + set terrain-type "open" + set pcolor gray - 3 + ] [ + set pcolor brown + 3 ;; damaged wall is lighter + ] + ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + + ;; Check for tank collision + let hit-tanks (tanks-on next-p) with [tank-health > 0 and team-id != [bullet-team] of myself] + if any? hit-tanks [ + ask one-of hit-tanks [ + set tank-health tank-health - 1 + if tank-health <= 0 [ + ;; Tank destroyed + ifelse team-id = 1 + [ set blue-score blue-score + 1 ] + [ set red-score red-score + 1 ] + spawn-explosion xcor ycor + set shape "x" + set size 1 + set color gray + ] + ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + + ;; Check for base collision + if tt = "base-red" or tt = "base-blue" [ + let bt [base-team] of next-p + if bt != bullet-team [ + ask next-p [ + set base-hp base-hp - 1 + ifelse bt = 1 + [ set red-base-hp red-base-hp - 1 ] + [ set blue-base-hp blue-base-hp - 1 ] + if base-hp <= 0 [ + set terrain-type "open" + set pcolor gray - 3 + ] + ] + ;; Score for base damage + ifelse bt = 1 + [ set blue-score blue-score + 3 ] + [ set red-score red-score + 3 ] + spawn-explosion [pxcor] of next-p [pycor] of next-p + die + stop + ] + ] + + ;; Move bullet forward + fd 1 + set bullet-distance bullet-distance + 1 + + ;; Despawn after 15 patches + if bullet-distance >= 15 [ + die + stop + ] + ] + set steps steps + 1 + ] + ] +end + +to check-collisions + ;; Stage 1: check if LLM tank reached flag + if current-stage = 1 [ + ask tanks with [is-llm? and tank-health > 0] [ + if any? flags-here [ + set game-over? true + set winner "Blue (LLM)" + output-print "LLM tank reached the flag!" + ] + ] + ] +end + +;; ============================================================ +;; EXPLOSIONS +;; ============================================================ +to spawn-explosion [ex ey] + hatch-explosions 1 [ + setxy ex ey + set shape "star" + set color orange + set size 1.0 + set explosion-timer 3 + ] +end + +to update-explosions + ask explosions [ + set explosion-timer explosion-timer - 1 + ;; Pulse size + set size 1.0 + (3 - explosion-timer) * 0.3 + set color orange - (3 - explosion-timer) + if explosion-timer <= 0 [ die ] + ] +end + +;; ============================================================ +;; WIN CONDITION CHECKS +;; ============================================================ +to check-win-condition + if current-stage = 1 [ stop ] ;; handled in check-collisions + + ;; Base destroyed + if red-base-hp <= 0 [ + set game-over? true + set winner "Blue" + output-print "Blue wins! Red base destroyed." + ] + if blue-base-hp <= 0 [ + set game-over? true + set winner "Red" + output-print "Red wins! Blue base destroyed." + ] + + ;; All enemy tanks eliminated + let red-alive count tanks with [team-id = 1 and tank-health > 0] + let blue-alive count tanks with [team-id = 2 and tank-health > 0] + if red-alive = 0 and blue-alive > 0 [ + set game-over? true + set winner "Blue" + output-print "Blue wins! All red tanks eliminated." + ] + if blue-alive = 0 and red-alive > 0 [ + set game-over? true + set winner "Red" + output-print "Red wins! All blue tanks eliminated." + ] +end + +to resolve-timeout + set game-over? true + ifelse red-score > blue-score [ + set winner "Red" + output-print (word "Time's up! Red wins " red-score " to " blue-score) + ] [ + ifelse blue-score > red-score [ + set winner "Blue" + output-print (word "Time's up! Blue wins " blue-score " to " red-score) + ] [ + set winner "Draw" + output-print (word "Time's up! Draw at " red-score " each") + ] + ] +end + +;; ============================================================ +;; REPORTERS for monitors +;; ============================================================ +to-report red-score-report + report red-score +end + +to-report blue-score-report + report blue-score +end + +to-report llm-calls-report + report total-llm-calls +end + +to-report winner-report + report winner +end + +to-report red-base-hp-report + report red-base-hp +end + +to-report blue-base-hp-report + report blue-base-hp +end + +to-report game-status + if game-over? [ report (word "GAME OVER: " winner " wins!") ] + report (word "Stage " current-stage " | Tick " ticks "/" max-ticks) +end +]]> + + + + + + + + + + + + + + + red-score-report + blue-score-report + red-base-hp-report + blue-base-hp-report + llm-calls-report + ticks + game-status + winner-report + + + + + + plot red-score + + + + plot blue-score + + + + + + + + plot total-llm-calls + + + + + ## WHAT IS IT? + +(a general understanding of what the model is trying to show or explain) + +## HOW IT WORKS + +(what rules the agents use to create the overall behavior of the model) + +## HOW TO USE IT + +(how to use the model, including a description of each of the items in the Interface tab) + +## THINGS TO NOTICE + +(suggested things for the user to notice while running the model) + +## THINGS TO TRY + +(suggested things for the user to try to do (move sliders, switches, etc.) with the model) + +## EXTENDING THE MODEL + +(suggested things to add or change in the Code tab to make the model more complicated, detailed, accurate, etc.) + +## NETLOGO FEATURES + +(interesting or unusual features of NetLogo that the model uses, particularly in the Code tab; or where workarounds were needed for missing features) + +## RELATED MODELS + +(models in the NetLogo Models Library and elsewhere which are of related interest) + +## CREDITS AND REFERENCES + +(a reference to the model's URL on the web if it has one, as well as any other necessary credits, citations, and links) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setup repeat 75 [ go ] + diff --git a/demos/battle-city-tank-arena/config.txt b/demos/battle-city-tank-arena/config.txt new file mode 100644 index 0000000..b0a3ba5 --- /dev/null +++ b/demos/battle-city-tank-arena/config.txt @@ -0,0 +1,3 @@ +provider=openai +model=gpt-4o-mini +temperature=0.3 diff --git a/demos/battle-city-tank-arena/message-template.yaml b/demos/battle-city-tank-arena/message-template.yaml new file mode 100644 index 0000000..9d80e74 --- /dev/null +++ b/demos/battle-city-tank-arena/message-template.yaml @@ -0,0 +1,11 @@ +system: | + You are a tank commander communicating with your ally in a Battle City arena. + Send a brief tactical message (one sentence max) about what you see and plan to do. + Focus on: enemy positions, threats, suggested coordination, or requests for help. + Be concise — your ally needs actionable intel, not essays. + +template: | + Your situation: + {observation} + + Write one short tactical message to your ally.