UG_RRobots 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rrobots +202 -0
- data/bin/tournament +413 -0
- data/config/rrobots.yml +15 -0
- data/contribs/allbots.rb +0 -0
- data/doc/manual.rdoc +126 -0
- data/doc/manual_fr.rdoc +129 -0
- data/images/explosion00.gif +0 -0
- data/images/explosion01.gif +0 -0
- data/images/explosion02.gif +0 -0
- data/images/explosion03.gif +0 -0
- data/images/explosion04.gif +0 -0
- data/images/explosion05.gif +0 -0
- data/images/explosion06.gif +0 -0
- data/images/explosion07.gif +0 -0
- data/images/explosion08.gif +0 -0
- data/images/explosion09.gif +0 -0
- data/images/explosion10.gif +0 -0
- data/images/explosion11.gif +0 -0
- data/images/explosion12.gif +0 -0
- data/images/explosion13.gif +0 -0
- data/images/explosion14.gif +0 -0
- data/images/red_body000.gif +0 -0
- data/images/red_body010.gif +0 -0
- data/images/red_body020.gif +0 -0
- data/images/red_body030.gif +0 -0
- data/images/red_body040.gif +0 -0
- data/images/red_body050.gif +0 -0
- data/images/red_body060.gif +0 -0
- data/images/red_body070.gif +0 -0
- data/images/red_body080.gif +0 -0
- data/images/red_body090.gif +0 -0
- data/images/red_body100.gif +0 -0
- data/images/red_body110.gif +0 -0
- data/images/red_body120.gif +0 -0
- data/images/red_body130.gif +0 -0
- data/images/red_body140.gif +0 -0
- data/images/red_body150.gif +0 -0
- data/images/red_body160.gif +0 -0
- data/images/red_body170.gif +0 -0
- data/images/red_body180.gif +0 -0
- data/images/red_body190.gif +0 -0
- data/images/red_body200.gif +0 -0
- data/images/red_body210.gif +0 -0
- data/images/red_body220.gif +0 -0
- data/images/red_body230.gif +0 -0
- data/images/red_body240.gif +0 -0
- data/images/red_body250.gif +0 -0
- data/images/red_body260.gif +0 -0
- data/images/red_body270.gif +0 -0
- data/images/red_body280.gif +0 -0
- data/images/red_body290.gif +0 -0
- data/images/red_body300.gif +0 -0
- data/images/red_body310.gif +0 -0
- data/images/red_body320.gif +0 -0
- data/images/red_body330.gif +0 -0
- data/images/red_body340.gif +0 -0
- data/images/red_body350.gif +0 -0
- data/images/red_radar000.gif +0 -0
- data/images/red_radar010.gif +0 -0
- data/images/red_radar020.gif +0 -0
- data/images/red_radar030.gif +0 -0
- data/images/red_radar040.gif +0 -0
- data/images/red_radar050.gif +0 -0
- data/images/red_radar060.gif +0 -0
- data/images/red_radar070.gif +0 -0
- data/images/red_radar080.gif +0 -0
- data/images/red_radar090.gif +0 -0
- data/images/red_radar100.gif +0 -0
- data/images/red_radar110.gif +0 -0
- data/images/red_radar120.gif +0 -0
- data/images/red_radar130.gif +0 -0
- data/images/red_radar140.gif +0 -0
- data/images/red_radar150.gif +0 -0
- data/images/red_radar160.gif +0 -0
- data/images/red_radar170.gif +0 -0
- data/images/red_radar180.gif +0 -0
- data/images/red_radar190.gif +0 -0
- data/images/red_radar200.gif +0 -0
- data/images/red_radar210.gif +0 -0
- data/images/red_radar220.gif +0 -0
- data/images/red_radar230.gif +0 -0
- data/images/red_radar240.gif +0 -0
- data/images/red_radar250.gif +0 -0
- data/images/red_radar260.gif +0 -0
- data/images/red_radar270.gif +0 -0
- data/images/red_radar280.gif +0 -0
- data/images/red_radar290.gif +0 -0
- data/images/red_radar300.gif +0 -0
- data/images/red_radar310.gif +0 -0
- data/images/red_radar320.gif +0 -0
- data/images/red_radar330.gif +0 -0
- data/images/red_radar340.gif +0 -0
- data/images/red_radar350.gif +0 -0
- data/images/red_turret000.gif +0 -0
- data/images/red_turret010.gif +0 -0
- data/images/red_turret020.gif +0 -0
- data/images/red_turret030.gif +0 -0
- data/images/red_turret040.gif +0 -0
- data/images/red_turret050.gif +0 -0
- data/images/red_turret060.gif +0 -0
- data/images/red_turret070.gif +0 -0
- data/images/red_turret080.gif +0 -0
- data/images/red_turret090.gif +0 -0
- data/images/red_turret100.gif +0 -0
- data/images/red_turret110.gif +0 -0
- data/images/red_turret120.gif +0 -0
- data/images/red_turret130.gif +0 -0
- data/images/red_turret140.gif +0 -0
- data/images/red_turret150.gif +0 -0
- data/images/red_turret160.gif +0 -0
- data/images/red_turret170.gif +0 -0
- data/images/red_turret180.gif +0 -0
- data/images/red_turret190.gif +0 -0
- data/images/red_turret200.gif +0 -0
- data/images/red_turret210.gif +0 -0
- data/images/red_turret220.gif +0 -0
- data/images/red_turret230.gif +0 -0
- data/images/red_turret240.gif +0 -0
- data/images/red_turret250.gif +0 -0
- data/images/red_turret260.gif +0 -0
- data/images/red_turret270.gif +0 -0
- data/images/red_turret280.gif +0 -0
- data/images/red_turret290.gif +0 -0
- data/images/red_turret300.gif +0 -0
- data/images/red_turret310.gif +0 -0
- data/images/red_turret320.gif +0 -0
- data/images/red_turret330.gif +0 -0
- data/images/red_turret340.gif +0 -0
- data/images/red_turret350.gif +0 -0
- data/images/toolbox.gif +0 -0
- data/lib/battlefield.rb +102 -0
- data/lib/bullets.rb +39 -0
- data/lib/configuration.rb +26 -0
- data/lib/explosions.rb +20 -0
- data/lib/overloads.rb +10 -0
- data/lib/robot.rb +122 -0
- data/lib/robotrunner.rb +260 -0
- data/lib/tkarena.rb +197 -0
- data/lib/toolboxes.rb +28 -0
- data/robots/BillDuck.rb +92 -0
- data/robots/BotOne.rb +39 -0
- data/robots/DuckBill.rb +384 -0
- data/robots/DuckBill04.rb +330 -0
- data/robots/DuckToEndAllDucks.rb +140 -0
- data/robots/EdgeBot.rb +203 -0
- data/robots/HuntingDuck.rb +74 -0
- data/robots/HyperactiveDuck.rb +15 -0
- data/robots/Killer.rb +58 -0
- data/robots/Kite.rb +193 -0
- data/robots/KoDuck.rb +57 -0
- data/robots/LinearShooter.rb +279 -0
- data/robots/LuckyDuck.rb +83 -0
- data/robots/MoxonoM.rb +85 -0
- data/robots/MsgBot.rb +13 -0
- data/robots/NervousDuck.rb +13 -0
- data/robots/Polisher.rb +15 -0
- data/robots/RomBot.rb +514 -0
- data/robots/RoomPainter.rb +205 -0
- data/robots/Rrrkele.rb +48 -0
- data/robots/Seeker.rb +57 -0
- data/robots/ShootingStation.rb +15 -0
- data/robots/SittingDuck.rb +18 -0
- data/robots/SniperDuck.rb +277 -0
- data/robots/WallPainter.rb +224 -0
- metadata +220 -0
data/bin/rrobots
ADDED
@@ -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
|
data/bin/tournament
ADDED
@@ -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
|
+
|