rrobots 0.0.1

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.
Files changed (55) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/README.rdoc +93 -0
  4. data/Rakefile +1 -0
  5. data/VERSION +1 -0
  6. data/bin/rrobots +154 -0
  7. data/bin/tournament +275 -0
  8. data/lib/rrobots.rb +6 -0
  9. data/lib/rrobots/battlefield.rb +69 -0
  10. data/lib/rrobots/bullet.rb +39 -0
  11. data/lib/rrobots/explosion.rb +20 -0
  12. data/lib/rrobots/gui.rb +2 -0
  13. data/lib/rrobots/gui/gosuarena.rb +124 -0
  14. data/lib/rrobots/gui/leaderboard.rb +19 -0
  15. data/lib/rrobots/images/blue_body000.bmp +0 -0
  16. data/lib/rrobots/images/blue_radar000.bmp +0 -0
  17. data/lib/rrobots/images/blue_turret000.bmp +0 -0
  18. data/lib/rrobots/images/bullet.png +0 -0
  19. data/lib/rrobots/images/explosion00.bmp +0 -0
  20. data/lib/rrobots/images/explosion01.bmp +0 -0
  21. data/lib/rrobots/images/explosion02.bmp +0 -0
  22. data/lib/rrobots/images/explosion03.bmp +0 -0
  23. data/lib/rrobots/images/explosion04.bmp +0 -0
  24. data/lib/rrobots/images/explosion05.bmp +0 -0
  25. data/lib/rrobots/images/explosion06.bmp +0 -0
  26. data/lib/rrobots/images/explosion07.bmp +0 -0
  27. data/lib/rrobots/images/explosion08.bmp +0 -0
  28. data/lib/rrobots/images/explosion09.bmp +0 -0
  29. data/lib/rrobots/images/explosion10.bmp +0 -0
  30. data/lib/rrobots/images/explosion11.bmp +0 -0
  31. data/lib/rrobots/images/explosion12.bmp +0 -0
  32. data/lib/rrobots/images/explosion13.bmp +0 -0
  33. data/lib/rrobots/images/explosion14.bmp +0 -0
  34. data/lib/rrobots/images/lime_body000.bmp +0 -0
  35. data/lib/rrobots/images/lime_radar000.bmp +0 -0
  36. data/lib/rrobots/images/lime_turret000.bmp +0 -0
  37. data/lib/rrobots/images/red_body000.bmp +0 -0
  38. data/lib/rrobots/images/red_radar000.bmp +0 -0
  39. data/lib/rrobots/images/red_turret000.bmp +0 -0
  40. data/lib/rrobots/images/space.png +0 -0
  41. data/lib/rrobots/images/white_body000.bmp +0 -0
  42. data/lib/rrobots/images/white_radar000.bmp +0 -0
  43. data/lib/rrobots/images/white_turret000.bmp +0 -0
  44. data/lib/rrobots/images/yellow_body000.bmp +0 -0
  45. data/lib/rrobots/images/yellow_radar000.bmp +0 -0
  46. data/lib/rrobots/images/yellow_turret000.bmp +0 -0
  47. data/lib/rrobots/numeric.rb +10 -0
  48. data/lib/rrobots/robot.rb +115 -0
  49. data/lib/rrobots/robot_runner.rb +237 -0
  50. data/lib/rrobots/tournament.rb +2 -0
  51. data/lib/rrobots/tournament/match.rb +71 -0
  52. data/lib/rrobots/tournament/round.rb +56 -0
  53. data/lib/rrobots/version.rb +3 -0
  54. data/rrobots.gemspec +25 -0
  55. metadata +113 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rrobots.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,93 @@
