UG_RRobots 1.2

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