rrobots 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+