UG_RRobots 1.2

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 (165) hide show
  1. data/bin/rrobots +202 -0
  2. data/bin/tournament +413 -0
  3. data/config/rrobots.yml +15 -0
  4. data/contribs/allbots.rb +0 -0
  5. data/doc/manual.rdoc +126 -0
  6. data/doc/manual_fr.rdoc +129 -0
  7. data/images/explosion00.gif +0 -0
  8. data/images/explosion01.gif +0 -0
  9. data/images/explosion02.gif +0 -0
  10. data/images/explosion03.gif +0 -0
  11. data/images/explosion04.gif +0 -0
  12. data/images/explosion05.gif +0 -0
  13. data/images/explosion06.gif +0 -0
  14. data/images/explosion07.gif +0 -0
  15. data/images/explosion08.gif +0 -0
  16. data/images/explosion09.gif +0 -0
  17. data/images/explosion10.gif +0 -0
  18. data/images/explosion11.gif +0 -0
  19. data/images/explosion12.gif +0 -0
  20. data/images/explosion13.gif +0 -0
  21. data/images/explosion14.gif +0 -0
  22. data/images/red_body000.gif +0 -0
  23. data/images/red_body010.gif +0 -0
  24. data/images/red_body020.gif +0 -0
  25. data/images/red_body030.gif +0 -0
  26. data/images/red_body040.gif +0 -0
  27. data/images/red_body050.gif +0 -0
  28. data/images/red_body060.gif +0 -0
  29. data/images/red_body070.gif +0 -0
  30. data/images/red_body080.gif +0 -0
  31. data/images/red_body090.gif +0 -0
  32. data/images/red_body100.gif +0 -0
  33. data/images/red_body110.gif +0 -0
  34. data/images/red_body120.gif +0 -0
  35. data/images/red_body130.gif +0 -0
  36. data/images/red_body140.gif +0 -0
  37. data/images/red_body150.gif +0 -0
  38. data/images/red_body160.gif +0 -0
  39. data/images/red_body170.gif +0 -0
  40. data/images/red_body180.gif +0 -0
  41. data/images/red_body190.gif +0 -0
  42. data/images/red_body200.gif +0 -0
  43. data/images/red_body210.gif +0 -0
  44. data/images/red_body220.gif +0 -0
  45. data/images/red_body230.gif +0 -0
  46. data/images/red_body240.gif +0 -0
  47. data/images/red_body250.gif +0 -0
  48. data/images/red_body260.gif +0 -0
  49. data/images/red_body270.gif +0 -0
  50. data/images/red_body280.gif +0 -0
  51. data/images/red_body290.gif +0 -0
  52. data/images/red_body300.gif +0 -0
  53. data/images/red_body310.gif +0 -0
  54. data/images/red_body320.gif +0 -0
  55. data/images/red_body330.gif +0 -0
  56. data/images/red_body340.gif +0 -0
  57. data/images/red_body350.gif +0 -0
  58. data/images/red_radar000.gif +0 -0
  59. data/images/red_radar010.gif +0 -0
  60. data/images/red_radar020.gif +0 -0
  61. data/images/red_radar030.gif +0 -0
  62. data/images/red_radar040.gif +0 -0
  63. data/images/red_radar050.gif +0 -0
  64. data/images/red_radar060.gif +0 -0
  65. data/images/red_radar070.gif +0 -0
  66. data/images/red_radar080.gif +0 -0
  67. data/images/red_radar090.gif +0 -0
  68. data/images/red_radar100.gif +0 -0
  69. data/images/red_radar110.gif +0 -0
  70. data/images/red_radar120.gif +0 -0
  71. data/images/red_radar130.gif +0 -0
  72. data/images/red_radar140.gif +0 -0
  73. data/images/red_radar150.gif +0 -0
  74. data/images/red_radar160.gif +0 -0
  75. data/images/red_radar170.gif +0 -0
  76. data/images/red_radar180.gif +0 -0
  77. data/images/red_radar190.gif +0 -0
  78. data/images/red_radar200.gif +0 -0
  79. data/images/red_radar210.gif +0 -0
  80. data/images/red_radar220.gif +0 -0
  81. data/images/red_radar230.gif +0 -0
  82. data/images/red_radar240.gif +0 -0
  83. data/images/red_radar250.gif +0 -0
  84. data/images/red_radar260.gif +0 -0
  85. data/images/red_radar270.gif +0 -0
  86. data/images/red_radar280.gif +0 -0
  87. data/images/red_radar290.gif +0 -0
  88. data/images/red_radar300.gif +0 -0
  89. data/images/red_radar310.gif +0 -0
  90. data/images/red_radar320.gif +0 -0
  91. data/images/red_radar330.gif +0 -0
  92. data/images/red_radar340.gif +0 -0
  93. data/images/red_radar350.gif +0 -0
  94. data/images/red_turret000.gif +0 -0
  95. data/images/red_turret010.gif +0 -0
  96. data/images/red_turret020.gif +0 -0
  97. data/images/red_turret030.gif +0 -0
  98. data/images/red_turret040.gif +0 -0
  99. data/images/red_turret050.gif +0 -0
  100. data/images/red_turret060.gif +0 -0
  101. data/images/red_turret070.gif +0 -0
  102. data/images/red_turret080.gif +0 -0
  103. data/images/red_turret090.gif +0 -0
  104. data/images/red_turret100.gif +0 -0
  105. data/images/red_turret110.gif +0 -0
  106. data/images/red_turret120.gif +0 -0
  107. data/images/red_turret130.gif +0 -0
  108. data/images/red_turret140.gif +0 -0
  109. data/images/red_turret150.gif +0 -0
  110. data/images/red_turret160.gif +0 -0
  111. data/images/red_turret170.gif +0 -0
  112. data/images/red_turret180.gif +0 -0
  113. data/images/red_turret190.gif +0 -0
  114. data/images/red_turret200.gif +0 -0
  115. data/images/red_turret210.gif +0 -0
  116. data/images/red_turret220.gif +0 -0
  117. data/images/red_turret230.gif +0 -0
  118. data/images/red_turret240.gif +0 -0
  119. data/images/red_turret250.gif +0 -0
  120. data/images/red_turret260.gif +0 -0
  121. data/images/red_turret270.gif +0 -0
  122. data/images/red_turret280.gif +0 -0
  123. data/images/red_turret290.gif +0 -0
  124. data/images/red_turret300.gif +0 -0
  125. data/images/red_turret310.gif +0 -0
  126. data/images/red_turret320.gif +0 -0
  127. data/images/red_turret330.gif +0 -0
  128. data/images/red_turret340.gif +0 -0
  129. data/images/red_turret350.gif +0 -0
  130. data/images/toolbox.gif +0 -0
  131. data/lib/battlefield.rb +102 -0
  132. data/lib/bullets.rb +39 -0
  133. data/lib/configuration.rb +26 -0
  134. data/lib/explosions.rb +20 -0
  135. data/lib/overloads.rb +10 -0
  136. data/lib/robot.rb +122 -0
  137. data/lib/robotrunner.rb +260 -0
  138. data/lib/tkarena.rb +197 -0
  139. data/lib/toolboxes.rb +28 -0
  140. data/robots/BillDuck.rb +92 -0
  141. data/robots/BotOne.rb +39 -0
  142. data/robots/DuckBill.rb +384 -0
  143. data/robots/DuckBill04.rb +330 -0
  144. data/robots/DuckToEndAllDucks.rb +140 -0
  145. data/robots/EdgeBot.rb +203 -0
  146. data/robots/HuntingDuck.rb +74 -0
  147. data/robots/HyperactiveDuck.rb +15 -0
  148. data/robots/Killer.rb +58 -0
  149. data/robots/Kite.rb +193 -0
  150. data/robots/KoDuck.rb +57 -0
  151. data/robots/LinearShooter.rb +279 -0
  152. data/robots/LuckyDuck.rb +83 -0
  153. data/robots/MoxonoM.rb +85 -0
  154. data/robots/MsgBot.rb +13 -0
  155. data/robots/NervousDuck.rb +13 -0
  156. data/robots/Polisher.rb +15 -0
  157. data/robots/RomBot.rb +514 -0
  158. data/robots/RoomPainter.rb +205 -0
  159. data/robots/Rrrkele.rb +48 -0
  160. data/robots/Seeker.rb +57 -0
  161. data/robots/ShootingStation.rb +15 -0
  162. data/robots/SittingDuck.rb +18 -0
  163. data/robots/SniperDuck.rb +277 -0
  164. data/robots/WallPainter.rb +224 -0
  165. metadata +220 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'robot'
