colstrom-rtanque 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +168 -0
- data/Rakefile +1 -0
- data/bin/rtanque +117 -0
- data/lib/rtanque.rb +31 -0
- data/lib/rtanque/arena.rb +8 -0
- data/lib/rtanque/bot.rb +117 -0
- data/lib/rtanque/bot/brain.rb +50 -0
- data/lib/rtanque/bot/brain_helper.rb +23 -0
- data/lib/rtanque/bot/command.rb +23 -0
- data/lib/rtanque/bot/radar.rb +54 -0
- data/lib/rtanque/bot/sensors.rb +37 -0
- data/lib/rtanque/bot/turret.rb +14 -0
- data/lib/rtanque/configuration.rb +47 -0
- data/lib/rtanque/explosion.rb +23 -0
- data/lib/rtanque/gui.rb +25 -0
- data/lib/rtanque/gui/bot.rb +71 -0
- data/lib/rtanque/gui/bot/health_color_calculator.rb +37 -0
- data/lib/rtanque/gui/draw_group.rb +30 -0
- data/lib/rtanque/gui/explosion.rb +25 -0
- data/lib/rtanque/gui/shell.rb +31 -0
- data/lib/rtanque/gui/window.rb +64 -0
- data/lib/rtanque/heading.rb +162 -0
- data/lib/rtanque/match.rb +73 -0
- data/lib/rtanque/match/tick_group.rb +50 -0
- data/lib/rtanque/movable.rb +51 -0
- data/lib/rtanque/normalized_attr.rb +69 -0
- data/lib/rtanque/point.rb +140 -0
- data/lib/rtanque/runner.rb +88 -0
- data/lib/rtanque/shell.rb +44 -0
- data/lib/rtanque/version.rb +3 -0
- data/resources/images/body.png +0 -0
- data/resources/images/bullet.png +0 -0
- data/resources/images/explosions/explosion2-1.png +0 -0
- data/resources/images/explosions/explosion2-10.png +0 -0
- data/resources/images/explosions/explosion2-11.png +0 -0
- data/resources/images/explosions/explosion2-12.png +0 -0
- data/resources/images/explosions/explosion2-13.png +0 -0
- data/resources/images/explosions/explosion2-14.png +0 -0
- data/resources/images/explosions/explosion2-15.png +0 -0
- data/resources/images/explosions/explosion2-16.png +0 -0
- data/resources/images/explosions/explosion2-17.png +0 -0
- data/resources/images/explosions/explosion2-18.png +0 -0
- data/resources/images/explosions/explosion2-19.png +0 -0
- data/resources/images/explosions/explosion2-2.png +0 -0
- data/resources/images/explosions/explosion2-20.png +0 -0
- data/resources/images/explosions/explosion2-21.png +0 -0
- data/resources/images/explosions/explosion2-22.png +0 -0
- data/resources/images/explosions/explosion2-23.png +0 -0
- data/resources/images/explosions/explosion2-24.png +0 -0
- data/resources/images/explosions/explosion2-25.png +0 -0
- data/resources/images/explosions/explosion2-26.png +0 -0
- data/resources/images/explosions/explosion2-27.png +0 -0
- data/resources/images/explosions/explosion2-28.png +0 -0
- data/resources/images/explosions/explosion2-29.png +0 -0
- data/resources/images/explosions/explosion2-3.png +0 -0
- data/resources/images/explosions/explosion2-30.png +0 -0
- data/resources/images/explosions/explosion2-31.png +0 -0
- data/resources/images/explosions/explosion2-32.png +0 -0
- data/resources/images/explosions/explosion2-33.png +0 -0
- data/resources/images/explosions/explosion2-34.png +0 -0
- data/resources/images/explosions/explosion2-35.png +0 -0
- data/resources/images/explosions/explosion2-36.png +0 -0
- data/resources/images/explosions/explosion2-37.png +0 -0
- data/resources/images/explosions/explosion2-38.png +0 -0
- data/resources/images/explosions/explosion2-39.png +0 -0
- data/resources/images/explosions/explosion2-4.png +0 -0
- data/resources/images/explosions/explosion2-40.png +0 -0
- data/resources/images/explosions/explosion2-41.png +0 -0
- data/resources/images/explosions/explosion2-42.png +0 -0
- data/resources/images/explosions/explosion2-43.png +0 -0
- data/resources/images/explosions/explosion2-44.png +0 -0
- data/resources/images/explosions/explosion2-45.png +0 -0
- data/resources/images/explosions/explosion2-46.png +0 -0
- data/resources/images/explosions/explosion2-47.png +0 -0
- data/resources/images/explosions/explosion2-48.png +0 -0
- data/resources/images/explosions/explosion2-49.png +0 -0
- data/resources/images/explosions/explosion2-5.png +0 -0
- data/resources/images/explosions/explosion2-50.png +0 -0
- data/resources/images/explosions/explosion2-51.png +0 -0
- data/resources/images/explosions/explosion2-52.png +0 -0
- data/resources/images/explosions/explosion2-53.png +0 -0
- data/resources/images/explosions/explosion2-54.png +0 -0
- data/resources/images/explosions/explosion2-55.png +0 -0
- data/resources/images/explosions/explosion2-56.png +0 -0
- data/resources/images/explosions/explosion2-57.png +0 -0
- data/resources/images/explosions/explosion2-58.png +0 -0
- data/resources/images/explosions/explosion2-59.png +0 -0
- data/resources/images/explosions/explosion2-6.png +0 -0
- data/resources/images/explosions/explosion2-60.png +0 -0
- data/resources/images/explosions/explosion2-61.png +0 -0
- data/resources/images/explosions/explosion2-62.png +0 -0
- data/resources/images/explosions/explosion2-63.png +0 -0
- data/resources/images/explosions/explosion2-64.png +0 -0
- data/resources/images/explosions/explosion2-65.png +0 -0
- data/resources/images/explosions/explosion2-66.png +0 -0
- data/resources/images/explosions/explosion2-67.png +0 -0
- data/resources/images/explosions/explosion2-68.png +0 -0
- data/resources/images/explosions/explosion2-69.png +0 -0
- data/resources/images/explosions/explosion2-7.png +0 -0
- data/resources/images/explosions/explosion2-70.png +0 -0
- data/resources/images/explosions/explosion2-71.png +0 -0
- data/resources/images/explosions/explosion2-8.png +0 -0
- data/resources/images/explosions/explosion2-9.png +0 -0
- data/resources/images/grass.png +0 -0
- data/resources/images/radar.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rtanque.gemspec +34 -0
- data/sample_bots/camper.rb +79 -0
- data/sample_bots/keyboard.rb +50 -0
- data/sample_bots/seek_and_destroy.rb +53 -0
- data/screenshots/battle_1.png +0 -0
- data/screenshots/battle_2.png +0 -0
- data/spec/rtanque/bot_spec.rb +239 -0
- data/spec/rtanque/heading_spec.rb +279 -0
- data/spec/rtanque/match_spec.rb +46 -0
- data/spec/rtanque/normalized_attr_spec.rb +90 -0
- data/spec/rtanque/point_spec.rb +196 -0
- data/spec/rtanque/radar_spec.rb +87 -0
- data/spec/rtanque/shell_spec.rb +35 -0
- data/spec/rtanque_spec.rb +34 -0
- data/spec/spec_helper.rb +51 -0
- data/templates/bot.erb +17 -0
- metadata +315 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5bb81d35a1207166bc9b7916b483204c4de420b6
|
4
|
+
data.tar.gz: d432f6a4c1422a07238ac0bb28181afb2e8f9a50
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7af7f69becd7608f1f9c6f50e38cf9d4679f54d3bd1ac9ce031405b7886d677cb7f9aed246e45e325a26ebded823845ffc1cde7fd49168b29804deccf94832d4
|
7
|
+
data.tar.gz: 390ed015f716aa4fd65e340f522d02b909117e9dfeee12bfecac0865298f879ee843eaaf7dc958a22b291c0960a8a49fde68602642eef8c5f2acc6d48a6b6788
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
.idea
|
19
|
+
twitter_images/
|
20
|
+
bots/
|
21
|
+
example.rb
|
22
|
+
examples/
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
RTanque
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.ci
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Adam Williams
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# RTanque [![Build Status](https://travis-ci.org/awilliams/RTanque.png?branch=dev)](https://travis-ci.org/awilliams/RTanque) [![CodeClimate](https://codeclimate.com/github/awilliams/RTanque.png)](https://codeclimate.com/github/awilliams/RTanque)
|
2
|
+
|
3
|
+
**What is this?**
|
4
|
+
RTanque is a game for ( *Ruby* ) programmers. Players program the brain of a tank and then send their tank+brain into battle with other tanks. All tanks are otherwise equal.
|
5
|
+
|
6
|
+
[Getting started guide](http://awilliams.github.io/articles/rtanque-getting-started/)
|
7
|
+
|
8
|
+
Rules of the game are simple: Last bot standing wins. Gameplay is also pretty simple. Each tank has a **base**, **turret** and **radar**, each of which rotate independently. The base moves the tank, the turret has a gun mounted to it which can fire at other tanks, and the radar detects other tanks in its field of vision.
|
9
|
+
|
10
|
+
Have fun competing against friends' tanks or the sample ones included. Maybe you'll start a small league at your local Ruby meetup. CLI provides easy way to download bots from gists.
|
11
|
+
|
12
|
+
Sound difficult or time consuming? It's not! Check out the included sample tank [Seek&Destroy](https://github.com/awilliams/RTanque/blob/master/sample_bots/seek_and_destroy.rb) (which is actually fairly difficult to beat with the keyboard controlled bot). Note that it clocks in at under 50 LOC.
|
13
|
+
|
14
|
+
This is not an original idea, see [influences](https://github.com/awilliams/RTanque#influences). There's a lot of resources out there around tank design and tactics that could be applied to RTanque.
|
15
|
+
|
16
|
+
How does it look? Here's a video of a battle:
|
17
|
+
|
18
|
+
<a href="http://www.youtube.com/watch?feature=player_embedded&v=UPBqwOgGlVY
|
19
|
+
" target="_blank"><img src="http://img.youtube.com/vi/UPBqwOgGlVY/0.jpg"
|
20
|
+
alt="RTanque Demo" width="640" height="480" border="10" /></a>
|
21
|
+
|
22
|
+
#### Influences
|
23
|
+
RTanque is based on the Java project [Robocode](http://robocode.sourceforge.net/) and inspired by other Ruby ports. Thanks and credit go to them both.
|
24
|
+
|
25
|
+
* [RobotWar](http://corewar.co.uk/robotwar/) - Perhaps the canonical version. Created in 1970's and set in the distant 2002. Apple II
|
26
|
+
* [Robocode](http://robocode.sourceforge.net/) - Java game, originally from IBM. Tank & explosion images taken from here.
|
27
|
+
* [RRobots](http://rrobots.rubyforge.org/) - Ruby port of Robocode. 2005
|
28
|
+
* [RRobots fork](https://github.com/ralreegorganon/rrobots)
|
29
|
+
* [FightCode](http://fightcodegame.com/) - Online javascript tank game
|
30
|
+
* [Scalatron](http://scalatron.github.com/) - Scala bot game
|
31
|
+
* [Many more...](https://www.google.com/?q=robocode%20clone)
|
32
|
+
|
33
|
+
## Requirements
|
34
|
+
|
35
|
+
* The [Gosu](https://github.com/jlnr/gosu) library used for rendering has some dependencies. Use the [Gosu getting started](https://github.com/jlnr/gosu/wiki/Getting-Started-on-Linux) to resolve any for your system.
|
36
|
+
* Ruby 2.0.0 or 1.9.3 (tested on 1.8.7 and 1.9.2)
|
37
|
+
|
38
|
+
## Quick Start
|
39
|
+
|
40
|
+
Make a project directory, init bundler, add the RTanque gem, and create a bot:
|
41
|
+
|
42
|
+
$ mkdir RTanque; cd RTanque
|
43
|
+
$ bundle init
|
44
|
+
$ echo "gem 'rtanque'" >> Gemfile
|
45
|
+
$ bundle
|
46
|
+
$ bundle exec rtanque new_bot my_deadly_bot
|
47
|
+
$ bundle exec rtanque start bots/my_deadly_bot sample_bots/keyboard sample_bots/camper:x2
|
48
|
+
|
49
|
+
*Drive the Keyboard bot with asdf. Aim/fire with the arrow keys*
|
50
|
+
|
51
|
+
## [RTanque Documentation](http://rubydoc.info/github/awilliams/RTanque/master/frames/file/README.md)
|
52
|
+
|
53
|
+
* [RTanque](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque)
|
54
|
+
* [RTanque::Bot::Brain](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque/Bot/Brain)
|
55
|
+
* [RTanque::Heading](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque/Heading)
|
56
|
+
* [RTanque::Point](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque/Point)
|
57
|
+
|
58
|
+
## Advanced Options
|
59
|
+
|
60
|
+
Set arena dimensions
|
61
|
+
|
62
|
+
$ bundle exec rtanque start --width=400 --height=400 sample_bots/camper:x4
|
63
|
+
|
64
|
+
Adjust max ticks allowed
|
65
|
+
|
66
|
+
$ bundle exec rtanque start --max_ticks=500 sample_bots/camper:x4
|
67
|
+
|
68
|
+
Run headless match (no gui)
|
69
|
+
|
70
|
+
$ bundle exec rtanque start --gui=false sample_bots/camper:x4
|
71
|
+
|
72
|
+
Run team match (teams are currently determined by bot name. Bots with same name are on the same team. The match finished if alive bots have the same name.) https://github.com/awilliams/RTanque/pull/10
|
73
|
+
|
74
|
+
$ bundle exec rtanque start --teams sample_bots/camper:x4 sample_bots/seek_and_destroy.rb:4
|
75
|
+
|
76
|
+
Quiet mode (less console chatter).
|
77
|
+
|
78
|
+
$ bundle exec rtanque start --quiet sample_bots/camper:x4
|
79
|
+
|
80
|
+
Set random number seed, allowing same battle to be repeated. https://github.com/awilliams/RTanque/pull/7
|
81
|
+
|
82
|
+
$ bundle exec rtanque start --seed 1234 sample_bots/camper:x4
|
83
|
+
|
84
|
+
**Experimental** Disable garbage collection during match
|
85
|
+
|
86
|
+
$ bundle exec rtanque start --gc=false sample_bots/camper:x4
|
87
|
+
|
88
|
+
## Sharing
|
89
|
+
At some point you'll want to compete against other bots, or maybe you'll even organize a small tournament. Sharing bots is easy.
|
90
|
+
|
91
|
+
Ask your friends to upload their bot(s) in a [gist](https://gist.github.com/), which you can then download with the following command:
|
92
|
+
|
93
|
+
bundle exec rtanque get_gist <gist_id> ...
|
94
|
+
|
95
|
+
For example, to download [Marksman](https://gist.github.com/SteveRidout/5909793)
|
96
|
+
|
97
|
+
bundle exec rtanque get_gist 5909793
|
98
|
+
|
99
|
+
If you'd like to publicly share your bot, post its gist id on the wiki https://github.com/awilliams/RTanque/wiki/bot-gists
|
100
|
+
|
101
|
+
## Bot API
|
102
|
+
|
103
|
+
The tank api consists of reading input from Brain#sensors and giving output to Brain#command
|
104
|
+
|
105
|
+
**[Brain#sensors](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque/Bot/Sensors)**
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
class Bot < RTanque::Bot::Brain
|
109
|
+
# RTanque::Bot::Sensors =
|
110
|
+
# Struct.new(:ticks, :health, :speed, :position, :heading, :radar, :turret)
|
111
|
+
def tick!
|
112
|
+
sensors.ticks # Integer
|
113
|
+
sensors.health # Float
|
114
|
+
sensors.position # RTanque::Point
|
115
|
+
sensors.heading # RTanque::Heading
|
116
|
+
sensors.speed # Float
|
117
|
+
sensors.radar_heading # RTanque::Heading
|
118
|
+
sensors.turret_heading # RTanque::Heading
|
119
|
+
sensors.radar.each do |scanned_bot|
|
120
|
+
# scanned_bot: RTanque::Bot::Radar::Reflection
|
121
|
+
# Reflection(:heading, :distance, :name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
**[Brain#command](http://rubydoc.info/github/awilliams/RTanque/master/frames/RTanque/Bot/Command)**
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
class Bot < RTanque::Bot::Brain
|
130
|
+
# RTanque::Bot::Command =
|
131
|
+
# Struct.new(:speed, :heading, :radar_heading, :turret_heading, :fire_power)
|
132
|
+
def tick!
|
133
|
+
command.speed = 1
|
134
|
+
command.heading = Math::PI / 2.0
|
135
|
+
command.radar_heading = Math::PI
|
136
|
+
command.turret_heading = Math::PI
|
137
|
+
command.fire(3)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
**RTanque::Heading**
|
143
|
+
This class handles angles. It is a wrapper around Float bound to `(0..Math::PI*2)`
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
RTanque::Heading.new(Math::PI)
|
147
|
+
=> <RTanque::Heading: 3.141592653589793rad 180.0deg>
|
148
|
+
|
149
|
+
RTanque::Heading.new_from_degrees(180)
|
150
|
+
=> <RTanque::Heading: 3.141592653589793rad 180.0deg>
|
151
|
+
|
152
|
+
RTanque::Heading.new_from_degrees(180) + RTanque::Heading.new(Math::PI)
|
153
|
+
=> <RTanque::Heading: 0.0rad 0.0deg>
|
154
|
+
|
155
|
+
RTanque::Heading.new(Math::PI) + (Math::PI / 2.0)
|
156
|
+
=> <RTanque::Heading: 4.71238898038469rad 270.0deg>
|
157
|
+
|
158
|
+
RTanque::Heading.new == 0
|
159
|
+
=> true
|
160
|
+
```
|
161
|
+
|
162
|
+
## Contributing
|
163
|
+
|
164
|
+
1. Fork it
|
165
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
166
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
167
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
168
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/rtanque
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'thor'
|
4
|
+
require 'rtanque'
|
5
|
+
require 'rtanque/runner'
|
6
|
+
require 'octokit'
|
7
|
+
|
8
|
+
class RTanqueCLI < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
BOT_DIR = 'bots'
|
11
|
+
source_root(File.expand_path('../../', __FILE__))
|
12
|
+
|
13
|
+
desc "start <path_to_brain> <path_to_brain>:x3 ...", "Starts match with given bots"
|
14
|
+
long_desc <<-LONGDESC
|
15
|
+
Start an RTanque match. Provide as arguments paths to the brains.
|
16
|
+
Note that multiple of the same bot can be loaded by appending ':x<int>' to the path. Eg 'bot/my_bot:x2'
|
17
|
+
|
18
|
+
There exist a few sample bots to help get started:
|
19
|
+
\x5 * sample_bots/keyboard (special bot controlled with keyboard: a/s/d/f and arrow keys)
|
20
|
+
\x5 * sample_bots/seek_and_destroy
|
21
|
+
\x5 * sample_bots/camper
|
22
|
+
LONGDESC
|
23
|
+
method_option :width, :aliases => '-w', :default => 1200, :type => :numeric, :banner => 'width of window'
|
24
|
+
method_option :height, :aliases => '-h', :default => 700, :type => :numeric, :banner => 'height of window'
|
25
|
+
method_option :max_ticks, :aliases => '-m', :default => Float::INFINITY, :type => :numeric, :banner => 'max ticks allowed per match'
|
26
|
+
method_option :teams, :default => false, :type => :boolean, :banner => 'true to do a team based match based on tank names'
|
27
|
+
method_option :gui, :default => true, :type => :boolean, :banner => 'false to run headless'
|
28
|
+
method_option :gc, :default => true, :type => :boolean, :banner => 'disable GC (EXPERIMENTAL)'
|
29
|
+
method_option :quiet, :aliases => '-q', :default => false, :type => :boolean, :banner => 'disable chatter'
|
30
|
+
method_option :seed, :default => Kernel.srand, :type => :numeric, :banner => 'random number seed value'
|
31
|
+
def start(*brain_paths)
|
32
|
+
Kernel.srand(options[:seed])
|
33
|
+
runner = RTanque::Runner.new(options[:width], options[:height], options[:max_ticks], options[:teams])
|
34
|
+
brain_paths.each { |brain_path|
|
35
|
+
begin
|
36
|
+
runner.add_brain_path(brain_path)
|
37
|
+
rescue RTanque::Runner::LoadError => e
|
38
|
+
say e.message, :red
|
39
|
+
exit false
|
40
|
+
end
|
41
|
+
}
|
42
|
+
self.print_start_banner(runner) unless options[:quiet]
|
43
|
+
self.set_gc(options[:gc]) { runner.start(options[:gui]) }
|
44
|
+
self.print_runner_stats(runner) unless options[:quiet]
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "new_bot <bot_name>", "Creates a new bot"
|
48
|
+
long_desc <<-LONGDESC
|
49
|
+
Helper to create a basic brain template in the bots directory
|
50
|
+
LONGDESC
|
51
|
+
def new_bot(bot_name)
|
52
|
+
@bot_name = bot_name
|
53
|
+
@bot_class_name = Thor::Util.camel_case(bot_name)
|
54
|
+
template('templates/bot.erb', "#{BOT_DIR}/#{Thor::Util.snake_case(bot_name)}.rb")
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "get_gist <gist_id> <gist_id> ...", "Downloads files from given gist ids into #{BOT_DIR} directory"
|
58
|
+
long_desc <<-LONGDESC
|
59
|
+
Helper to download tanks from github gists for easier sharing. Gists can be both 'secret' and 'public'.
|
60
|
+
LONGDESC
|
61
|
+
method_option :force, :aliases => '-f', :default => false, :type => :boolean, :banner => 'overwrite existing file without prompt'
|
62
|
+
def get_gist(*gist_ids)
|
63
|
+
gist_ids.each { |gist_id| self.download_gist(gist_id, options) }
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def print_start_banner(runner)
|
69
|
+
self.print_stats{ |table|
|
70
|
+
# print options
|
71
|
+
options.each { |opts| table << [set_color(opts[0], :yellow), opts[1].to_s] }
|
72
|
+
# print bots
|
73
|
+
table << [set_color('Bots', :green), runner.match.bots.map { |bot| bot.name }]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_runner_stats(runner)
|
78
|
+
say '='*30
|
79
|
+
self.print_stats{ |table|
|
80
|
+
table << [set_color('Ticks', :blue), runner.match.ticks.to_s]
|
81
|
+
table << [set_color('Survivors', :green)] + runner.match.bots.map { |bot| "#{bot.name} [#{bot.health.round}]" }
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_gc(gc = true)
|
86
|
+
if gc
|
87
|
+
yield
|
88
|
+
else
|
89
|
+
GC.disable
|
90
|
+
begin
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
GC.enable
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def download_gist(gist_id, options)
|
99
|
+
client = Octokit::Client.new
|
100
|
+
begin
|
101
|
+
gist = client.gist(gist_id)
|
102
|
+
rescue Octokit::NotFound
|
103
|
+
puts set_color("Error! Gist #{gist_id} not found. Please ensure the gist id is correct.", :red)
|
104
|
+
else
|
105
|
+
gist.files.attrs.each do |name, gist_file|
|
106
|
+
gist_path = "#{BOT_DIR}/#{gist.user.login}.#{gist_id}/#{name}"
|
107
|
+
create_file(gist_path, gist_file.content, options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def print_stats(indent = 2, &block)
|
113
|
+
self.print_table([].tap(&block), :indent => indent)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
RTanqueCLI.start
|
data/lib/rtanque.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# RTanque
|
2
|
+
#
|
3
|
+
# #Interesting classes for the spelunker:
|
4
|
+
#
|
5
|
+
# * {RTanque::Bot::Brain} All brains should inherit from this class
|
6
|
+
# * {RTanque::Bot::Sensors} Instance provided to {RTanque::Bot::Brain#sensors}
|
7
|
+
# * {RTanque::Bot::Command} Instance provided to {RTanque::Bot::Brain#command}
|
8
|
+
# * {RTanque::Heading} Handles angles
|
9
|
+
# * {RTanque::Point} Handles coordinates
|
10
|
+
module RTanque
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rtanque/version'
|
15
|
+
require 'rtanque/point'
|
16
|
+
require 'rtanque/heading'
|
17
|
+
require 'rtanque/configuration'
|
18
|
+
require 'rtanque/arena'
|
19
|
+
require 'rtanque/normalized_attr'
|
20
|
+
require 'rtanque/movable'
|
21
|
+
require 'rtanque/bot'
|
22
|
+
require 'rtanque/bot/radar'
|
23
|
+
require 'rtanque/bot/turret'
|
24
|
+
require 'rtanque/bot/sensors'
|
25
|
+
require 'rtanque/bot/command'
|
26
|
+
require 'rtanque/bot/brain'
|
27
|
+
require 'rtanque/bot/brain_helper'
|
28
|
+
require 'rtanque/explosion'
|
29
|
+
require 'rtanque/shell'
|
30
|
+
require 'rtanque/match'
|
31
|
+
require 'rtanque/match/tick_group'
|
data/lib/rtanque/bot.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
include Movable
|
4
|
+
extend NormalizedAttr
|
5
|
+
HEALTH_REDUCTION_ON_EXCEPTION = Configuration.bot.health_reduction_on_exception
|
6
|
+
RADIUS = Configuration.bot.radius
|
7
|
+
MAX_GUN_ENERGY = Configuration.bot.gun_energy_max
|
8
|
+
GUN_ENERGY_FACTOR = Configuration.bot.gun_energy_factor
|
9
|
+
attr_reader :arena, :brain, :radar, :turret, :ticks, :health, :fire_power, :gun_energy
|
10
|
+
attr_accessor :gui_window
|
11
|
+
attr_normalized(:speed, Configuration.bot.speed, Configuration.bot.speed_step)
|
12
|
+
attr_normalized(:heading, Heading::FULL_RANGE, Configuration.bot.turn_step)
|
13
|
+
attr_normalized(:fire_power, Configuration.bot.fire_power)
|
14
|
+
attr_normalized(:health, Configuration.bot.health)
|
15
|
+
|
16
|
+
def self.new_random_location(*args)
|
17
|
+
self.new(*args).tap do |bot|
|
18
|
+
rand_heading = Heading.rand
|
19
|
+
bot.position = Point.rand(bot.arena)
|
20
|
+
bot.heading = rand_heading
|
21
|
+
bot.radar.heading = rand_heading
|
22
|
+
bot.turret.heading = rand_heading
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(arena, brain_klass = Brain)
|
27
|
+
@arena = arena
|
28
|
+
@brain = brain_klass.new(self.arena)
|
29
|
+
@ticks = 0
|
30
|
+
self.health = self.class::MAX_HEALTH
|
31
|
+
self.speed = 0
|
32
|
+
self.fire_power = nil
|
33
|
+
self.heading = Heading.new
|
34
|
+
self.position = Point.new(0, 0, self.arena)
|
35
|
+
@radar = Radar.new(self, self.heading.clone)
|
36
|
+
@turret = Turret.new(self.heading.clone)
|
37
|
+
end
|
38
|
+
|
39
|
+
def name
|
40
|
+
@name ||= self.brain.class.const_defined?(:NAME) ? self.brain.class.const_get(:NAME) : [self.brain.class.name, self.object_id].join(':')
|
41
|
+
end
|
42
|
+
|
43
|
+
def health=(val)
|
44
|
+
@health = val
|
45
|
+
end
|
46
|
+
|
47
|
+
def fire_power=(power)
|
48
|
+
@fire_power = power || 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def adjust_fire_power
|
52
|
+
@gun_energy ||= MAX_GUN_ENERGY
|
53
|
+
if @gun_energy <= 0
|
54
|
+
self.fire_power = 0
|
55
|
+
else
|
56
|
+
@gun_energy -= (self.fire_power**RTanque::Shell::RATIO) * GUN_ENERGY_FACTOR
|
57
|
+
end
|
58
|
+
@gun_energy += 1
|
59
|
+
@gun_energy = MAX_GUN_ENERGY if @gun_energy > MAX_GUN_ENERGY
|
60
|
+
end
|
61
|
+
|
62
|
+
def firing?
|
63
|
+
self.fire_power && self.fire_power > 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def reduce_health(reduce_by)
|
67
|
+
self.health -= reduce_by
|
68
|
+
end
|
69
|
+
|
70
|
+
def dead?
|
71
|
+
self.health <= self.class::MIN_HEALTH
|
72
|
+
end
|
73
|
+
|
74
|
+
def tick
|
75
|
+
@ticks += 1
|
76
|
+
self.tick_brain
|
77
|
+
self.adjust_fire_power
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
def tick_brain
|
82
|
+
begin
|
83
|
+
self.execute_command(self.brain.tick(self.sensors))
|
84
|
+
rescue Exception => brain_error
|
85
|
+
if Configuration.raise_brain_tick_errors
|
86
|
+
raise brain_error
|
87
|
+
else
|
88
|
+
puts brain_error
|
89
|
+
self.reduce_health(HEALTH_REDUCTION_ON_EXCEPTION)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute_command(command)
|
95
|
+
self.fire_power = self.normalize_fire_power(self.fire_power, command.fire_power)
|
96
|
+
self.speed = self.normalize_speed(self.speed, command.speed)
|
97
|
+
self.heading = self.normalize_heading(self.heading, command.heading)
|
98
|
+
self.radar.heading = self.radar.normalize_heading(self.radar.heading, command.radar_heading)
|
99
|
+
self.turret.heading = self.turret.normalize_heading(self.turret.heading, command.turret_heading)
|
100
|
+
end
|
101
|
+
|
102
|
+
def sensors
|
103
|
+
Sensors.new do |sensors|
|
104
|
+
sensors.ticks = self.ticks
|
105
|
+
sensors.health = self.health
|
106
|
+
sensors.speed = self.speed
|
107
|
+
sensors.position = self.position
|
108
|
+
sensors.heading = self.heading
|
109
|
+
sensors.radar = self.radar.to_enum
|
110
|
+
sensors.radar_heading = self.radar.heading
|
111
|
+
sensors.gun_energy = self.gun_energy
|
112
|
+
sensors.turret_heading = self.turret.heading
|
113
|
+
sensors.gui_window = self.gui_window
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|