rrobots 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +93 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/rrobots +154 -0
- data/bin/tournament +275 -0
- data/lib/rrobots.rb +6 -0
- data/lib/rrobots/battlefield.rb +69 -0
- data/lib/rrobots/bullet.rb +39 -0
- data/lib/rrobots/explosion.rb +20 -0
- data/lib/rrobots/gui.rb +2 -0
- data/lib/rrobots/gui/gosuarena.rb +124 -0
- data/lib/rrobots/gui/leaderboard.rb +19 -0
- data/lib/rrobots/images/blue_body000.bmp +0 -0
- data/lib/rrobots/images/blue_radar000.bmp +0 -0
- data/lib/rrobots/images/blue_turret000.bmp +0 -0
- data/lib/rrobots/images/bullet.png +0 -0
- data/lib/rrobots/images/explosion00.bmp +0 -0
- data/lib/rrobots/images/explosion01.bmp +0 -0
- data/lib/rrobots/images/explosion02.bmp +0 -0
- data/lib/rrobots/images/explosion03.bmp +0 -0
- data/lib/rrobots/images/explosion04.bmp +0 -0
- data/lib/rrobots/images/explosion05.bmp +0 -0
- data/lib/rrobots/images/explosion06.bmp +0 -0
- data/lib/rrobots/images/explosion07.bmp +0 -0
- data/lib/rrobots/images/explosion08.bmp +0 -0
- data/lib/rrobots/images/explosion09.bmp +0 -0
- data/lib/rrobots/images/explosion10.bmp +0 -0
- data/lib/rrobots/images/explosion11.bmp +0 -0
- data/lib/rrobots/images/explosion12.bmp +0 -0
- data/lib/rrobots/images/explosion13.bmp +0 -0
- data/lib/rrobots/images/explosion14.bmp +0 -0
- data/lib/rrobots/images/lime_body000.bmp +0 -0
- data/lib/rrobots/images/lime_radar000.bmp +0 -0
- data/lib/rrobots/images/lime_turret000.bmp +0 -0
- data/lib/rrobots/images/red_body000.bmp +0 -0
- data/lib/rrobots/images/red_radar000.bmp +0 -0
- data/lib/rrobots/images/red_turret000.bmp +0 -0
- data/lib/rrobots/images/space.png +0 -0
- data/lib/rrobots/images/white_body000.bmp +0 -0
- data/lib/rrobots/images/white_radar000.bmp +0 -0
- data/lib/rrobots/images/white_turret000.bmp +0 -0
- data/lib/rrobots/images/yellow_body000.bmp +0 -0
- data/lib/rrobots/images/yellow_radar000.bmp +0 -0
- data/lib/rrobots/images/yellow_turret000.bmp +0 -0
- data/lib/rrobots/numeric.rb +10 -0
- data/lib/rrobots/robot.rb +115 -0
- data/lib/rrobots/robot_runner.rb +237 -0
- data/lib/rrobots/tournament.rb +2 -0
- data/lib/rrobots/tournament/match.rb +71 -0
- data/lib/rrobots/tournament/round.rb +56 -0
- data/lib/rrobots/version.rb +3 -0
- data/rrobots.gemspec +25 -0
- metadata +113 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= RRobots
|
2
|
+
RRobots is a simulation environment for robots, these robots have a scanner
|
3
|
+
and a gun, can move forward and backwards and are entirely controlled by
|
4
|
+
ruby scripts.
|
5
|
+
|
6
|
+
== Example script
|
7
|
+
|
8
|
+
require 'rrobots'
|
9
|
+
class NervousDuck
|
10
|
+
include Robot
|
11
|
+
|
12
|
+
def tick events
|
13
|
+
turn_radar 1 if time == 0
|
14
|
+
turn_gun 30 if time < 3
|
15
|
+
accelerate 1
|
16
|
+
turn 2
|
17
|
+
fire 3 unless events['robot_scanned'].empty?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
All you need to implement is the tick method which should accept a hash of events that occurred during the last tick.
|
22
|
+
|
23
|
+
The following methods are available to your bot:
|
24
|
+
|
25
|
+
battlefield_height #the height of the battlefield
|
26
|
+
battlefield_width #the width of the battlefield
|
27
|
+
energy #your remaining energy (if this drops below 0 you are dead)
|
28
|
+
gun_heading #the heading of your gun, 0 pointing east, 90 pointing
|
29
|
+
#north, 180 pointing west, 270 pointing south
|
30
|
+
gun_heat #your gun heat, if this is above 0 you can't shoot
|
31
|
+
heading #your robots heading, 0 pointing east, 90 pointing north,
|
32
|
+
#180 pointing west, 270 pointing south
|
33
|
+
size #your robots radius, if x <= size you hit the left wall
|
34
|
+
radar_heading #the heading of your radar, 0 pointing east,
|
35
|
+
#90 pointing north, 180 pointing west, 270 pointing south
|
36
|
+
time #ticks since match start
|
37
|
+
speed #your speed (-8/8)
|
38
|
+
x #your x coordinate, 0...battlefield_width
|
39
|
+
y #your y coordinate, 0...battlefield_height
|
40
|
+
accelerate(param) #accelerate (max speed is 8, max accelerate is 1/-1,
|
41
|
+
#negativ speed means moving backwards)
|
42
|
+
stop #accelerates negativ if moving forward (and vice versa),
|
43
|
+
#may take 8 ticks to stop (and you have to call it every tick)
|
44
|
+
fire(power) #fires a bullet in the direction of your gun,
|
45
|
+
#power is 0.1 - 3, this power will heat your gun
|
46
|
+
turn(degrees) #turns the robot (and the gun and the radar),
|
47
|
+
#max 10 degrees per tick
|
48
|
+
turn_gun(degrees) #turns the gun (and the radar), max 30 degrees per tick
|
49
|
+
turn_radar(degrees) #turns the radar, max 60 degrees per tick
|
50
|
+
dead #true if you are dead
|
51
|
+
say(msg) #shows msg above the robot on screen
|
52
|
+
broadcast(msg) #broadcasts msg to all bots (they receive 'broadcasts'
|
53
|
+
#events with the msg and rough direction)
|
54
|
+
|
55
|
+
These methods are intentionally of very basic nature, you are free to
|
56
|
+
unleash the whole power of ruby to create higher level functions.
|
57
|
+
(e.g. move_to, fire_at and so on)
|
58
|
+
|
59
|
+
Some words of explanation: The gun is mounted on the body, if you turn
|
60
|
+
the body the gun will follow. In a similar way the radar is mounted on
|
61
|
+
the gun. The radar scans everything it sweeps over in a single tick (100
|
62
|
+
degrees if you turn your body, gun and radar in the same direction) but
|
63
|
+
will report only the distance of scanned robots, not the angle. If you
|
64
|
+
want more precision you have to turn your radar slower.
|
65
|
+
|
66
|
+
RRobots is implemented in pure ruby using a Gosu ui and should run on all
|
67
|
+
platforms that have ruby and Gosu.
|
68
|
+
|
69
|
+
== Usage
|
70
|
+
|
71
|
+
To start a match call:
|
72
|
+
|
73
|
+
Usage: rrobots [options] file1 file2 ...
|
74
|
+
--resolution x,y X and Y resolution
|
75
|
+
--match N Replay match number N
|
76
|
+
--timeout N Maximum number of ticks for a match
|
77
|
+
--teams N Splits robots into N teams
|
78
|
+
--[no-]gui Run the match with the GUI
|
79
|
+
-h, --help Show this message
|
80
|
+
|
81
|
+
If you want to run a tournament call:
|
82
|
+
|
83
|
+
Usage: tournament [options] file1 file2 ...
|
84
|
+
--resolution x,y X and Y resolution
|
85
|
+
--matches N Number of times each robot fights each other robot N
|
86
|
+
--timeout N Maximum number of ticks for a match
|
87
|
+
--dir N All robots from this directory will be matched against each other
|
88
|
+
-h, --help Show this message
|
89
|
+
|
90
|
+
The names of the rb files have to match the class names of the robots.
|
91
|
+
|
92
|
+
Each robot is matched against each other 1on1. The results are available
|
93
|
+
as yaml or html files.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/bin/rrobots
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rrobots'
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
class OptionsParser
|
7
|
+
def self.parse!(args)
|
8
|
+
options = OpenStruct.new
|
9
|
+
options.resolution = [800,800]
|
10
|
+
options.match = Time.now.to_i + Process.pid
|
11
|
+
options.gui = true
|
12
|
+
options.timeout = 50000
|
13
|
+
options.teams = 8
|
14
|
+
|
15
|
+
opts = OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: rrobots [options] file1 file2 ..."
|
17
|
+
|
18
|
+
opts.on("--resolution x,y", Array, "X and Y resolution") do |resolution|
|
19
|
+
options.resolution = resolution.map &:to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("--match N", Integer, "Replay match number N") do |n|
|
23
|
+
options.match = n
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("--timeout N", Integer, "Maximum number of ticks for a match") do |n|
|
27
|
+
options.timeout = n
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("--teams N", Integer, "Splits robots into N teams") do |n|
|
31
|
+
options.teams = n if n > 0 && n < 8
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("--[no-]gui", "Run the match with the GUI") do |y|
|
35
|
+
options.gui = y
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
opts.parse!(args)
|
44
|
+
if ARGV.size == 0
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
return options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_out_of_gui(battlefield)
|
53
|
+
$stderr.puts 'match ends if only 1 bot/team left or dots get here-->|'
|
54
|
+
|
55
|
+
until battlefield.game_over
|
56
|
+
battlefield.tick
|
57
|
+
$stderr.print "." if battlefield.time % (battlefield.timeout / 54).to_i == 0
|
58
|
+
end
|
59
|
+
print_outcome(battlefield)
|
60
|
+
exit 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_in_gui(battlefield, xres, yres)
|
64
|
+
require 'rrobots/gui'
|
65
|
+
arena = RRobotsGameWindow.new(battlefield, xres, yres)
|
66
|
+
game_over_counter = battlefield.teams.all?{|k,t| t.size < 2} ? 250 : 500
|
67
|
+
outcome_printed = false
|
68
|
+
arena.on_game_over{|battlefield|
|
69
|
+
unless outcome_printed
|
70
|
+
print_outcome(battlefield)
|
71
|
+
outcome_printed = true
|
72
|
+
end
|
73
|
+
if game_over_counter < 0
|
74
|
+
arena.close
|
75
|
+
exit 0
|
76
|
+
end
|
77
|
+
game_over_counter -= 1
|
78
|
+
}
|
79
|
+
arena.show
|
80
|
+
end
|
81
|
+
|
82
|
+
def print_outcome(battlefield)
|
83
|
+
winners = battlefield.robots.find_all{|robot| !robot.dead}
|
84
|
+
puts
|
85
|
+
if battlefield.robots.size > battlefield.teams.size
|
86
|
+
teams = battlefield.teams.find_all{|name,team| !team.all?{|robot| robot.dead} }
|
87
|
+
puts "winner_is: { #{
|
88
|
+
teams.map do |name,team|
|
89
|
+
"Team #{name}: [#{team.join(', ')}]"
|
90
|
+
end.join(', ')
|
91
|
+
} }"
|
92
|
+
puts "winner_energy: { #{
|
93
|
+
teams.map do |name,team|
|
94
|
+
"Team #{name}: [#{team.map do |w| ('%.1f' % w.energy) end.join(', ')}]"
|
95
|
+
end.join(', ')
|
96
|
+
} }"
|
97
|
+
else
|
98
|
+
puts "winner_is: [#{winners.map{|w|w.name}.join(', ')}]"
|
99
|
+
puts "winner_energy: [#{winners.map{|w|'%.1f' % w.energy}.join(', ')}]"
|
100
|
+
end
|
101
|
+
puts "elapsed_ticks: #{battlefield.time}"
|
102
|
+
puts "seed : #{battlefield.seed}"
|
103
|
+
puts
|
104
|
+
puts "robots :"
|
105
|
+
battlefield.robots.each do |robot|
|
106
|
+
puts " #{robot.name}:"
|
107
|
+
puts " damage_given: #{'%.1f' % robot.damage_given}"
|
108
|
+
puts " damage_taken: #{'%.1f' % (100 - robot.energy)}"
|
109
|
+
puts " kills: #{robot.kills}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def setup_battlefield(options, robots)
|
114
|
+
teams = Array.new([options.teams, robots.size].min){ [] }
|
115
|
+
battlefield = Battlefield.new options.resolution[0]*2, options.resolution[1]*2, options.timeout, options.match
|
116
|
+
|
117
|
+
c = 0
|
118
|
+
team_divider = (robots.size / teams.size.to_f).ceil
|
119
|
+
robots.map! do |robot|
|
120
|
+
begin
|
121
|
+
begin
|
122
|
+
require "./"+robot.downcase
|
123
|
+
rescue LoadError
|
124
|
+
end
|
125
|
+
begin
|
126
|
+
require "./"+robot
|
127
|
+
rescue LoadError
|
128
|
+
end
|
129
|
+
in_game_name = File.basename(robot).sub(/\..*$/, '')
|
130
|
+
in_game_name[0] = in_game_name[0,1].upcase
|
131
|
+
team = c / team_divider
|
132
|
+
c += 1
|
133
|
+
robotrunner = RobotRunner.new(Object.const_get(in_game_name).new, battlefield, team)
|
134
|
+
battlefield << robotrunner
|
135
|
+
rescue Exception => error
|
136
|
+
puts 'Error loading ' + robot + '!'
|
137
|
+
warn error
|
138
|
+
end
|
139
|
+
in_game_name
|
140
|
+
end
|
141
|
+
battlefield
|
142
|
+
end
|
143
|
+
|
144
|
+
$stdout.sync = true
|
145
|
+
|
146
|
+
options = OptionsParser.parse!(ARGV)
|
147
|
+
robots = ARGV
|
148
|
+
battlefield = setup_battlefield(options, robots)
|
149
|
+
|
150
|
+
if options.gui
|
151
|
+
run_in_gui(battlefield, options.resolution[0], options.resolution[1])
|
152
|
+
else
|
153
|
+
run_out_of_gui(battlefield)
|
154
|
+
end
|
data/bin/tournament
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "YAML"
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'rrobots/tournament'
|
7
|
+
|
8
|
+
|
9
|
+
class OptionsParser
|
10
|
+
def self.parse!(args)
|
11
|
+
options = OpenStruct.new
|
12
|
+
options.resolution = [800,800]
|
13
|
+
options.matches = 2
|
14
|
+
options.timeout = 10000
|
15
|
+
|
16
|
+
opts = OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: tournament [options] file1 file2 ..."
|
18
|
+
|
19
|
+
opts.on("--resolution x,y", Array, "X and Y resolution") do |resolution|
|
20
|
+
options.resolution = resolution.map &:to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("--matches N", Integer, "Number of times each robot fights each other robot N") do |n|
|
24
|
+
options.matches = n
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--timeout N", Integer, "Maximum number of ticks for a match") do |n|
|
28
|
+
options.timeout = n
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("--dir N", String, "All robots from this directory will be matched against each other") do |n|
|
32
|
+
options.directory = n
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
36
|
+
puts opts
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
end
|
40
|
+
opts.parse!(args)
|
41
|
+
if ARGV.size == 0 and options.directory.nil?
|
42
|
+
puts opts
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
return options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
def print_header(html)
|
52
|
+
header = '''
|
53
|
+
<html>
|
54
|
+
<head> <title>RRobots Tournament Results </title>
|
55
|
+
<style type="text/css">
|
56
|
+
<!--
|
57
|
+
body, p{font-family: verdana, arial, helvetica, sans-serif; font-size: 12px;}
|
58
|
+
table{background-color:white; border:none; padding:0px; font-size: 12px;}
|
59
|
+
tr{}
|
60
|
+
td {
|
61
|
+
background-color:#efefef;
|
62
|
+
border-left:white 1px solid;
|
63
|
+
border-top:white 1px solid;
|
64
|
+
border-right:white 1px solid;
|
65
|
+
border-bottom:white 1px solid;
|
66
|
+
margin:0px;
|
67
|
+
padding:4px;
|
68
|
+
}
|
69
|
+
td.sum {font-weight:bold;}
|
70
|
+
td.blank {background-color:white}
|
71
|
+
-->
|
72
|
+
</style>
|
73
|
+
</head>
|
74
|
+
<body>
|
75
|
+
'''
|
76
|
+
html.print(header)
|
77
|
+
end
|
78
|
+
|
79
|
+
def print_footer(html)
|
80
|
+
footer = '''
|
81
|
+
</body>
|
82
|
+
</html>
|
83
|
+
'''
|
84
|
+
html.print(footer)
|
85
|
+
end
|
86
|
+
|
87
|
+
def rank_by_round_stat(stat, rounds)
|
88
|
+
bots = Hash.new {|h,k| h[k] = 0}
|
89
|
+
rounds.each do |round|
|
90
|
+
bots[round.winner] += round.bots[round.winner][stat]
|
91
|
+
bots[round.loser] += round.bots[round.loser][stat]
|
92
|
+
end
|
93
|
+
sorted = bots.sort {|a,b| b[1] <=> a[1]}
|
94
|
+
return sorted
|
95
|
+
end
|
96
|
+
|
97
|
+
# print a html page with a table containing once cell per round
|
98
|
+
# include stat in that cell. if r is the round in question
|
99
|
+
# then r.bots[current_bot][stat] had better exist!
|
100
|
+
def print_round_table(stat, rounds, ranking, round_tables, html)
|
101
|
+
#ranking is an array of 2 element arrays - we just want the first element in the 'sub' array,
|
102
|
+
# which is the robot name
|
103
|
+
rows = ranking.collect {|a| a[0]}
|
104
|
+
cols = rows.clone
|
105
|
+
|
106
|
+
html.puts "<hr style='width: 100%; height: 2px;'>"
|
107
|
+
html.puts"<a name='#{stat}'></a>"
|
108
|
+
html.puts "<h1>#{stat}</h1>"
|
109
|
+
html.puts "<table>"
|
110
|
+
html.puts "<th>"
|
111
|
+
cols.each {|col| html.puts "<td> #{col[0..2]} </td>"}
|
112
|
+
html.puts "<td class='sum'>Sum</td></th>"
|
113
|
+
rows.each do |row|
|
114
|
+
html.puts " <tr>"
|
115
|
+
html.puts " <td> #{row} </td> "
|
116
|
+
row_total = 0
|
117
|
+
cols.each do |col|
|
118
|
+
round = nil
|
119
|
+
if row != col
|
120
|
+
round = rounds.find {|x| x.bots.has_key?(row) and x.bots.has_key?(col)}
|
121
|
+
if round == nil then puts "couldn't find round bewtween #{row} and #{col}" end
|
122
|
+
html.puts " <td> #{ '%.1f'% round.bots[row][stat] } </td>"
|
123
|
+
row_total += round.bots[row][stat]
|
124
|
+
else
|
125
|
+
html.puts" <td> --- </td>"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
html.puts "<td class='sum'> #{'%.1f'% row_total}</td>"
|
129
|
+
html.puts " </tr>"
|
130
|
+
end
|
131
|
+
html.puts "</table>"
|
132
|
+
end
|
133
|
+
|
134
|
+
def print_index(round_tables, rankings, stats, html)
|
135
|
+
html.puts"<a name='top'></a>"
|
136
|
+
html.puts "<h1> Round Summary Tables </h1>"
|
137
|
+
html.puts "<ul>"
|
138
|
+
|
139
|
+
round_tables.each { |t| html.puts "<li><a href='##{t}'>#{t}</a></li>" }
|
140
|
+
|
141
|
+
html.puts "</ul>"
|
142
|
+
html.puts "<h1> Rankings</h1>"
|
143
|
+
html.puts "<table><tr>"
|
144
|
+
|
145
|
+
stats.each do |stat|
|
146
|
+
html.puts "<td>#{stat}<table>"
|
147
|
+
list = rankings[stat]
|
148
|
+
list.each {|row| html.puts "<tr><td>#{row[0]}</td> <td> #{'%.1f'%row[1]}</td></tr>"}
|
149
|
+
|
150
|
+
html.puts "</table></td>"
|
151
|
+
end
|
152
|
+
|
153
|
+
html.puts "</tr></table>"
|
154
|
+
|
155
|
+
html.puts """
|
156
|
+
<h1>Definitions:</h1>
|
157
|
+
<ul>
|
158
|
+
<li> Wins: The numer of matches (battles) won by a robot. (ties or timeouts earn both bots 0.5)</li>
|
159
|
+
<li> Points: Similar to wins, but timeouts earn bots points proportional to their amount of health. There is a chance this doesn't work properly yet</li>
|
160
|
+
<li> Round_wins: Number of rounds won by a robot. Indicates how many robots this robot can beat. Ties earn 0.5 </li>
|
161
|
+
<li> Margin: The margin of one match is how much energy the winning robot won by</li>
|
162
|
+
<li> Simul: Number of times a robot was involved in Simultaneous deaths</li>
|
163
|
+
<li> Timedout: Number of times a robot was in a match that timed out</li>
|
164
|
+
<li> Ties: Number of matches a robot was in that ended in a tie </li>
|
165
|
+
</ul>
|
166
|
+
"""
|
167
|
+
print_footer(html)
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_html match_data, html
|
171
|
+
print_header(html)
|
172
|
+
|
173
|
+
round_matches = Hash.new {|h,k| h[k] = []}
|
174
|
+
robots = []
|
175
|
+
|
176
|
+
match_data.each do |m|
|
177
|
+
match = Match.new(m)
|
178
|
+
round_matches[m['round']] << match
|
179
|
+
robots << match.winner
|
180
|
+
robots << match.loser
|
181
|
+
end
|
182
|
+
robots = robots.uniq
|
183
|
+
|
184
|
+
rounds = []
|
185
|
+
round_matches.values.each do |matches|
|
186
|
+
r = Round.new(matches)
|
187
|
+
rounds << r
|
188
|
+
end
|
189
|
+
|
190
|
+
stats = ['wins', 'points','round_wins', 'margin', 'simul', 'timedout', 'ties']
|
191
|
+
rankings = {}
|
192
|
+
stats.each{|stat| rankings[stat] = rank_by_round_stat(stat, rounds)}
|
193
|
+
|
194
|
+
print_index(stats, rankings, stats, html) #pass stats array in instead of using rankings.keys so we can preserve order
|
195
|
+
stats.each {|stat| print_round_table(stat, rounds, rankings[stat],stats, html) }
|
196
|
+
print_footer(html)
|
197
|
+
html.close
|
198
|
+
end
|
199
|
+
|
200
|
+
####################################################################
|
201
|
+
# Main
|
202
|
+
####################################################################
|
203
|
+
|
204
|
+
$stdout.sync = true
|
205
|
+
|
206
|
+
options = OptionsParser.parse!(ARGV)
|
207
|
+
robots = ARGV
|
208
|
+
|
209
|
+
if(!options.directory.nil?)
|
210
|
+
pwd = Dir.pwd
|
211
|
+
Dir.chdir(options.directory)
|
212
|
+
robots = Dir.glob('*.rb')
|
213
|
+
Dir.chdir(pwd)
|
214
|
+
else
|
215
|
+
options.directory = '.'
|
216
|
+
end
|
217
|
+
|
218
|
+
suffix=0
|
219
|
+
while File.exists?('tournament' + suffix.to_s.rjust(2, '0') + '.yml')
|
220
|
+
suffix = suffix + 1
|
221
|
+
end
|
222
|
+
results_file = 'tournament' + suffix.to_s.rjust(2, '0') + ".yml"
|
223
|
+
|
224
|
+
rounds = []
|
225
|
+
round_number = 0
|
226
|
+
opponents = robots.clone
|
227
|
+
@all_match_data = []
|
228
|
+
robots.each do |bot1|
|
229
|
+
|
230
|
+
opponents.delete bot1
|
231
|
+
opponents.each do |bot2|
|
232
|
+
round_number += 1
|
233
|
+
puts
|
234
|
+
puts "===== Round #{round_number}: #{bot1} vs #{bot2} ====="
|
235
|
+
i = 1
|
236
|
+
matches = []
|
237
|
+
options.matches.times do |i|
|
238
|
+
puts "- Match #{i+1} of #{options.matches} -"
|
239
|
+
cmd = "rrobots --no-gui --timeout #{options.timeout} #{options.directory}/#{bot1} #{options.directory}/#{bot2}"
|
240
|
+
|
241
|
+
# using popen instead of `cmd` lets us see the progress dots (......) output as the match progresses.
|
242
|
+
sub = IO.popen(cmd)
|
243
|
+
results_string = sub.read
|
244
|
+
sub.close
|
245
|
+
|
246
|
+
match_data = YAML.load(results_string)
|
247
|
+
match_data['match'] = i
|
248
|
+
match_data['round'] = round_number
|
249
|
+
match_data['timedout'] = 1 if options.timeout <= match_data['elapsed_ticks']
|
250
|
+
@all_match_data << match_data
|
251
|
+
|
252
|
+
match = Match.new(match_data)
|
253
|
+
puts
|
254
|
+
puts match.one_line_summary
|
255
|
+
matches << match
|
256
|
+
end
|
257
|
+
rounds << Round.new(matches)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# write out match results to a file for possible later analysis
|
262
|
+
File.open(results_file, 'w').puts(YAML::dump(@all_match_data))
|
263
|
+
to_html(@all_match_data, File.open(results_file.sub(/\..*$/, '.html'), 'w'))
|
264
|
+
|
265
|
+
puts
|
266
|
+
puts
|
267
|
+
puts "####################################################################"
|
268
|
+
puts " TOURNAMENT SUMMARY"
|
269
|
+
puts "####################################################################"
|
270
|
+
round_results = []
|
271
|
+
rounds.each {|round| round_results << round.one_line_summary}
|
272
|
+
round_results = round_results.sort
|
273
|
+
round_results.each {|r| puts r}
|
274
|
+
|
275
|
+
|