Rubowar includes several command-line scripts for running battles, tournaments, and logging.
Run one or more battles with configurable options, renderers, and output formats.
bin/battle [options] rubot1.rb rubot2.rb [rubot3.rb ...]
bin/battle [options] rubot.rb:2 # same rubot fighting itselfIf no rubot files are specified, loads all rubots from rubots/.
You can run multiple instances of the same rubot using either syntax:
# Repeat the file
bin/battle rubots/spinner.rb rubots/spinner.rb
# Use count syntax (more convenient)
bin/battle rubots/spinner.rb:2
# Mix and match
bin/battle rubots/spinner.rb:2 rubots/hunter.rb # 2 Spinners vs 1 Hunter
bin/battle rubots/spinner.rb:3 rubots/hunter.rb:2 # 3 Spinners vs 2 Hunters| Option | Description | Default |
|---|---|---|
-W, --width WIDTH |
Arena width | 800 |
-H, --height HEIGHT |
Arena height | 600 |
-f, --friction FRICTION |
Friction coefficient | 0.95 |
-t, --chronons LIMIT |
Chronon limit per battle | 1000 |
| Option | Description | Default |
|---|---|---|
-n, --battles COUNT |
Number of battles to run | 1 |
-s, --seed SEED |
Random seed for reproducibility | random |
| Option | Description | Default |
|---|---|---|
-w, --watch, --terminal |
Watch battle live in terminal | off |
-d, --delay SECONDS |
Delay between frames in watch mode | 0.05 |
--html |
Generate HTML replay file in battle-logs/ |
off |
--html-dir DIR |
Generate HTML replay in custom directory | - |
--json |
Generate JSON log file in battle-logs/ |
off |
--json-file FILE |
Generate JSON log to specific file | - |
| Option | Description | Default |
|---|---|---|
-o, --output FORMAT |
Summary format: text, json, csv |
text |
-q, --quiet |
Suppress summary output (file paths still shown) | off |
# Run a single battle between two rubots
bin/battle rubots/spinner.rb rubots/hunter.rb
# Same rubot fighting itself
bin/battle rubots/spinner.rb:2
bin/battle rubots/spinner.rb rubots/spinner.rb # equivalent
# 2 Spinners vs 1 Hunter
bin/battle rubots/spinner.rb:2 rubots/hunter.rb
# Watch a battle live in terminal
bin/battle --terminal rubots/spinner.rb rubots/crusher.rb
bin/battle -w rubots/spinner.rb rubots/crusher.rb # short form
# Generate HTML replay
bin/battle --html rubots/spinner.rb rubots/tracker.rb
# Generate JSON log
bin/battle --json rubots/spinner.rb rubots/tracker.rb
# Use all three renderers at once
bin/battle --terminal --html --json rubots/hunter.rb rubots/evader.rb
# Generate files to custom locations
bin/battle --html-dir replays/ --json-file logs/battle.json rubots/spinner.rb rubots/tracker.rb
# Quiet mode (suppresses summary, still shows file paths)
bin/battle --html -q rubots/spinner.rb rubots/tracker.rb
# Run 100 battles and output statistics
bin/battle -n 100 rubots/spinner.rb rubots/hunter.rb
# Reproducible battle with seed
bin/battle -s 12345 rubots/coroner.rb rubots/evader.rb
# Output summary as JSON
bin/battle -o json rubots/spinner.rb rubots/tracker.rb
# Output summary as CSV (good for spreadsheets)
bin/battle -n 50 -o csv rubots/spinner.rb rubots/hunter.rb > results.csv
# Custom arena size
bin/battle -W 800 -H 600 -t 2000 rubots/spinner.rb rubots/hunter.rb
# Battle all rubots in rubots/ directory
bin/battle --htmlTerminal (--terminal, -w)
- Live ASCII/Unicode visualization in terminal
- Use
-dto adjust frame delay (default: 0.05s)
HTML (--html)
- Self-contained HTML file with Canvas animation
- Playback controls: Play/Pause, step forward/back, go to start/end
- Speed control: 0.25x to 8x playback speed
- Timeline scrubber: Drag to any point in the battle
- Keyboard shortcuts: Space (play/pause), arrows (step), Home/End
- Smooth animation with interpolation between frames
- Status panel showing health, energy, shield, damage stats
JSON (--json)
- NDJSON format (one JSON object per line)
- Each line is a frame with rubot positions, bullets, energons
- Final line is battle summary with winner
All files are saved to battle-logs/ with timestamped names:
battle-20260116-143052-spinner-vs-tracker.htmlbattle-20260116-143052-spinner-vs-tracker.json
Record a battle to JSON for replay, analysis, or external tool integration.
bin/log [options] rubot1.rb rubot2.rb [rubot3.rb ...]If no rubot files are specified, loads all rubots from rubots/.
| Option | Description | Default |
|---|---|---|
-W, --width WIDTH |
Arena width | 480 |
-H, --height HEIGHT |
Arena height | 480 |
-f, --friction FRICTION |
Friction coefficient | 0.95 |
-t, --chronons LIMIT |
Chronon limit | 1000 |
-s, --seed SEED |
Random seed for reproducibility | random |
| Option | Description | Default |
|---|---|---|
-o, --output FILE |
Output file | stdout |
-p, --pretty |
Pretty-print JSON | off |
--stream |
Stream NDJSON (one object per line) | off |
The JSON output contains:
{
"metadata": {
"arena": { "width": 480, "height": 480, "friction": 0.95 },
"rubots": [
{ "name": "Spinner", "size": "medium", "id": "uuid-here" }
],
"recorded_at": "2024-01-15T10:30:00Z"
},
"frames": [
{
"type": "tick",
"chronon": 1,
"rubots": [
{
"id": "uuid", "name": "Spinner",
"x": 100.5, "y": 200.3,
"velocity_x": 0.0, "velocity_y": 0.0,
"speed": 0.0, "turret_angle": 45.0,
"health": 100, "energy": 100, "shield_level": 0,
"damage_dealt": 0, "damage_taken": 0,
"size": "medium", "alive": true
}
],
"bullets": [
{ "x": 150.0, "y": 180.0, "velocity_x": 18.0, "velocity_y": 0.0 }
],
"energons": [
{ "x": 400.0, "y": 300.0, "spawn_chronon": 80, "current_value": 25 }
]
},
{
"type": "summary",
"winner": { "id": "uuid", "name": "Spinner", ... },
"outcome": "victory",
"duration_ms": 1234
}
]
}With --stream, outputs newline-delimited JSON (one object per line) instead of a single JSON array. This is useful for:
- Real-time log processing
- Large battles that shouldn't be held in memory
- Piping to tools like
jq
# Log to stdout (compact JSON)
bin/log rubots/spinner.rb rubots/hunter.rb
# Log to file with pretty-print
bin/log -o replay.json -p rubots/spinner.rb rubots/hunter.rb
# Reproducible battle
bin/log -s 12345 -o battle.json rubots/coroner.rb rubots/evader.rb
# Stream NDJSON to file
bin/log --stream -o battle.ndjson rubots/spinner.rb rubots/hunter.rb
# Pipe to jq for analysis
bin/log -t 100 | jq '.frames[-1]'
# Extract just the winner
bin/log -t 500 | jq '.frames[] | select(.type == "summary") | .winner.name'
# Count total damage dealt
bin/log -t 1000 | jq '[.frames[] | select(.type == "tick") | .rubots[].damage_dealt] | max'Run a full tournament across all rubots in the rubots/ directory with 1v1, 1v1v1, and 1v1v1v1 formats.
bin/tournamentNo options - runs a comprehensive tournament with:
- All combinations of rubots
- 64 iterations per combination
- 1v1, 1v1v1, and 1v1v1v1 formats
- Points-based scoring
Produces a results table showing points earned in each format:
================================================================================
TOURNAMENT RESULTS
================================================================================
| Rubot | 1v1 Total | 1v1v1 Total | 1v1v1v1 Total | Grand Total |
|--------------|------------|--------------|----------------|--------------|
| Coroner | 2048 | 4320 | 5760 | 12128 |
| Spinner | 1536 | 3456 | 4608 | 9600 |
| ...
- 1v1: Winner gets N points (N = number of rubots)
- 1v1v1: 1st: 3 pts, 2nd: 2 pts, 3rd: 1 pt
- 1v1v1v1: 1st: 4 pts, 2nd: 3 pts, 3rd: 2 pts, 4th: 1 pt
Start an interactive Ruby console with Rubowar loaded.
bin/consoleUseful for experimenting with the API:
# In console
battle = Rubowar::Battle.local([Spinner, Hunter])
battle.run
puts battle.winner.rubot_class.nameInstall dependencies and set up the development environment.
bin/setup