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.
- 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
|
+
|