4
+ require 'yaml'
5
+ require 'overloads'
6
+ require 'configuration'
7
+ require 'battlefield'
8
+ require 'toolboxes'
9
+ require 'explosions'
10
+ require 'bullets'
11
+
12
+ ##############################################
13
+ # arena
14
+ ##############################################
15
+
16
+ def usage
17
+ puts "usage: rrobots.rb [resolution] [#match] [-nogui] [-speed=<N>] [-timeout=<N>] [-teams=<N>] [-with-toolboxes] <FirstRobotClassName[.rb]> <SecondRobotClassName[.rb]> <...>"
18
+ puts "\t[resolution] (optional) should be of the form 640x480 or 800*600. default is 800x800"
19
+ puts "\t[match] (optional) to replay a match, put the match# here, including the #sign. "
20
+ puts "\t[-nogui] (optional) run the match without the gui, for highest possible speed.(ignores speed value if present)"
21
+ puts "\t[-speed=<N>] (optional, defaults to 1) updates GUI after every N ticks. The higher the N, the faster the match will play."
22
+ puts "\t[-timeout=<N>] (optional, default 50000) number of ticks a match will last at most."
23
+ puts "\t[-teams=<N>] (optional) split robots into N teams. Match ends when only one team has robots left."
24
+ puts "\t[-with-toolboxes] (optional) to accept the spawning of healing toolboxes (randomly)."
25
+ puts "\t[-ignore-config] (optional) to ignore the config file present in the path."
26
+ puts "\t[-write-config] (optional) to write the config in a file in the path."
27
+ puts "\tthe names of the rb files have to match the class names of the robots"
28
+ puts "\t(up to 8 robots)"
29
+ puts "\te.g. 'ruby rrobots.rb SittingDuck NervousDuck'"
30
+ puts "\t or 'ruby rrobots.rb 600x600 #1234567890 SittingDuck NervousDuck'"
31
+ exit
32
+ end
33
+
34
+ def run_out_of_gui(battlefield)
35
+ $stderr.puts 'match ends if only 1 bot/team left or dots get here-->|'
36
+
37
+ until battlefield.game_over
38
+ battlefield.tick
39
+ $stderr.print "." if battlefield.time % (battlefield.timeout / 54).to_i == 0
40
+ end
41
+ print_outcome(battlefield)
42
+ exit 0
43
+ end
44
+
45
+ def run_in_gui(battlefield, xres, yres, speed_multiplier)
46
+ require 'tkarena'
47
+ arena = TkArena.new(battlefield, xres, yres, speed_multiplier)
48
+ game_over_counter = battlefield.teams.all?{|k,t| t.size < 2} ? 250 : 500
49
+ outcome_printed = false
50
+ arena.on_game_over{|battlefield|
51
+ unless outcome_printed
52
+ print_outcome(battlefield)
53
+ outcome_printed = true
54
+ end
55
+ exit 0 if game_over_counter < 0
56
+ game_over_counter -= 1
57
+ }
58
+ arena.run
59
+ end
60
+
61
+ def print_outcome(battlefield)
62
+ winners = battlefield.robots.find_all{|robot| !robot.dead}
63
+ puts
64
+ if battlefield.robots.size > battlefield.teams.size
65
+ teams = battlefield.teams.find_all{|name,team| !team.all?{|robot| robot.dead} }
66
+ puts "winner_is: { #{
67
+ teams.map do |name,team|
68
+ "Team #{name}: [#{team.join(', ')}]"
69
+ end.join(', ')
70
+ } }"
71
+ puts "winner_energy: { #{
72
+ teams.map do |name,team|
73
+ "Team #{name}: [#{team.map do |w| ('%.1f' % w.energy) end.join(', ')}]"
74
+ end.join(', ')
75
+ } }"
76
+ else
77
+ puts "winner_is: [#{winners.map{|w|w.name}.join(', ')}]"
78
+ puts "winner_energy: [#{winners.map{|w|'%.1f' % w.energy}.join(', ')}]"
79
+ end
80
+ puts "elapsed_ticks: #{battlefield.time}"
81
+ puts "seed : #{battlefield.seed}"
82
+ puts
83
+ puts "robots :"
84
+ battlefield.robots.each do |robot|
85
+ puts " #{robot.name}:"
86
+ puts " damage_given: #{'%.1f' % robot.damage_given}"
87
+ if battlefield.with_toolboxes
88
+ puts " damage_taken: #{'%.1f' % (100 + (robot.catched_toolboxes*20) - robot.energy)}"
89
+ else
90
+ puts " damage_taken: #{'%.1f' % (100 - robot.energy)}"
91
+ end
92
+ puts " kills: #{robot.kills}"
93
+ puts " toolboxes catched: #{robot.catched_toolboxes}" if battlefield.with_toolboxes
94
+ end
95
+ end
96
+
97
+ $stdout.sync = true
98
+
99
+ # look for resolution arg
100
+ xres, yres = 800, 800
101
+ ARGV.grep(/^(\d+)[x\*](\d+$)/) do |item|
102
+ xres, yres = $1.to_i, $2.to_i
103
+ ARGV.delete(item)
104
+ end
105
+
106
+ # look for match arg
107
+ seed = Time.now.to_i + Process.pid
108
+ ARGV.grep(/^#(\d+)/) do |item|
109
+ seed = $1.to_i
110
+ ARGV.delete(item)
111
+ end
112
+
113
+ #look for with_toolboxes arg
114
+ with_toolboxes = false
115
+ ARGV.grep( /^(-with-toolboxes)/ ) do |item|
116
+ with_toolboxes = true
117
+ ARGV.delete(item)
118
+ end
119
+
120
+
121
+ #look for mode arg
122
+ mode = :run_in_gui
123
+ ARGV.grep( /^(-nogui)/ )do |item|
124
+ mode = :run_out_of_gui
125
+ ARGV.delete(item)
126
+ end
127
+
128
+ #look for config arg
129
+ merge = true
130
+ ARGV.grep( /^(-ignore-config)/ )do |item|
131
+ merge = false
132
+ ARGV.delete(item)
133
+ end
134
+
135
+ #look for config write arg
136
+ write_config = false
137
+ ARGV.grep( /^(-write-config)/ )do |item|
138
+ write_config = true
139
+ ARGV.delete(item)
140
+ end
141
+
142
+ #look for speed multiplier arg
143
+ speed_multiplier = 1
144
+ ARGV.grep( /^-speed=(\d\d?)/ )do |item|
145
+ x = $1.to_i
146
+ speed_multiplier = x if x > 0 && x < 100
147
+ ARGV.delete(item)
148
+ end
149
+
150
+ #look for timeout arg
151
+ timeout = 50000
152
+ ARGV.grep( /^-timeout=(\d+)/ )do |item|
153
+ timeout = $1.to_i
154
+ ARGV.delete(item)
155
+ end
156
+
157
+
158
+
159
+ #look for teams arg
160
+ team_count = 8
161
+ ARGV.grep( /^-teams=(\d)/ )do |item|
162
+ x = $1.to_i
163
+ team_count = x if x > 0 && x < 8
164
+ ARGV.delete(item)
165
+ end
166
+ teams = Array.new([team_count, ARGV.size].min){ [] }
167
+
168
+ usage if ARGV.size > 8 || ARGV.empty?
169
+
170
+ battlefield = Battlefield.new xres*2, yres*2, timeout, seed, with_toolboxes, merge, write_config
171
+
172
+ c = 0
173
+ team_divider = (ARGV.size / teams.size.to_f).ceil
174
+ ARGV.map! do |robot|
175
+ begin
176
+ begin
177
+ require robot.downcase
178
+ rescue LoadError
179
+ end
180
+ begin
181
+ require robot
182
+ rescue LoadError
183
+ end
184
+ in_game_name = File.basename(robot).sub(/\..*$/, '')
185
+ in_game_name[0] = in_game_name[0,1].upcase
186
+ team = c / team_divider
187
+ c += 1
188
+ robotrunner = RobotRunner.new(Object.const_get(in_game_name).new, battlefield, team)
189
+ battlefield << robotrunner
190
+ rescue Exception => error
191
+ puts 'Error loading ' + robot + '!'
192
+ warn error
193
+ usage
194
+ end
195
+ in_game_name
196
+ end
197
+
198
+ if mode == :run_out_of_gui
199
+ run_out_of_gui(battlefield)
200
+ else
201
+ run_in_gui(battlefield, xres, yres, speed_multiplier)
202
+ end
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ class Match
6
+ attr_reader :bots
7
+ attr_reader :seed
8
+ attr_reader :match
9
+
10
+ def initialize(data)
11
+ @bots = data['robots']
12
+ @seed = data['seed']
13
+ @ticks= data['elapsed_ticks']
14
+ @timedout = data['timedout']
15
+ @match = data['match']
16
+ end
17
+
18
+ def winner
19
+ sorted = @bots.sort{|a,b| a[1]['damage_given'] <=> b[1]['damage_given']}
20
+ return sorted[1][0]
21
+ end
22
+
23
+ def loser
24
+ sorted = @bots.sort{|a,b| a[1]['damage_given'] <=> b[1]['damage_given']}
25
+ return sorted[0][0]
26
+ end
27
+
28
+ def tie?
29
+ return margin == 0.0
30
+ end
31
+
32
+ def margin
33
+ @bots[winner]['damage_given'] - @bots[loser]['damage_given']
34
+ end
35
+
36
+ def winner_points
37
+ return 0.5 if simul?
38
+ return winner_health / (winner_health + loser_health)
39
+ end
40
+
41
+ def loser_points
42
+ return 0.5 if simul?
43
+ return loser_health / (winner_health + loser_health)
44
+ end
45
+
46
+ def simul?
47
+ winner_health + loser_health == 0
48
+ end
49
+
50
+ def timedout?
51
+ @timedout == 1
52
+ end
53
+
54
+ #between 100 and 0
55
+ def winner_health
56
+ [100 - @bots[winner]['damage_taken'], 0].max
57
+ end
58
+
59
+ #between 100 and 0
60
+ def loser_health
61
+ [100 - @bots[loser]['damage_taken'], 0].max
62
+ end
63
+
64
+ def one_line_summary
65
+ if !tie?
66
+ line = "#{winner} beats #{loser} by #{'%.1f' % margin} energy in #{@ticks} ticks"
67
+ if @timedout then line += " (match timed out, so #{winner} gets #{winner_points}, loser gets #{loser_points})" end
68
+ else
69
+ line = "#{winner} ties #{loser} at #{'%.1f' % winner_health} energy in #{@ticks} ticks"
70
+ if @timedout then line += " (match timed out.)" end
71
+ end
72
+ line += " (timed out)" if @timeout
73
+ return line
74
+ end
75
+ end
76
+
77
+
78
+ class Round
79
+ attr_accessor :matches
80
+ attr_accessor :winner
81
+ #attr_accessor :total_margin
82
+ attr_reader :bots
83
+
84
+ # matches should be an array of Matches
85
+ def initialize (matches)
86
+ @matches = matches
87
+ @bots = Hash.new {|h,key| h[key] = {}}
88
+
89
+ both_bots = [@bots[@matches[0].winner], @bots[@matches[0].loser]]
90
+ stats_to_init = ['wins', 'points', 'ties', 'margin', 'simul', 'timedout', 'round_wins']
91
+ stats_to_init.each {|stat| both_bots.each {|b| b[stat] = 0 }}
92
+
93
+ @matches.each do |match|
94
+ @bots[match.winner]['points'] += match.winner_points
95
+ @bots[match.loser]['points'] += match.loser_points
96
+ both_bots.each {|b| b['ties'] += 1 if match.tie?}
97
+ both_bots.each {|b| b['timedout'] += 1 if match.timedout?}
98
+ both_bots.each {|b| b['simul'] += 1 if match.simul?}
99
+ @bots[match.winner]['margin'] += match.margin
100
+ if match.tie?
101
+ both_bots.each {|b| b['wins'] += 0.5}
102
+ else
103
+ @bots[match.winner]['wins'] += 1
104
+ end
105
+ if both_bots[0]['wins'] > both_bots[1]['wins'] then both_bots[0]['round_wins'] = 1 end
106
+ if both_bots[1]['wins'] > both_bots[0]['wins'] then both_bots[1]['round_wins'] = 1 end
107
+ if both_bots[1]['wins'] == both_bots[0]['wins'] then both_bots[0]['round_wins'] = 0.5 ;both_bots[1]['round_wins'] = 0.5 end
108
+ end
109
+ end
110
+
111
+ def winner
112
+ sorted = @bots.sort{|a,b| a[1]['wins'] <=> b[1]['wins']}
113
+ return sorted[1][0]
114
+ end
115
+
116
+ def loser
117
+ sorted = @bots.sort{|a,b| a[1]['wins'] <=> b[1]['wins']}
118
+ return sorted[0][0]
119
+ end
120
+
121
+ def tie?
122
+ @bots[winner]['wins'] == @bots[loser]['wins']
123
+ end
124
+ #calc how many points for losing bot
125
+
126
+ def one_line_summary
127
+ if !tie?
128
+ line = "#{winner} conquers #{loser} (#{@bots[winner]['wins']} to #{@bots[loser]['wins']} )"
129
+ else
130
+ line = "#{winner} ties #{loser} (#{@bots[winner]['wins']} to #{@bots[loser]['wins']} )"
131
+ end
132
+ end
133
+ end
134
+
135
+ def print_header(html)
136
+ header = '''
137
+ <html>
138
+ <head> <title>RRobots Tournament Results </title>
139
+ <style type="text/css">
140
+ <!--
141
+ body, p{font-family: verdana, arial, helvetica, sans-serif; font-size: 12px;}
142
+ table{background-color:white; border:none; padding:0px; font-size: 12px;}
143
+ tr{}
144
+ td {
145
+ background-color:#efefef;
146
+ border-left:white 1px solid;
147
+ border-top:white 1px solid;
148
+ border-right:white 1px solid;
149
+ border-bottom:white 1px solid;
150
+ margin:0px;
151
+ padding:4px;
152
+ }
153
+ td.sum {font-weight:bold;}
154
+ td.blank {background-color:white}
155
+ -->
156
+ </style>
157
+ </head>
158
+ <body>
159
+ '''
160
+ html.print(header)
161
+ end
162
+
163
+ def print_footer(html)
164
+ footer = '''
165
+ </body>
166
+ </html>
167
+ '''
168
+ html.print(footer)
169
+ end
170
+
171
+ def rank_by_round_stat(stat, rounds)
172
+ bots = Hash.new {|h,k| h[k] = 0}
173
+ rounds.each do |round|
174
+ bots[round.winner] += round.bots[round.winner][stat]
175
+ bots[round.loser] += round.bots[round.loser][stat]
176
+ end
177
+ sorted = bots.sort {|a,b| b[1] <=> a[1]}
178
+ return sorted
179
+ end
180
+
181
+ # print a html page with a table containing once cell per round
182
+ # include stat in that cell. if r is the round in question
183
+ # then r.bots[current_bot][stat] had better exist!
184
+ def print_round_table(stat, rounds, ranking, round_tables, html)
185
+ #ranking is an array of 2 element arrays - we just want the first element in the 'sub' array,
186
+ # which is the robot name
187
+ rows = ranking.collect {|a| a[0]}
188
+ cols = rows.clone
189
+
190
+ html.puts "<hr style='width: 100%; height: 2px;'>"
191
+ html.puts"<a name='#{stat}'></a>"
192
+ html.puts "<h1>#{stat}</h1>"
193
+ html.puts "<table>"
194
+ html.puts "<th>"
195
+ cols.each {|col| html.puts "<td> #{col[0..2]} </td>"}
196
+ html.puts "<td class='sum'>Sum</td></th>"
197
+ rows.each do |row|
198
+ html.puts " <tr>"
199
+ html.puts " <td> #{row} </td> "
200
+ row_total = 0
201
+ cols.each do |col|
202
+ round = nil
203
+ if row != col
204
+ round = rounds.find {|x| x.bots.has_key?(row) and x.bots.has_key?(col)}
205
+ if round == nil then puts "couldn't find round bewtween #{row} and #{col}" end
206
+ html.puts " <td> #{ '%.1f'% round.bots[row][stat] } </td>"
207
+ row_total += round.bots[row][stat]
208
+ else
209
+ html.puts" <td> --- </td>"
210
+ end
211
+ end
212
+ html.puts "<td class='sum'> #{'%.1f'% row_total}</td>"
213
+ html.puts " </tr>"
214
+ end
215
+ html.puts "</table>"
216
+ end
217
+
218
+ def print_index(round_tables, rankings, stats, html)
219
+ html.puts"<a name='top'></a>"
220
+ html.puts "<h1> Round Summary Tables </h1>"
221
+ html.puts "<ul>"
222
+
223
+ round_tables.each { |t| html.puts "<li><a href='##{t}'>#{t}</a></li>" }
224
+
225
+ html.puts "</ul>"
226
+ html.puts "<h1> Rankings</h1>"
227
+ html.puts "<table><tr>"
228
+
229
+ stats.each do |stat|
230
+ html.puts "<td>#{stat}<table>"
231
+ list = rankings[stat]
232
+ list.each {|row| html.puts "<tr><td>#{row[0]}</td> <td> #{'%.1f'%row[1]}</td></tr>"}
233
+
234
+ html.puts "</table></td>"
235
+ end
236
+
237
+ html.puts "</tr></table>"
238
+
239
+ html.puts <<ENDHTML
240
+ <h1>Definitions:</h1>
241
+ <ul>
242
+ <li> Wins: The numer of matches (battles) won by a robot. (ties or timeouts earn both bots 0.5)</li>
243
+ <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>
244
+ <li> Round_wins: Number of rounds won by a robot. Indicates how many robots this robot can beat. Ties earn 0.5 </li>
245
+ <li> Margin: The margin of one match is how much energy the winning robot won by</li>
246
+ <li> Simul: Number of times a robot was involved in Simultaneous deaths</li>
247
+ <li> Timedout: Number of times a robot was in a match that timed out</li>
248
+ <li> Ties: Number of matches a robot was in that ended in a tie </li>
249
+ </ul>
250
+ ENDHTML
251
+ print_footer(html)
252
+ end
253
+
254
+ def to_html match_data, html
255
+ print_header(html)
256
+
257
+ round_matches = Hash.new {|h,k| h[k] = []}
258
+ robots = []
259
+
260
+ match_data.each do |m|
261
+ match = Match.new(m)
262
+ round_matches[m['round']] << match
263
+ robots << match.winner
264
+ robots << match.loser
265
+ end
266
+ robots = robots.uniq
267
+
268
+ rounds = []
269
+ round_matches.values.each do |matches|
270
+ r = Round.new(matches)
271
+ rounds << r
272
+ end
273
+
274
+ stats = ['wins', 'points','round_wins', 'margin', 'simul', 'timedout', 'ties']
275
+ rankings = {}
276
+ stats.each{|stat| rankings[stat] = rank_by_round_stat(stat, rounds)}
277
+
278
+ print_index(stats, rankings, stats, html) #pass stats array in insted of using rankings.keys so we can preserve order
279
+ stats.each {|stat| print_round_table(stat, rounds, rankings[stat],stats, html) }
280
+ print_footer(html)
281
+ html.close
282
+ end
283
+
284
+ ####################################################################
285
+ # Main
286
+ ####################################################################
287
+
288
+ def usage
289
+ puts "Usage: ruby tournament.rb [-timeout=<N>] [-matches=<N>] [-with-toolboxes] (-dir=<Directory> | <RobotClassName[.rb]>+)"
290
+ puts "\t[-with-toolboxes] (optional) to accept the spawning of healing toolboxes (randomly)."
291
+ puts "\t[-ignore-config] (optional) to ignore the config file present in the path."
292
+ puts "\t[-timeout=<N>] (optional, default 10000) number of ticks a match will last at most."
293
+ puts "\t[-matches=<N>] (optional, default 2) how many times each robot fights every other robot."
294
+ puts "\t-dir=<Directory> All .rb files from that directory will be matched against each other."
295
+ puts "\t[-ignore-config] (optional) to ignore the config file present in the path."
296
+ puts "\tthe names of the rb files have to match the class names of the robots."
297
+ exit(0)
298
+ end
299
+
300
+ #look for timeout arg
301
+ timeout = 10000
302
+ ARGV.grep( /^-timeout=(\d+)/ )do |item|
303
+ timeout = $1.to_i
304
+ ARGV.delete(item)
305
+ end
306
+
307
+ matches_per_round = 2
308
+ ARGV.grep( /^-matches=(\d+)/ )do |item|
309
+ matches_per_round = $1.to_i
310
+ ARGV.delete(item)
311
+ end
312
+
313
+ #look for with_toolboxes arg
314
+ with_toolboxes = false
315
+ ARGV.grep( /^(-with-toolboxes)/ ) do |item|
316
+ with_toolboxes = true
317
+ ARGV.delete(item)
318
+ end
319
+
320
+ #look for config arg
321
+ merge = true
322
+ ARGV.grep( /^(-ignore-config)/ )do |item|
323
+ merge = false
324
+ ARGV.delete(item)
325
+ end
326
+
327
+
328
+ folder = '.'
329
+ robots = ARGV
330
+ ARGV.grep( /^-dir=(.+)$/ )do |item|
331
+ begin
332
+ folder = $1
333
+ pwd = Dir.pwd
334
+ Dir.chdir(folder)
335
+ robots = Dir.glob('*.rb')
336
+ Dir.chdir(pwd)
337
+ ARGV.delete(item)
338
+ rescue Exception
339
+ puts "can't change directory to '#{folder}'"
340
+ exit(0)
341
+ end
342
+ end
343
+
344
+ ARGV.grep( /^-(.*)$/ )do |item|
345
+ puts "Unknown option '-#{$1}'"
346
+ puts
347
+ usage
348
+ end
349
+
350
+ usage if robots.length <= 1
351
+ $stdout.sync = true
352
+
353
+ suffix=0
354
+ while File.exists?('tournament' + suffix.to_s.rjust(2, '0') + '.yml')
355
+ suffix = suffix + 1
356
+ end
357
+ results_file = 'tournament' + suffix.to_s.rjust(2, '0') + ".yml"
358
+
359
+ rounds = []
360
+ round_number = 0
361
+ opponents = robots.clone
362
+ @all_match_data = []
363
+ robots.each do |bot1|
364
+
365
+ opponents.delete bot1
366
+ opponents.each do |bot2|
367
+ round_number += 1
368
+ puts
369
+ puts "===== Round #{round_number}: #{bot1} vs #{bot2} ====="
370
+ i = 1
371
+ matches = []
372
+ matches_per_round.times do |i|
373
+ puts "- Match #{i+1} of #{matches_per_round} -"
374
+ cmd = "rrobots -nogui -timeout=#{timeout} "
375
+ cmd += "-with-toolboxes " if with_toolboxes
376
+ cmd += "-ignore-config" if merge
377
+ cmd += "#{folder}/#{bot1} #{folder}/#{bot2}"
378
+
379
+ # using popen instead of `cmd` lets us see the progress dots (......) output as the match progresses.
380
+ sub = IO.popen(cmd)
381
+ results_string = sub.read
382
+ sub.close
383
+
384
+ match_data = YAML.load(results_string)
385
+ match_data['match'] = i
386
+ match_data['round'] = round_number
387
+ match_data['timedout'] = 1 if timeout <= match_data['elapsed_ticks']
388
+ @all_match_data << match_data
389
+
390
+ match = Match.new(match_data)
391
+ puts
392
+ puts match.one_line_summary
393
+ matches << match
394
+ end
395
+ rounds << Round.new(matches)
396
+ end
397
+ end
398
+
399
+ # write out match results to a file for possible later analysis
400
+ File.open(results_file, 'w').puts(YAML::dump(@all_match_data))
401
+ to_html(@all_match_data, File.open(results_file.sub(/\..*$/, '.html'), 'w'))
402
+
403
+ puts
404
+ puts
405
+ puts "####################################################################"
406
+ puts " TOURNAMENT SUMMARY"
407
+ puts "####################################################################"
408
+ round_results = []
409
+ rounds.each {|round| round_results << round.one_line_summary}
410
+ round_results = round_results.sort
411
+ round_results.each {|r| puts r}
412
+
413
+