rrobots 0.0.1
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/.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
|
+
|