1
+ = RRobots
2
+ RRobots is a simulation environment for robots, these robots have a scanner
3
+ and a gun, can move forward and backwards and are entirely controlled by
4
+ ruby scripts.
5
+
6
+ == Example script
7
+
8
+ require 'rrobots'
9
+ class NervousDuck
10
+ include Robot
11
+
12
+ def tick events
13
+ turn_radar 1 if time == 0
14
+ turn_gun 30 if time < 3
15
+ accelerate 1
16
+ turn 2
17
+ fire 3 unless events['robot_scanned'].empty?
18
+ end
19
+ end
20
+
21
+ All you need to implement is the tick method which should accept a hash of events that occurred during the last tick.
22
+
23
+ The following methods are available to your bot:
24
+
25
+ battlefield_height #the height of the battlefield
26
+ battlefield_width #the width of the battlefield
27
+ energy #your remaining energy (if this drops below 0 you are dead)
28
+ gun_heading #the heading of your gun, 0 pointing east, 90 pointing
29
+ #north, 180 pointing west, 270 pointing south
30
+ gun_heat #your gun heat, if this is above 0 you can't shoot
31
+ heading #your robots heading, 0 pointing east, 90 pointing north,
32
+ #180 pointing west, 270 pointing south
33
+ size #your robots radius, if x <= size you hit the left wall
34
+ radar_heading #the heading of your radar, 0 pointing east,
35
+ #90 pointing north, 180 pointing west, 270 pointing south
36
+ time #ticks since match start
37
+ speed #your speed (-8/8)
38
+ x #your x coordinate, 0...battlefield_width
39
+ y #your y coordinate, 0...battlefield_height
40
+ accelerate(param) #accelerate (max speed is 8, max accelerate is 1/-1,
41
+ #negativ speed means moving backwards)
42
+ stop #accelerates negativ if moving forward (and vice versa),
43
+ #may take 8 ticks to stop (and you have to call it every tick)
44
+ fire(power) #fires a bullet in the direction of your gun,
45
+ #power is 0.1 - 3, this power will heat your gun
46
+ turn(degrees) #turns the robot (and the gun and the radar),
47
+ #max 10 degrees per tick
48
+ turn_gun(degrees) #turns the gun (and the radar), max 30 degrees per tick
49
+ turn_radar(degrees) #turns the radar, max 60 degrees per tick
50
+ dead #true if you are dead
51
+ say(msg) #shows msg above the robot on screen
52
+ broadcast(msg) #broadcasts msg to all bots (they receive 'broadcasts'
53
+ #events with the msg and rough direction)
54
+
55
+ These methods are intentionally of very basic nature, you are free to
56
+ unleash the whole power of ruby to create higher level functions.
57
+ (e.g. move_to, fire_at and so on)
58
+
59
+ Some words of explanation: The gun is mounted on the body, if you turn
60
+ the body the gun will follow. In a similar way the radar is mounted on
61
+ the gun. The radar scans everything it sweeps over in a single tick (100
62
+ degrees if you turn your body, gun and radar in the same direction) but
63
+ will report only the distance of scanned robots, not the angle. If you
64
+ want more precision you have to turn your radar slower.
65
+
66
+ RRobots is implemented in pure ruby using a Gosu ui and should run on all
67
+ platforms that have ruby and Gosu.
68
+
69
+ == Usage
70
+
71
+ To start a match call:
72
+
73
+ Usage: rrobots [options] file1 file2 ...
74
+ --resolution x,y X and Y resolution
75
+ --match N Replay match number N
76
+ --timeout N Maximum number of ticks for a match
77
+ --teams N Splits robots into N teams
78
+ --[no-]gui Run the match with the GUI
79
+ -h, --help Show this message
80
+
81
+ If you want to run a tournament call:
82
+
83
+ Usage: tournament [options] file1 file2 ...
84
+ --resolution x,y X and Y resolution
85
+ --matches N Number of times each robot fights each other robot N
86
+ --timeout N Maximum number of ticks for a match
87
+ --dir N All robots from this directory will be matched against each other
88
+ -h, --help Show this message
89
+
90
+ The names of the rb files have to match the class names of the robots.
91
+
92
+ Each robot is matched against each other 1on1. The results are available
93
+ as yaml or html files.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bin/rrobots ADDED
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rrobots'
3
+ require 'optparse'
4
+ require 'ostruct'
5
+
6
+ class OptionsParser
7
+ def self.parse!(args)
8
+ options = OpenStruct.new
9
+ options.resolution = [800,800]
10
+ options.match = Time.now.to_i + Process.pid
11
+ options.gui = true
12
+ options.timeout = 50000
13
+ options.teams = 8
14
+
15
+ opts = OptionParser.new do |opts|
16
+ opts.banner = "Usage: rrobots [options] file1 file2 ..."
17
+
18
+ opts.on("--resolution x,y", Array, "X and Y resolution") do |resolution|
19
+ options.resolution = resolution.map &:to_i
20
+ end
21
+
22
+ opts.on("--match N", Integer, "Replay match number N") do |n|
23
+ options.match = n
24
+ end
25
+
26
+ opts.on("--timeout N", Integer, "Maximum number of ticks for a match") do |n|
27
+ options.timeout = n
28
+ end
29
+
30
+ opts.on("--teams N", Integer, "Splits robots into N teams") do |n|
31
+ options.teams = n if n > 0 && n < 8
32
+ end
33
+
34
+ opts.on("--[no-]gui", "Run the match with the GUI") do |y|
35
+ options.gui = y
36
+ end
37
+
38
+ opts.on_tail("-h", "--help", "Show this message") do
39
+ puts opts
40
+ exit
41
+ end
42
+ end
43
+ opts.parse!(args)
44
+ if ARGV.size == 0
45
+ puts opts
46
+ exit
47
+ end
48
+ return options
49
+ end
50
+ end
51
+
52
+ def run_out_of_gui(battlefield)
53
+ $stderr.puts 'match ends if only 1 bot/team left or dots get here-->|'
54
+
55
+ until battlefield.game_over
56
+ battlefield.tick
57
+ $stderr.print "." if battlefield.time % (battlefield.timeout / 54).to_i == 0
58
+ end
59
+ print_outcome(battlefield)
60
+ exit 0
61
+ end
62
+
63
+ def run_in_gui(battlefield, xres, yres)
64
+ require 'rrobots/gui'
65
+ arena = RRobotsGameWindow.new(battlefield, xres, yres)
66
+ game_over_counter = battlefield.teams.all?{|k,t| t.size < 2} ? 250 : 500
67
+ outcome_printed = false
68
+ arena.on_game_over{|battlefield|
69
+ unless outcome_printed
70
+ print_outcome(battlefield)
71
+ outcome_printed = true
72
+ end
73
+ if game_over_counter < 0
74
+ arena.close
75
+ exit 0
76
+ end
77
+ game_over_counter -= 1
78
+ }
79
+ arena.show
80
+ end
81
+
82
+ def print_outcome(battlefield)
83
+ winners = battlefield.robots.find_all{|robot| !robot.dead}
84
+ puts
85
+ if battlefield.robots.size > battlefield.teams.size
86
+ teams = battlefield.teams.find_all{|name,team| !team.all?{|robot| robot.dead} }
87
+ puts "winner_is: { #{
88
+ teams.map do |name,team|
89
+ "Team #{name}: [#{team.join(', ')}]"
90
+ end.join(', ')
91
+ } }"
92
+ puts "winner_energy: { #{
93
+ teams.map do |name,team|
94
+ "Team #{name}: [#{team.map do |w| ('%.1f' % w.energy) end.join(', ')}]"
95
+ end.join(', ')
96
+ } }"
97
+ else
98
+ puts "winner_is: [#{winners.map{|w|w.name}.join(', ')}]"
99
+ puts "winner_energy: [#{winners.map{|w|'%.1f' % w.energy}.join(', ')}]"
100
+ end
101
+ puts "elapsed_ticks: #{battlefield.time}"
102
+ puts "seed : #{battlefield.seed}"
103
+ puts
104
+ puts "robots :"
105
+ battlefield.robots.each do |robot|
106
+ puts " #{robot.name}:"
107
+ puts " damage_given: #{'%.1f' % robot.damage_given}"
108
+ puts " damage_taken: #{'%.1f' % (100 - robot.energy)}"
109
+ puts " kills: #{robot.kills}"
110
+ end
111
+ end
112
+
113
+ def setup_battlefield(options, robots)
114
+ teams = Array.new([options.teams, robots.size].min){ [] }
115
+ battlefield = Battlefield.new options.resolution[0]*2, options.resolution[1]*2, options.timeout, options.match
116
+
117
+ c = 0
118
+ team_divider = (robots.size / teams.size.to_f).ceil
119
+ robots.map! do |robot|
120
+ begin
121
+ begin
122
+ require "./"+robot.downcase
123
+ rescue LoadError
124
+ end
125
+ begin
126
+ require "./"+robot
127
+ rescue LoadError
128
+ end
129
+ in_game_name = File.basename(robot).sub(/\..*$/, '')
130
+ in_game_name[0] = in_game_name[0,1].upcase
131
+ team = c / team_divider
132
+ c += 1
133
+ robotrunner = RobotRunner.new(Object.const_get(in_game_name).new, battlefield, team)
134
+ battlefield << robotrunner
135
+ rescue Exception => error
136
+ puts 'Error loading ' + robot + '!'
137
+ warn error
138
+ end
139
+ in_game_name
140
+ end
141
+ battlefield
142
+ end
143
+
144
+ $stdout.sync = true
145
+
146
+ options = OptionsParser.parse!(ARGV)
147
+ robots = ARGV
148
+ battlefield = setup_battlefield(options, robots)
149
+
150
+ if options.gui
151
+ run_in_gui(battlefield, options.resolution[0], options.resolution[1])
152
+ else
153
+ run_out_of_gui(battlefield)
154
+ end
data/bin/tournament ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "YAML"
4
+ require 'optparse'
5
+ require 'ostruct'
6
+ require 'rrobots/tournament'
7
+
8
+
9
+ class OptionsParser
10
+ def self.parse!(args)
11
+ options = OpenStruct.new
12
+ options.resolution = [800,800]
13
+ options.matches = 2
14
+ options.timeout = 10000
15
+
16
+ opts = OptionParser.new do |opts|
17
+ opts.banner = "Usage: tournament [options] file1 file2 ..."
18
+
19
+ opts.on("--resolution x,y", Array, "X and Y resolution") do |resolution|
20
+ options.resolution = resolution.map &:to_i
21
+ end
22
+
23
+ opts.on("--matches N", Integer, "Number of times each robot fights each other robot N") do |n|
24
+ options.matches = n
25
+ end
26
+
27
+ opts.on("--timeout N", Integer, "Maximum number of ticks for a match") do |n|
28
+ options.timeout = n
29
+ end
30
+
31
+ opts.on("--dir N", String, "All robots from this directory will be matched against each other") do |n|
32
+ options.directory = n
33
+ end
34
+
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ puts opts
37
+ exit
38
+ end
39
+ end
40
+ opts.parse!(args)
41
+ if ARGV.size == 0 and options.directory.nil?
42
+ puts opts
43
+ exit
44
+ end
45
+ return options
46
+ end
47
+ end
48
+
49
+
50
+
51
+ def print_header(html)
52
+ header = '''
53
+ <html>
54
+ <head> <title>RRobots Tournament Results </title>
55
+ <style type="text/css">
56
+ <!--
57
+ body, p{font-family: verdana, arial, helvetica, sans-serif; font-size: 12px;}
58
+ table{background-color:white; border:none; padding:0px; font-size: 12px;}
59
+ tr{}
60
+ td {
61
+ background-color:#efefef;
62
+ border-left:white 1px solid;
63
+ border-top:white 1px solid;
64
+ border-right:white 1px solid;
65
+ border-bottom:white 1px solid;
66
+ margin:0px;
67
+ padding:4px;
68
+ }
69
+ td.sum {font-weight:bold;}
70
+ td.blank {background-color:white}
71
+ -->
72
+ </style>
73
+ </head>
74
+ <body>
75
+ '''
76
+ html.print(header)
77
+ end
78
+
79
+ def print_footer(html)
80
+ footer = '''
81
+ </body>
82
+ </html>
83
+ '''
84
+ html.print(footer)
85
+ end
86
+
87
+ def rank_by_round_stat(stat, rounds)
88
+ bots = Hash.new {|h,k| h[k] = 0}
89
+ rounds.each do |round|
90
+ bots[round.winner] += round.bots[round.winner][stat]
91
+ bots[round.loser] += round.bots[round.loser][stat]
92
+ end
93
+ sorted = bots.sort {|a,b| b[1] <=> a[1]}
94
+ return sorted
95
+ end
96
+
97
+ # print a html page with a table containing once cell per round
98
+ # include stat in that cell. if r is the round in question
99
+ # then r.bots[current_bot][stat] had better exist!
100
+ def print_round_table(stat, rounds, ranking, round_tables, html)
101
+ #ranking is an array of 2 element arrays - we just want the first element in the 'sub' array,
102
+ # which is the robot name
103
+ rows = ranking.collect {|a| a[0]}
104
+ cols = rows.clone
105
+
106
+ html.puts "<hr style='width: 100%; height: 2px;'>"
107
+ html.puts"<a name='#{stat}'></a>"
108
+ html.puts "<h1>#{stat}</h1>"
109
+ html.puts "<table>"
110
+ html.puts "<th>"
111
+ cols.each {|col| html.puts "<td> #{col[0..2]} </td>"}
112
+ html.puts "<td class='sum'>Sum</td></th>"
113
+ rows.each do |row|
114
+ html.puts " <tr>"
115
+ html.puts " <td> #{row} </td> "
116
+ row_total = 0
117
+ cols.each do |col|
118
+ round = nil
119
+ if row != col
120
+ round = rounds.find {|x| x.bots.has_key?(row) and x.bots.has_key?(col)}
121
+ if round == nil then puts "couldn't find round bewtween #{row} and #{col}" end
122
+ html.puts " <td> #{ '%.1f'% round.bots[row][stat] } </td>"
123
+ row_total += round.bots[row][stat]
124
+ else
125
+ html.puts" <td> --- </td>"
126
+ end
127
+ end
128
+ html.puts "<td class='sum'> #{'%.1f'% row_total}</td>"
129
+ html.puts " </tr>"
130
+ end
131
+ html.puts "</table>"
132
+ end
133
+
134
+ def print_index(round_tables, rankings, stats, html)
135
+ html.puts"<a name='top'></a>"
136
+ html.puts "<h1> Round Summary Tables </h1>"
137
+ html.puts "<ul>"
138
+
139
+ round_tables.each { |t| html.puts "<li><a href='##{t}'>#{t}</a></li>" }
140
+
141
+ html.puts "</ul>"
142
+ html.puts "<h1> Rankings</h1>"
143
+ html.puts "<table><tr>"
144
+
145
+ stats.each do |stat|
146
+ html.puts "<td>#{stat}<table>"
147
+ list = rankings[stat]
148
+ list.each {|row| html.puts "<tr><td>#{row[0]}</td> <td> #{'%.1f'%row[1]}</td></tr>"}
149
+
150
+ html.puts "</table></td>"
151
+ end
152
+
153
+ html.puts "</tr></table>"
154
+
155
+ html.puts """
156
+ <h1>Definitions:</h1>
157
+ <ul>
158
+ <li> Wins: The numer of matches (battles) won by a robot. (ties or timeouts earn both bots 0.5)</li>
159
+ <li> Points: Similar to wins, but timeouts earn bots points proportional to their amount of health. There is a chance this doesn't work properly yet</li>
160
+ <li> Round_wins: Number of rounds won by a robot. Indicates how many robots this robot can beat. Ties earn 0.5 </li>
161
+ <li> Margin: The margin of one match is how much energy the winning robot won by</li>
162
+ <li> Simul: Number of times a robot was involved in Simultaneous deaths</li>
163
+ <li> Timedout: Number of times a robot was in a match that timed out</li>
164
+ <li> Ties: Number of matches a robot was in that ended in a tie </li>
165
+ </ul>
166
+ """
167
+ print_footer(html)
168
+ end
169
+
170
+ def to_html match_data, html
171
+ print_header(html)
172
+
173
+ round_matches = Hash.new {|h,k| h[k] = []}
174
+ robots = []
175
+
176
+ match_data.each do |m|
177
+ match = Match.new(m)
178
+ round_matches[m['round']] << match
179
+ robots << match.winner
180
+ robots << match.loser
181
+ end
182
+ robots = robots.uniq
183
+
184
+ rounds = []
185
+ round_matches.values.each do |matches|
186
+ r = Round.new(matches)
187
+ rounds << r
188
+ end
189
+
190
+ stats = ['wins', 'points','round_wins', 'margin', 'simul', 'timedout', 'ties']
191
+ rankings = {}
192
+ stats.each{|stat| rankings[stat] = rank_by_round_stat(stat, rounds)}
193
+
194
+ print_index(stats, rankings, stats, html) #pass stats array in instead of using rankings.keys so we can preserve order
195
+ stats.each {|stat| print_round_table(stat, rounds, rankings[stat],stats, html) }
196
+ print_footer(html)
197
+ html.close
198
+ end
199
+
200
+ ####################################################################
201
+ # Main
202
+ ####################################################################
203
+
204
+ $stdout.sync = true
205
+
206
+ options = OptionsParser.parse!(ARGV)
207
+ robots = ARGV
208
+
209
+ if(!options.directory.nil?)
210
+ pwd = Dir.pwd
211
+ Dir.chdir(options.directory)
212
+ robots = Dir.glob('*.rb')
213
+ Dir.chdir(pwd)
214
+ else
215
+ options.directory = '.'
216
+ end
217
+
218
+ suffix=0
219
+ while File.exists?('tournament' + suffix.to_s.rjust(2, '0') + '.yml')
220
+ suffix = suffix + 1
221
+ end
222
+ results_file = 'tournament' + suffix.to_s.rjust(2, '0') + ".yml"
223
+
224
+ rounds = []
225
+ round_number = 0
226
+ opponents = robots.clone
227
+ @all_match_data = []
228
+ robots.each do |bot1|
229
+
230
+ opponents.delete bot1
231
+ opponents.each do |bot2|
232
+ round_number += 1
233
+ puts
234
+ puts "===== Round #{round_number}: #{bot1} vs #{bot2} ====="
235
+ i = 1
236
+ matches = []
237
+ options.matches.times do |i|
238
+ puts "- Match #{i+1} of #{options.matches} -"
239
+ cmd = "rrobots --no-gui --timeout #{options.timeout} #{options.directory}/#{bot1} #{options.directory}/#{bot2}"
240
+
241
+ # using popen instead of `cmd` lets us see the progress dots (......) output as the match progresses.
242
+ sub = IO.popen(cmd)
243
+ results_string = sub.read
244
+ sub.close
245
+
246
+ match_data = YAML.load(results_string)
247
+ match_data['match'] = i
248
+ match_data['round'] = round_number
249
+ match_data['timedout'] = 1 if options.timeout <= match_data['elapsed_ticks']
250
+ @all_match_data << match_data
251
+
252
+ match = Match.new(match_data)
253
+ puts
254
+ puts match.one_line_summary
255
+ matches << match
256
+ end
257
+ rounds << Round.new(matches)
258
+ end
259
+ end
260
+
261
+ # write out match results to a file for possible later analysis
262
+ File.open(results_file, 'w').puts(YAML::dump(@all_match_data))
263
+ to_html(@all_match_data, File.open(results_file.sub(/\..*$/, '.html'), 'w'))
264
+
265
+ puts
266
+ puts
267
+ puts "####################################################################"
268
+ puts " TOURNAMENT SUMMARY"
269
+ puts "####################################################################"
270
+ round_results = []
271
+ rounds.each {|round| round_results << round.one_line_summary}
272
+ round_results = round_results.sort
273
+ round_results.each {|r| puts r}
274
+
275
+