rubowar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +156 -0
- data/Rakefile +12 -0
- data/TASKS.md +138 -0
- data/design-doc.md +537 -0
- data/lib/rubowar/version.rb +5 -0
- data/lib/rubowar.rb +8 -0
- data/sig/rubowar.rbs +4 -0
- metadata +51 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fac80228c1207c6235ca6d4eaa20f45fd1d3315a0d84355c1c6fb596f3c1de3a
|
|
4
|
+
data.tar.gz: 62a05e81bf17290816e8be0c8890cf0e27e0c082e843daa45ad4694b4da9bf86
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dc5de3e1387a90f2a090fb81cb0dc700f084c8370504604a85657d020cc1a97814d61dacf7858082766cc326b886eb45f9f93973c8b07a130157451193711c71
|
|
7
|
+
data.tar.gz: b8c6c84db2010b8a1730c7efc9a46189872409f6fdba77afc779d734bb6dc4e5e94c14a90703d6a7f10fbf8fa80eafbf04f1006037e696728b7fa42642c14280
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Utah Ruby Users Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tad Thorley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Rubowar
|
|
2
|
+
|
|
3
|
+
A competitive programming game where Ruby club members write Ruby classes to control robots ("Rubots") that battle in an arena. The engine is a standalone Ruby gem with a pluggable renderer interface.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class MyRobot
|
|
9
|
+
include Rubot
|
|
10
|
+
size :medium # :small, :medium, or :large
|
|
11
|
+
|
|
12
|
+
def tick
|
|
13
|
+
turret(10) # Rotate turret
|
|
14
|
+
fire(5) if look(1) # Fire when we see someone
|
|
15
|
+
thrust(2) if speed < 5 # Keep moving
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Robot API
|
|
21
|
+
|
|
22
|
+
### State Accessors (read-only)
|
|
23
|
+
|
|
24
|
+
| Method | Description |
|
|
25
|
+
|--------|-------------|
|
|
26
|
+
| `x`, `y` | Position in arena |
|
|
27
|
+
| `velocity_x`, `velocity_y` | Current velocity |
|
|
28
|
+
| `speed` | Velocity magnitude |
|
|
29
|
+
| `body_angle` | Body direction (0-360) |
|
|
30
|
+
| `turret_angle` | Turret direction (0-360, absolute) |
|
|
31
|
+
| `health` | Current HP (starts 100) |
|
|
32
|
+
| `energy` | Current energy (max 100) |
|
|
33
|
+
| `shield_level` | Shield strength (0-50, degrades 2/tick) |
|
|
34
|
+
| `arena_width`, `arena_height` | Arena dimensions |
|
|
35
|
+
| `friction` | Arena friction (default 0.95) |
|
|
36
|
+
| `tick_number` | Current game tick |
|
|
37
|
+
| `damage_dealt`, `damage_taken` | Match stats |
|
|
38
|
+
| `energons` | All energon positions (free) |
|
|
39
|
+
| `size` | Robot size (:small, :medium, :large) |
|
|
40
|
+
|
|
41
|
+
### Actions
|
|
42
|
+
|
|
43
|
+
| Method | Cost | Effect |
|
|
44
|
+
|--------|------|--------|
|
|
45
|
+
| `thrust(energy)` | energy | velocity = sqrt(energy) * 1.5 |
|
|
46
|
+
| `turn(degrees)` | \|degrees\|/10 | Rotate body |
|
|
47
|
+
| `turret(degrees)` | \|degrees\|/30 | Rotate turret (cheaper) |
|
|
48
|
+
| `fire(energy)` | energy | Damage = 1.5 * energy, 18 u/tick |
|
|
49
|
+
| `shield(energy)` | energy | Add to shield (max 50) |
|
|
50
|
+
|
|
51
|
+
### Sensing
|
|
52
|
+
|
|
53
|
+
| Method | Cost | Returns |
|
|
54
|
+
|--------|------|---------|
|
|
55
|
+
| `look(1-5)` | 1-5 | Line scan, more energy = more detail |
|
|
56
|
+
| `scan(width)` | width | Cone scan, returns robots + bullets |
|
|
57
|
+
| `pulse(radius)` | radius^2/10 | Circle scan, position + size only |
|
|
58
|
+
|
|
59
|
+
**look(energy) detail levels:**
|
|
60
|
+
- 1: position + size
|
|
61
|
+
- 2: + velocity
|
|
62
|
+
- 3: + shield_level
|
|
63
|
+
- 4: + health
|
|
64
|
+
- 5: + energy
|
|
65
|
+
|
|
66
|
+
### Callbacks
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
def on_hit(damage, direction) # Projectile hit
|
|
70
|
+
def on_spawn # Match start
|
|
71
|
+
def on_death # Health reached 0
|
|
72
|
+
def on_wall # Wall collision (10 damage)
|
|
73
|
+
def on_collision(robot) # Robot collision (5 damage)
|
|
74
|
+
def on_energon(amount) # Collected energon
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Robot Sizes
|
|
78
|
+
|
|
79
|
+
| Size | Radius | Energy Regen | Collision Bonus |
|
|
80
|
+
|------|--------|--------------|-----------------|
|
|
81
|
+
| `:small` | 15 | +8/tick | Takes +3 from larger |
|
|
82
|
+
| `:medium` | 20 | +10/tick | Standard |
|
|
83
|
+
| `:large` | 25 | +12/tick | Deals +3 to smaller |
|
|
84
|
+
|
|
85
|
+
## Arena
|
|
86
|
+
|
|
87
|
+
- **Dimensions**: Variable (default 800x600)
|
|
88
|
+
- **Origin**: Bottom-left (0,0)
|
|
89
|
+
- **Angles**: 0 = East, 90 = North, 180 = West, 270 = South
|
|
90
|
+
- **Friction**: 0.95 default (configurable)
|
|
91
|
+
- **Max speed**: 20 u/tick
|
|
92
|
+
- **Wall collision**: 10 damage + bounce
|
|
93
|
+
- **Robot collision**: 5 damage (with size modifiers)
|
|
94
|
+
|
|
95
|
+
## Physics
|
|
96
|
+
|
|
97
|
+
- `thrust(energy)` adds velocity: sqrt(energy) * 1.5
|
|
98
|
+
- Bullets travel 18 u/tick
|
|
99
|
+
- Self-damage is possible (your bullets can hit you)
|
|
100
|
+
- Friction slows robots each tick (velocity *= friction)
|
|
101
|
+
|
|
102
|
+
## Renderer Interface
|
|
103
|
+
|
|
104
|
+
The engine emits events for any renderer:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
match = Rubowar::Match.new(robots: [Spinner, Tracker])
|
|
108
|
+
|
|
109
|
+
# Block-based (real-time)
|
|
110
|
+
match.on(:tick) { |state| render_frame(state) }
|
|
111
|
+
match.on(:hit) { |event| play_sound(:hit) }
|
|
112
|
+
match.run
|
|
113
|
+
|
|
114
|
+
# Collect events (replays)
|
|
115
|
+
events = match.run
|
|
116
|
+
save_replay(events)
|
|
117
|
+
|
|
118
|
+
# Built-in terminal
|
|
119
|
+
match.run(renderer: Rubowar::Renderers::Terminal)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Event types**: `:tick`, `:fire`, `:hit`, `:death`, `:wall_collision`, `:robot_collision`, `:energon_spawn`, `:energon_collect`, `:match_end`
|
|
123
|
+
|
|
124
|
+
## Victory
|
|
125
|
+
|
|
126
|
+
- Last robot standing wins
|
|
127
|
+
- Tick limit (5000) prevents stalemates
|
|
128
|
+
- Tiebreaker: highest HP, then most damage dealt
|
|
129
|
+
|
|
130
|
+
## Error Handling
|
|
131
|
+
|
|
132
|
+
If robot code crashes or times out: **10 damage** + skip tick.
|
|
133
|
+
|
|
134
|
+
## Project Structure
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
rubowar/
|
|
138
|
+
├── lib/
|
|
139
|
+
│ ├── rubowar/
|
|
140
|
+
│ │ ├── rubot.rb # Module participants include
|
|
141
|
+
│ │ ├── arena.rb # Physics, collisions
|
|
142
|
+
│ │ ├── match.rb # Game loop
|
|
143
|
+
│ │ ├── robot_runner.rb # Sandboxed execution
|
|
144
|
+
│ │ ├── bullet.rb # Projectile tracking
|
|
145
|
+
│ │ ├── energon.rb # Energy power-up
|
|
146
|
+
│ │ └── events.rb # Event types
|
|
147
|
+
│ └── rubowar.rb
|
|
148
|
+
├── test/
|
|
149
|
+
├── robots/ # Example robots
|
|
150
|
+
├── bin/rubowar # CLI
|
|
151
|
+
└── rubowar.gemspec
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT License - see [LICENSE](LICENSE)
|
data/Rakefile
ADDED
data/TASKS.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Rubowar Implementation Tasks
|
|
2
|
+
|
|
3
|
+
## Phase 1: Core Engine (MVP)
|
|
4
|
+
|
|
5
|
+
### Project Setup
|
|
6
|
+
- [ ] Create `rubowar.gemspec`
|
|
7
|
+
- [ ] Create `Gemfile`
|
|
8
|
+
- [ ] Create `lib/rubowar.rb` (main entry point)
|
|
9
|
+
- [ ] Create `Rakefile` with test task
|
|
10
|
+
- [ ] Set up `test/test_helper.rb`
|
|
11
|
+
|
|
12
|
+
### Rubot Module (`lib/rubowar/rubot.rb`)
|
|
13
|
+
- [ ] Module inclusion hook (registers robot class)
|
|
14
|
+
- [ ] `size` class method (`:small`, `:medium`, `:large`)
|
|
15
|
+
- [ ] State accessors: `x`, `y`, `velocity_x`, `velocity_y`, `speed`
|
|
16
|
+
- [ ] State accessors: `body_angle`, `turret_angle`
|
|
17
|
+
- [ ] State accessors: `health`, `energy`, `shield_level`
|
|
18
|
+
- [ ] State accessors: `arena_width`, `arena_height`, `friction`
|
|
19
|
+
- [ ] State accessors: `tick_number`, `damage_dealt`, `damage_taken`
|
|
20
|
+
- [ ] State accessors: `energons`, `size`
|
|
21
|
+
- [ ] Action method: `thrust(energy)`
|
|
22
|
+
- [ ] Action method: `turn(degrees)`
|
|
23
|
+
- [ ] Action method: `turret(degrees)`
|
|
24
|
+
- [ ] Action method: `fire(energy)`
|
|
25
|
+
- [ ] Action method: `look(energy)` (basic version)
|
|
26
|
+
- [ ] Action queuing system
|
|
27
|
+
|
|
28
|
+
### Arena (`lib/rubowar/arena.rb`)
|
|
29
|
+
- [ ] Initialize with configurable width/height (default 800x600)
|
|
30
|
+
- [ ] Robot spawning (random positions, min distances)
|
|
31
|
+
- [ ] Position/velocity tracking for all robots
|
|
32
|
+
- [ ] Friction application each tick
|
|
33
|
+
- [ ] Wall collision detection + bounce + 10 damage
|
|
34
|
+
- [ ] Robot collision detection + push apart + 5 damage
|
|
35
|
+
- [ ] Max speed enforcement (20 u/tick)
|
|
36
|
+
|
|
37
|
+
### Bullet (`lib/rubowar/bullet.rb`)
|
|
38
|
+
- [ ] Position, velocity, owner, damage tracking
|
|
39
|
+
- [ ] Movement (18 u/tick)
|
|
40
|
+
- [ ] Collision detection with robots
|
|
41
|
+
- [ ] Out-of-bounds removal
|
|
42
|
+
|
|
43
|
+
### Match (`lib/rubowar/match.rb`)
|
|
44
|
+
- [ ] Initialize with robot classes and arena config
|
|
45
|
+
- [ ] Game loop structure
|
|
46
|
+
- [ ] Call robot `tick` methods
|
|
47
|
+
- [ ] Process queued actions
|
|
48
|
+
- [ ] Apply physics (movement, collisions)
|
|
49
|
+
- [ ] Update bullets
|
|
50
|
+
- [ ] Event emission system (`on` method for callbacks)
|
|
51
|
+
- [ ] Victory detection (last standing, tick limit, tiebreakers)
|
|
52
|
+
|
|
53
|
+
### Terminal Renderer (`lib/rubowar/renderers/terminal.rb`)
|
|
54
|
+
- [ ] ASCII arena display
|
|
55
|
+
- [ ] Robot positions with direction indicator
|
|
56
|
+
- [ ] Health/energy bars
|
|
57
|
+
- [ ] Bullet positions
|
|
58
|
+
- [ ] Match status output
|
|
59
|
+
|
|
60
|
+
### Example Robots (`robots/`)
|
|
61
|
+
- [ ] `spinner.rb` - Simple turret spinner, fires on sight
|
|
62
|
+
- [ ] `tracker.rb` - Seeks and tracks enemies
|
|
63
|
+
|
|
64
|
+
### Tests
|
|
65
|
+
- [ ] Rubot module tests
|
|
66
|
+
- [ ] Arena physics tests
|
|
67
|
+
- [ ] Bullet tests
|
|
68
|
+
- [ ] Match runner tests
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 2: Full Sensing & Combat
|
|
73
|
+
|
|
74
|
+
### Enhanced Sensing
|
|
75
|
+
- [ ] `look(energy)` with tiered detail (1-5 energy levels)
|
|
76
|
+
- [ ] `scan(width)` cone scan with bullet detection
|
|
77
|
+
- [ ] `pulse(radius)` circle scan
|
|
78
|
+
|
|
79
|
+
### Shield System
|
|
80
|
+
- [ ] `shield(energy)` action
|
|
81
|
+
- [ ] Shield degradation (2/tick)
|
|
82
|
+
- [ ] Damage absorption logic
|
|
83
|
+
- [ ] Max shield (50)
|
|
84
|
+
|
|
85
|
+
### Callbacks
|
|
86
|
+
- [ ] `on_hit(damage, direction)`
|
|
87
|
+
- [ ] `on_spawn`
|
|
88
|
+
- [ ] `on_death`
|
|
89
|
+
- [ ] `on_wall`
|
|
90
|
+
- [ ] `on_collision(robot)`
|
|
91
|
+
|
|
92
|
+
### Robot Sizes
|
|
93
|
+
- [ ] Size-based collision damage modifiers
|
|
94
|
+
- [ ] Size-based energy regeneration
|
|
95
|
+
- [ ] Size-based radius for collision
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Phase 3: Energons & Polish
|
|
100
|
+
|
|
101
|
+
### Energons (`lib/rubowar/energon.rb`)
|
|
102
|
+
- [ ] Spawn logic (every ~150 ticks, max 2)
|
|
103
|
+
- [ ] Random energy value (20-80)
|
|
104
|
+
- [ ] Collection detection (15 unit radius)
|
|
105
|
+
- [ ] `on_energon(amount)` callback
|
|
106
|
+
- [ ] `energons` accessor (always visible, free)
|
|
107
|
+
|
|
108
|
+
### CLI (`bin/rubowar`)
|
|
109
|
+
- [ ] Load robot files
|
|
110
|
+
- [ ] Run match with options (arena size, tick limit)
|
|
111
|
+
- [ ] Output results
|
|
112
|
+
|
|
113
|
+
### Replay System
|
|
114
|
+
- [ ] Event log recording
|
|
115
|
+
- [ ] JSON serialization
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Phase 4: Sandboxing & Safety
|
|
120
|
+
|
|
121
|
+
### Process Isolation (`lib/rubowar/robot_runner.rb`)
|
|
122
|
+
- [ ] Subprocess spawning for robot code
|
|
123
|
+
- [ ] JSON state serialization
|
|
124
|
+
- [ ] JSON action deserialization
|
|
125
|
+
- [ ] Timeout enforcement (10ms/tick)
|
|
126
|
+
- [ ] Dangerous method removal
|
|
127
|
+
|
|
128
|
+
### Error Handling
|
|
129
|
+
- [ ] Crash detection → 10 damage + skip tick
|
|
130
|
+
- [ ] Timeout detection → 10 damage + skip tick
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Design Reference
|
|
135
|
+
|
|
136
|
+
See `README.md` for complete API documentation.
|
|
137
|
+
|
|
138
|
+
Full design spec available at: `~/.claude/plans/mutable-yawning-pascal.md`
|
data/design-doc.md
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# Rubowar: Ruby Robot Battle Arena
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
A competitive programming game where Ruby club members write Ruby classes to control robots ("Rubots") that battle in an arena. The engine is a standalone Ruby gem with a pluggable renderer interface - bring your own visualization (terminal, web, desktop app, etc.).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Core Design Decisions
|
|
9
|
+
|
|
10
|
+
### Robot API
|
|
11
|
+
```ruby
|
|
12
|
+
class Destructo
|
|
13
|
+
include Rubot
|
|
14
|
+
|
|
15
|
+
def tick
|
|
16
|
+
# Access state via methods: energy, health, x, y, turret_angle, body_angle, etc.
|
|
17
|
+
# Call action methods directly: move, fire, turn_turret, look, radar
|
|
18
|
+
# Actions are queued internally and executed after tick returns
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Optional callbacks
|
|
22
|
+
def on_hit(damage, direction)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_spawn
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_death
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Error Handling**: If a robot's code crashes or times out, it takes **10 damage** and skips that tick. This encourages robust code without instant disqualification.
|
|
34
|
+
|
|
35
|
+
### Rubot Module - Complete API
|
|
36
|
+
|
|
37
|
+
#### Robot Size (chosen at class definition)
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
class MyRobot
|
|
41
|
+
include Rubot
|
|
42
|
+
size :medium # :small, :medium, or :large
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Size | Radius | Energy Regen | Collision Bonus |
|
|
47
|
+
|------|--------|--------------|-----------------|
|
|
48
|
+
| `:small` | 15 units | +8/tick | Takes +3 damage from larger robots |
|
|
49
|
+
| `:medium` | 20 units | +10/tick | Standard collision damage |
|
|
50
|
+
| `:large` | 25 units | +12/tick | Deals +3 damage to smaller robots |
|
|
51
|
+
|
|
52
|
+
**Collision damage formula**: Base 5 + size bonus. Small vs Large = Small takes 8, Large takes 2.
|
|
53
|
+
|
|
54
|
+
#### State Accessors (read-only)
|
|
55
|
+
| Method | Type | Description |
|
|
56
|
+
|--------|------|-------------|
|
|
57
|
+
| `x`, `y` | Float | Current position in arena |
|
|
58
|
+
| `velocity_x`, `velocity_y` | Float | Current velocity vector |
|
|
59
|
+
| `speed` | Float | Magnitude of velocity (convenience) |
|
|
60
|
+
| `body_angle` | Float | Direction robot body faces (0-360°) |
|
|
61
|
+
| `turret_angle` | Float | Direction turret points (0-360°, absolute) |
|
|
62
|
+
| `health` | Integer | Current HP (starts at 100) |
|
|
63
|
+
| `energy` | Integer | Current energy (max 100, regen varies by size) |
|
|
64
|
+
| `shield_level` | Integer | Current shield level (0 = no shield, degrades 2/tick, max 50) |
|
|
65
|
+
| `arena_width`, `arena_height` | Integer | Arena dimensions |
|
|
66
|
+
| `friction` | Float | Arena friction coefficient (default 0.95) |
|
|
67
|
+
| `tick_number` | Integer | Current game tick |
|
|
68
|
+
| `damage_dealt` | Integer | Total damage dealt this match |
|
|
69
|
+
| `damage_taken` | Integer | Total damage received this match |
|
|
70
|
+
| `energons` | Array | All energon positions `[{x:, y:}]` (always visible, free) |
|
|
71
|
+
| `size` | Symbol | Robot's size (`:small`, `:medium`, `:large`) |
|
|
72
|
+
|
|
73
|
+
#### Action Methods
|
|
74
|
+
| Method | Cost | Description |
|
|
75
|
+
|--------|------|-------------|
|
|
76
|
+
| `thrust(energy)` | energy spent | Add velocity in body direction. **velocity = √energy × 1.5** (physics-based). |
|
|
77
|
+
| `turn(degrees)` | \|degrees\|/10 energy | Rotate body only. Negative = left, positive = right. |
|
|
78
|
+
| `turret(degrees)` | \|degrees\|/30 energy | Rotate turret only. Cheaper than body turn. |
|
|
79
|
+
| `fire(energy)` | energy spent | Fire projectile. Damage = **1.5 × energy**. Any amount up to current energy. All travel 15 u/tick. |
|
|
80
|
+
| `shield(energy)` | energy spent | Pump energy into shield. Shield strength = energy invested. Degrades 2/tick. |
|
|
81
|
+
| `look(energy)` | 1-5 energy | Line in turret direction. More energy = more detail (see below). |
|
|
82
|
+
| `scan(width)` | width energy | Cone of `width` degrees. Returns `[{distance:, angle:}]` for robots in cone. |
|
|
83
|
+
| `pulse(radius)` | radius²/10 energy | Circle around self. Returns `[{distance:, angle:}]` for robots in radius. (least detail) |
|
|
84
|
+
|
|
85
|
+
**Turn cost examples**: `turn(90)` costs 9 energy, `turn(-45)` costs 4.5 energy, `turret(90)` costs 3 energy
|
|
86
|
+
|
|
87
|
+
#### Sensing System
|
|
88
|
+
|
|
89
|
+
**`look(energy)`** - Variable detail line scan (size always free)
|
|
90
|
+
|
|
91
|
+
| Cost | Returns |
|
|
92
|
+
|------|---------|
|
|
93
|
+
| 1 | position + size |
|
|
94
|
+
| 2 | + velocity |
|
|
95
|
+
| 3 | + shield_level |
|
|
96
|
+
| 4 | + health |
|
|
97
|
+
| 5 | + energy |
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
look(1) → {x: 400, y: 300, size: :large}
|
|
101
|
+
look(3) → {x: 400, y: 300, size: :large, velocity_x: 5, velocity_y: -2, shield_level: 20}
|
|
102
|
+
look(5) → {x: 400, y: 300, size: :large, velocity_x: 5, velocity_y: -2, shield_level: 20, health: 75, energy: 50}
|
|
103
|
+
look(n) → nil # nothing in line of sight
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**`scan(width)`** - Cone scan (width degrees, costs width energy)
|
|
107
|
+
- Returns: position + body_angle + shield_level + size
|
|
108
|
+
- Also detects bullets: position + velocity
|
|
109
|
+
|
|
110
|
+
**`pulse(radius)`** - Circle scan (costs radius²/10 energy)
|
|
111
|
+
- Returns: position + size only
|
|
112
|
+
- Also detects bullets: position only
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
scan(30) → {
|
|
116
|
+
robots: [{x: 400, y: 300, body_angle: 45, shield_level: 10, size: :small}],
|
|
117
|
+
bullets: [{x: 350, y: 280, velocity_x: 10, velocity_y: 5}]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
pulse(10) → {
|
|
121
|
+
robots: [{x: 400, y: 300, size: :medium}],
|
|
122
|
+
bullets: [{x: 350, y: 280}]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Bullet awareness**: scan and pulse detect incoming bullets!
|
|
127
|
+
|
|
128
|
+
#### Shield System
|
|
129
|
+
|
|
130
|
+
Shields absorb damage before health. They require continuous energy investment to maintain.
|
|
131
|
+
|
|
132
|
+
**Mechanics**:
|
|
133
|
+
- `shield(energy)` - Add energy to shield strength
|
|
134
|
+
- Shield degrades **2 points per tick** naturally
|
|
135
|
+
- Damage hits shield first, then health when shield = 0
|
|
136
|
+
- Shield absorbs damage 1:1 (10 damage removes 10 shield)
|
|
137
|
+
- Max shield strength: 50
|
|
138
|
+
|
|
139
|
+
**Example**:
|
|
140
|
+
```ruby
|
|
141
|
+
def tick
|
|
142
|
+
# Maintain shield at ~20 level
|
|
143
|
+
shield(5) if shield_level < 20
|
|
144
|
+
|
|
145
|
+
# Or burst shield when under attack
|
|
146
|
+
shield(30) if under_fire?
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Strategy**: Constant small investments (2-3 energy/tick) maintain a buffer. Burst shielding when you detect incoming bullets via `scan`.
|
|
151
|
+
|
|
152
|
+
#### Physics
|
|
153
|
+
- **Movement**: `thrust(energy)` adds velocity (√energy × 1.5). Friction slows robots each tick (default 0.95×, configurable per tournament).
|
|
154
|
+
- **Projectiles**: Travel at **18 units/tick** (slightly slower than max robot speed). Can hit anyone including the shooter. Disappear on contact or after leaving arena.
|
|
155
|
+
- **Wall collision**: **10 damage** + bounce. Walls hurt! Discourages reckless speed.
|
|
156
|
+
- **Robot collision**: **5 damage** to both robots + push apart. Less than walls.
|
|
157
|
+
- **Max speed**: Capped at 20 units/tick to prevent wall-slamming exploits.
|
|
158
|
+
|
|
159
|
+
#### Callbacks
|
|
160
|
+
```ruby
|
|
161
|
+
def on_hit(damage, direction) # Called when taking damage from projectile
|
|
162
|
+
def on_spawn # Called at match start
|
|
163
|
+
def on_death # Called when health reaches 0
|
|
164
|
+
def on_wall # Called on wall collision (10 damage)
|
|
165
|
+
def on_collision(robot) # Called on robot collision (5 damage), receives other robot info
|
|
166
|
+
def on_energon(amount) # Called when collecting energon (20-80 energy)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Arena
|
|
170
|
+
|
|
171
|
+
**Dimensions**: Variable (default 800×600 units)
|
|
172
|
+
- Configurable per match/tournament
|
|
173
|
+
- Robots access via `arena_width` and `arena_height` accessors
|
|
174
|
+
|
|
175
|
+
**Coordinate System**:
|
|
176
|
+
- Origin (0,0) at **bottom-left** corner
|
|
177
|
+
- X increases rightward (0 to arena_width)
|
|
178
|
+
- Y increases upward (0 to arena_height)
|
|
179
|
+
- **0° = Right (East)**, 90° = Up, 180° = Left, 270° = Down
|
|
180
|
+
|
|
181
|
+
**Spawning**:
|
|
182
|
+
- **Random positions** at match start
|
|
183
|
+
- Minimum distance from walls (50 units)
|
|
184
|
+
- Minimum distance between robots (100 units)
|
|
185
|
+
- Random starting angle
|
|
186
|
+
|
|
187
|
+
**Players**: 2-4 robots per match (1v1, 1v1v1, 1v1v1v1, or 2v2)
|
|
188
|
+
|
|
189
|
+
**Walls**: Collision causes 10 damage + bounce
|
|
190
|
+
|
|
191
|
+
### Energons
|
|
192
|
+
|
|
193
|
+
Simple energy power-up system. No healing - damage is permanent.
|
|
194
|
+
|
|
195
|
+
| Type | Effect | Spawn Rate |
|
|
196
|
+
|------|--------|------------|
|
|
197
|
+
| Energon | +20 to +80 energy (random) | Every ~150 ticks |
|
|
198
|
+
|
|
199
|
+
**Energon mechanics:**
|
|
200
|
+
- Max 2 energons on field at once
|
|
201
|
+
- Spawn at random positions (not too close to robots or walls)
|
|
202
|
+
- Collect by touching (radius ~15 units)
|
|
203
|
+
- Energy is capped at 100 even with energons
|
|
204
|
+
- `on_energon(amount)` callback fires when collected
|
|
205
|
+
|
|
206
|
+
**Detection:** Energons are **always visible** via the `energons` accessor (free, no energy cost). Returns `[{x:, y:}]` for all energons on the field. The actual energy amount (20-80) is hidden until collected - adds risk/reward decisions.
|
|
207
|
+
|
|
208
|
+
### Victory Condition
|
|
209
|
+
- Last robot standing wins
|
|
210
|
+
- Matches have a tick limit (e.g., 5000 ticks) to prevent stalemates
|
|
211
|
+
- If time expires: highest HP wins, then most damage dealt
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Architecture
|
|
216
|
+
|
|
217
|
+
### Project Structure
|
|
218
|
+
```
|
|
219
|
+
rubowar/
|
|
220
|
+
├── lib/
|
|
221
|
+
│ ├── rubowar/
|
|
222
|
+
│ │ ├── rubot.rb # The module participants include
|
|
223
|
+
│ │ ├── arena.rb # Game world, physics, collision
|
|
224
|
+
│ │ ├── match.rb # Runs a single match
|
|
225
|
+
│ │ ├── robot_runner.rb # Sandboxed execution of robot code
|
|
226
|
+
│ │ ├── bullet.rb # Projectile tracking
|
|
227
|
+
│ │ ├── energon.rb # Energy power-up
|
|
228
|
+
│ │ └── events.rb # Event types for renderers
|
|
229
|
+
│ └── rubowar.rb
|
|
230
|
+
├── test/
|
|
231
|
+
├── robots/ # Example robots for learning
|
|
232
|
+
│ ├── spinner.rb
|
|
233
|
+
│ ├── tracker.rb
|
|
234
|
+
│ └── wall_hugger.rb
|
|
235
|
+
├── bin/
|
|
236
|
+
│ └── rubowar # CLI to run matches
|
|
237
|
+
├── Gemfile
|
|
238
|
+
├── rubowar.gemspec
|
|
239
|
+
└── README.md
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Renderer Interface
|
|
243
|
+
|
|
244
|
+
The engine is renderer-agnostic. It emits events that any renderer can consume:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
match = Rubowar::Match.new(robots: [Spinner, Tracker], width: 800, height: 600)
|
|
248
|
+
|
|
249
|
+
# Option 1: Block-based (for real-time renderers)
|
|
250
|
+
match.on(:tick) { |state| render_frame(state) }
|
|
251
|
+
match.on(:hit) { |event| play_sound(:hit) }
|
|
252
|
+
match.on(:death) { |event| show_explosion(event) }
|
|
253
|
+
match.run
|
|
254
|
+
|
|
255
|
+
# Option 2: Collect all events (for replays)
|
|
256
|
+
events = match.run # Returns array of all events
|
|
257
|
+
save_replay(events)
|
|
258
|
+
|
|
259
|
+
# Option 3: Simple terminal output (built-in)
|
|
260
|
+
match.run(renderer: Rubowar::Renderers::Terminal)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Event Types**:
|
|
264
|
+
- `:tick` - Full game state each tick
|
|
265
|
+
- `:fire` - Robot fired a projectile
|
|
266
|
+
- `:hit` - Projectile hit a robot
|
|
267
|
+
- `:death` - Robot destroyed
|
|
268
|
+
- `:wall_collision` - Robot hit wall
|
|
269
|
+
- `:robot_collision` - Robots collided
|
|
270
|
+
- `:energon_spawn` - Energon appeared
|
|
271
|
+
- `:energon_collect` - Robot collected energon
|
|
272
|
+
- `:match_end` - Match concluded with winner
|
|
273
|
+
|
|
274
|
+
### Key Components
|
|
275
|
+
|
|
276
|
+
#### 1. Rubot Module (`lib/rubowar/rubot.rb`)
|
|
277
|
+
- Included by participant classes
|
|
278
|
+
- Registers the class with the game engine
|
|
279
|
+
- Provides helper methods: `move`, `fire`, `look`, `radar`, `turn_body`, `turn_turret`
|
|
280
|
+
- Actions are queued and returned from `tick`
|
|
281
|
+
|
|
282
|
+
#### 2. Arena (`lib/rubowar/arena.rb`)
|
|
283
|
+
- Manages game state: robot positions, velocities, health, energy
|
|
284
|
+
- Handles physics: movement, projectile travel, collisions
|
|
285
|
+
- Spawns and manages energons
|
|
286
|
+
- Emits events for renderers
|
|
287
|
+
|
|
288
|
+
#### 3. Match (`lib/rubowar/match.rb`)
|
|
289
|
+
- Loads robot classes (sandboxed)
|
|
290
|
+
- Runs game loop: call each robot's `tick`, process actions, update state
|
|
291
|
+
- Emits events via callbacks (renderer-agnostic)
|
|
292
|
+
- Determines winner
|
|
293
|
+
|
|
294
|
+
#### 4. Robot Runner (`lib/rubowar/robot_runner.rb`)
|
|
295
|
+
- **Sandboxing**: Runs robot code with restrictions
|
|
296
|
+
- No file I/O, network, system calls
|
|
297
|
+
- Time limit per tick (e.g., 10ms) to prevent infinite loops
|
|
298
|
+
- Memory limits
|
|
299
|
+
- Process isolation with JSON communication
|
|
300
|
+
|
|
301
|
+
#### 5. Terminal Renderer (`lib/rubowar/renderers/terminal.rb`)
|
|
302
|
+
- Built-in simple renderer for testing
|
|
303
|
+
- ASCII visualization of arena state
|
|
304
|
+
- Useful for development and debugging
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Implementation Phases
|
|
309
|
+
|
|
310
|
+
### Phase 1: Core Engine (MVP)
|
|
311
|
+
1. Project structure with gemspec
|
|
312
|
+
2. `Rubot` module with basic API (state accessors, action methods)
|
|
313
|
+
3. `Arena` with physics (movement, friction, collision)
|
|
314
|
+
4. `Match` runner with event emission
|
|
315
|
+
5. Basic actions: thrust, turn, turret, fire, look
|
|
316
|
+
6. Terminal renderer for testing
|
|
317
|
+
7. 2-3 example robots
|
|
318
|
+
|
|
319
|
+
### Phase 2: Full Sensing & Combat
|
|
320
|
+
1. Complete sensing system (look, scan, pulse)
|
|
321
|
+
2. Shield system
|
|
322
|
+
3. All callbacks (on_hit, on_wall, on_collision, etc.)
|
|
323
|
+
4. Robot sizes with tradeoffs
|
|
324
|
+
|
|
325
|
+
### Phase 3: Energons & Polish
|
|
326
|
+
1. Energon spawning and collection
|
|
327
|
+
2. CLI tool (`bin/rubowar`) for running matches
|
|
328
|
+
3. Match replay recording (event log)
|
|
329
|
+
|
|
330
|
+
### Phase 4: Sandboxing & Safety
|
|
331
|
+
1. Process isolation for robot execution
|
|
332
|
+
2. Time limits per tick
|
|
333
|
+
3. Dangerous method removal
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Technical Decisions
|
|
338
|
+
|
|
339
|
+
### Sandboxing (Process Isolation)
|
|
340
|
+
|
|
341
|
+
Since all code runs server-side, we need proper sandboxing to prevent cheating and abuse.
|
|
342
|
+
|
|
343
|
+
**Architecture:**
|
|
344
|
+
```
|
|
345
|
+
Main Process (Rails) Robot Process (Sandboxed)
|
|
346
|
+
┌─────────────────────┐ ┌─────────────────────┐
|
|
347
|
+
│ Match Runner │ JSON │ Robot Code │
|
|
348
|
+
│ - Arena physics │ ◄──────► │ - tick() execution │
|
|
349
|
+
│ - State management │ pipe │ - Isolated Ruby │
|
|
350
|
+
│ - WebSocket stream │ │ - No game access │
|
|
351
|
+
└─────────────────────┘ └─────────────────────┘
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**How it works:**
|
|
355
|
+
1. Match runner spawns a subprocess for each robot
|
|
356
|
+
2. Sends state as JSON: `{x:, y:, energy:, health:, energons:, ...}`
|
|
357
|
+
3. Robot subprocess executes `tick()`, returns actions as JSON: `[{action: "thrust", power: 3}, ...]`
|
|
358
|
+
4. Match runner validates actions, applies physics, repeats
|
|
359
|
+
5. Subprocess is killed if it times out (10ms limit per tick)
|
|
360
|
+
|
|
361
|
+
**Security layers:**
|
|
362
|
+
- **Process isolation**: Robot can't access Arena/Match (different process)
|
|
363
|
+
- **No dangerous methods**: Subprocess loads robot in clean environment, removes `File`, `IO`, `Net::HTTP`, `ObjectSpace`, `Kernel.system`, `eval`, `require`, etc.
|
|
364
|
+
- **Timeout enforcement**: Process-level kill if tick exceeds time limit
|
|
365
|
+
- **Resource limits**: ulimit on memory, CPU
|
|
366
|
+
- **Optional Docker**: For extra isolation, run subprocess in container
|
|
367
|
+
|
|
368
|
+
**Implementation**: Use Ruby's `Open3.popen3` or a gem like `childprocess` for subprocess management.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Example Robots
|
|
373
|
+
|
|
374
|
+
### Basic: Spinner
|
|
375
|
+
```ruby
|
|
376
|
+
class Spinner
|
|
377
|
+
include Rubot
|
|
378
|
+
|
|
379
|
+
def tick
|
|
380
|
+
turret(10) # Constantly rotate turret (cheap)
|
|
381
|
+
fire(1) if look # Fire when we see someone
|
|
382
|
+
thrust(1) if speed < 3 # Keep moving slowly
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Intermediate: Tracker
|
|
388
|
+
```ruby
|
|
389
|
+
class Tracker
|
|
390
|
+
include Rubot
|
|
391
|
+
|
|
392
|
+
def tick
|
|
393
|
+
if energy > 30
|
|
394
|
+
# Radar scan for enemies
|
|
395
|
+
enemies = radar(5) # Costs 25 energy
|
|
396
|
+
|
|
397
|
+
if enemies.any?
|
|
398
|
+
target = enemies.min_by { |e| e[:distance] }
|
|
399
|
+
|
|
400
|
+
# Aim and fire
|
|
401
|
+
angle_diff = normalize_angle(target[:angle] - turret_angle)
|
|
402
|
+
turret(angle_diff)
|
|
403
|
+
fire(3) if angle_diff.abs < 10 && energy > 40
|
|
404
|
+
|
|
405
|
+
# Strafe perpendicular to target
|
|
406
|
+
turn(target[:angle] + 90 - body_angle)
|
|
407
|
+
thrust(3)
|
|
408
|
+
else
|
|
409
|
+
search_pattern
|
|
410
|
+
end
|
|
411
|
+
else
|
|
412
|
+
# Low energy - conserve, just look
|
|
413
|
+
turret(15)
|
|
414
|
+
fire(1) if look
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def on_hit(damage, direction)
|
|
419
|
+
shield(true) if energy > 20 # Activate shield when hit
|
|
420
|
+
turn(direction + 90) # Turn perpendicular
|
|
421
|
+
thrust(5) # Burst away
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
private
|
|
425
|
+
|
|
426
|
+
def search_pattern
|
|
427
|
+
turn(5)
|
|
428
|
+
thrust(2) if speed < 5
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def normalize_angle(angle)
|
|
432
|
+
angle = angle % 360
|
|
433
|
+
angle -= 360 if angle > 180
|
|
434
|
+
angle
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Advanced: Wall Hugger
|
|
440
|
+
```ruby
|
|
441
|
+
class WallHugger
|
|
442
|
+
include Rubot
|
|
443
|
+
|
|
444
|
+
def on_spawn
|
|
445
|
+
@patrol_direction = 1
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def tick
|
|
449
|
+
# Stay near walls for protection
|
|
450
|
+
near_wall = x < 50 || x > arena_width - 50 ||
|
|
451
|
+
y < 50 || y > arena_height - 50
|
|
452
|
+
|
|
453
|
+
if near_wall
|
|
454
|
+
patrol_along_wall
|
|
455
|
+
else
|
|
456
|
+
move_to_nearest_wall
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Always be scanning and shooting
|
|
460
|
+
scan_and_fire
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def on_collision(what)
|
|
464
|
+
@patrol_direction *= -1 if what == :wall
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
private
|
|
468
|
+
|
|
469
|
+
def patrol_along_wall
|
|
470
|
+
# Move parallel to nearest wall
|
|
471
|
+
turn(90 * @patrol_direction)
|
|
472
|
+
thrust(3)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def move_to_nearest_wall
|
|
476
|
+
# Find nearest wall and head toward it
|
|
477
|
+
distances = {
|
|
478
|
+
0 => arena_width - x, # right wall
|
|
479
|
+
90 => arena_height - y, # top wall
|
|
480
|
+
180 => x, # left wall
|
|
481
|
+
270 => y # bottom wall
|
|
482
|
+
}
|
|
483
|
+
nearest_wall_angle = distances.min_by { |_, d| d }.first
|
|
484
|
+
turn(nearest_wall_angle - body_angle)
|
|
485
|
+
thrust(4)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def scan_and_fire
|
|
489
|
+
turret(20) # Sweep turret
|
|
490
|
+
distance = look
|
|
491
|
+
if distance && distance < 200
|
|
492
|
+
fire(4) # Heavy shot at close range
|
|
493
|
+
elsif distance
|
|
494
|
+
fire(2) # Light shot at distance
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Open Questions / Future Features
|
|
503
|
+
- Team battles (2v2, 3v3)?
|
|
504
|
+
- Customizable robot appearance/colors?
|
|
505
|
+
- Achievement system?
|
|
506
|
+
- Spectator chat during live matches?
|
|
507
|
+
- API for external bot submission (GitHub integration)?
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Dependencies
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
# Gemfile
|
|
515
|
+
source 'https://rubygems.org'
|
|
516
|
+
|
|
517
|
+
gemspec
|
|
518
|
+
|
|
519
|
+
group :development, :test do
|
|
520
|
+
gem 'minitest', '~> 5.20'
|
|
521
|
+
gem 'rake'
|
|
522
|
+
end
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Minimal dependencies - the engine should be self-contained with no external runtime dependencies.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Next Steps (Phase 1)
|
|
530
|
+
1. Initialize gem structure (gemspec, lib/, test/, robots/)
|
|
531
|
+
2. Implement `Rubot` module with state accessors and action methods
|
|
532
|
+
3. Implement `Arena` with physics (movement, friction, wall collision)
|
|
533
|
+
4. Implement `Match` runner with event emission
|
|
534
|
+
5. Implement `Bullet` for projectile tracking
|
|
535
|
+
6. Create terminal renderer
|
|
536
|
+
7. Create example robots (Spinner, Tracker)
|
|
537
|
+
8. Add tests for core functionality
|
data/lib/rubowar.rb
ADDED
data/sig/rubowar.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubowar
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tad Thorley
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A competitive programming game where Ruby developers write classes to
|
|
13
|
+
control robots that battle in a physics-based arena.
|
|
14
|
+
executables: []
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- LICENSE
|
|
19
|
+
- LICENSE.txt
|
|
20
|
+
- README.md
|
|
21
|
+
- Rakefile
|
|
22
|
+
- TASKS.md
|
|
23
|
+
- design-doc.md
|
|
24
|
+
- lib/rubowar.rb
|
|
25
|
+
- lib/rubowar/version.rb
|
|
26
|
+
- sig/rubowar.rbs
|
|
27
|
+
homepage: https://github.com/urug/rubowar
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
allowed_push_host: https://rubygems.org
|
|
32
|
+
homepage_uri: https://github.com/urug/rubowar
|
|
33
|
+
source_code_uri: https://github.com/urug/rubowar
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 3.2.0
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 4.0.3
|
|
49
|
+
specification_version: 4
|
|
50
|
+
summary: Ruby Robot Battle Arena
|
|
51
|
+
test_files: []
|