bcpm 0.11
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/CHANGELOG +133 -0
- data/Manifest +23 -0
- data/README +292 -0
- data/Rakefile +23 -0
- data/bcpm.gemspec +35 -0
- data/bin/bcpm +5 -0
- data/lib/bcpm.rb +23 -0
- data/lib/bcpm/cleanup.rb +24 -0
- data/lib/bcpm/cli.rb +262 -0
- data/lib/bcpm/config.rb +100 -0
- data/lib/bcpm/dist.rb +133 -0
- data/lib/bcpm/duel.rb +153 -0
- data/lib/bcpm/git.rb +93 -0
- data/lib/bcpm/match.rb +275 -0
- data/lib/bcpm/player.rb +410 -0
- data/lib/bcpm/regen.rb +102 -0
- data/lib/bcpm/socket.rb +8 -0
- data/lib/bcpm/tests/assertion_error.rb +13 -0
- data/lib/bcpm/tests/assertions.rb +102 -0
- data/lib/bcpm/tests/case_base.rb +166 -0
- data/lib/bcpm/tests/environment.rb +256 -0
- data/lib/bcpm/tests/suite.rb +114 -0
- data/lib/bcpm/tests/test_match.rb +166 -0
- data/lib/bcpm/update.rb +38 -0
- metadata +129 -0
data/lib/bcpm/dist.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Bcpm
|
6
|
+
|
7
|
+
# Battle-code distribution management.
|
8
|
+
module Dist
|
9
|
+
# Hooks a player's code into the installed battlecode distribution.
|
10
|
+
def self.add_player(player_path)
|
11
|
+
team_path = File.join dist_path, 'teams', File.basename(player_path)
|
12
|
+
if /mingw/ =~ RUBY_PLATFORM || (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
13
|
+
Dir.rmdir team_path if File.exist? team_path
|
14
|
+
command = Shellwords.shelljoin(['cmd', '/C', 'mklink', '/D', team_path.gsub('/', '\\'),
|
15
|
+
player_path.gsub('/', '\\')])
|
16
|
+
Kernel.`(command)
|
17
|
+
else
|
18
|
+
File.unlink team_path if File.exist? team_path
|
19
|
+
FileUtils.ln_s player_path, team_path
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Unhooks a player's code from the installed battlecode distribution.
|
24
|
+
def self.remove_player(player_path)
|
25
|
+
return unless contains_player?(player_path)
|
26
|
+
team_path = File.join dist_path, 'teams', File.basename(player_path)
|
27
|
+
if /mingw/ =~ RUBY_PLATFORM || (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
28
|
+
Dir.rmdir team_path
|
29
|
+
else
|
30
|
+
File.unlink team_path
|
31
|
+
end
|
32
|
+
bin_path = File.join dist_path, 'bin', File.basename(player_path)
|
33
|
+
FileUtils.rm_rf bin_path if File.exist?(bin_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# True if the given path is a player that is hooked into the distribution.
|
37
|
+
def self.contains_player?(player_path)
|
38
|
+
team_path = File.join dist_path, 'teams', File.basename(player_path)
|
39
|
+
if /mingw/ =~ RUBY_PLATFORM || (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
40
|
+
File.exist? team_path
|
41
|
+
else
|
42
|
+
File.exist?(team_path) && File.symlink?(team_path) && File.readlink(team_path) == player_path
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Upgrades the installed battlecode distribution to the latest version.
|
47
|
+
#
|
48
|
+
# Does a fresh install if no distribution is configured.
|
49
|
+
def self.upgrade
|
50
|
+
return install unless installed?
|
51
|
+
Bcpm::Git.update_repo dist_path
|
52
|
+
end
|
53
|
+
|
54
|
+
# Installs the latest battlecode distribution.
|
55
|
+
def self.install
|
56
|
+
Bcpm::Git.clone_repo repo_uri, 'master', dist_path
|
57
|
+
Bcpm::Config[:dist_repo_uri] = repo_uri
|
58
|
+
Bcpm::Config[:dist_path] = dist_path
|
59
|
+
end
|
60
|
+
|
61
|
+
# True if a battlecode distribution is installed on the local machine.
|
62
|
+
def self.installed?
|
63
|
+
Bcpm::Config.config.has_key? :dist_path
|
64
|
+
end
|
65
|
+
|
66
|
+
# Removes the battlecode distribution installed on the location machine.
|
67
|
+
def self.uninstall
|
68
|
+
return unless installed?
|
69
|
+
FileUtils.rm_rf dist_path
|
70
|
+
Bcpm::Config[:dist_path] = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Path to the battlecode ant file.
|
74
|
+
def self.ant_file
|
75
|
+
File.join dist_path, 'build.xml'
|
76
|
+
end
|
77
|
+
|
78
|
+
# Path to the battlecode maps directory.
|
79
|
+
def self.maps_path
|
80
|
+
File.join dist_path, 'maps'
|
81
|
+
end
|
82
|
+
|
83
|
+
# Path to the battlecode simulator configuration file.
|
84
|
+
def self.conf_file
|
85
|
+
File.join dist_path, 'bc.conf'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Path to the locally installed battlecode distribution.
|
89
|
+
def self.dist_path
|
90
|
+
Bcpm::Config[:dist_path] || default_dist_path
|
91
|
+
end
|
92
|
+
|
93
|
+
# Path to the locally installed battlecode distribution.
|
94
|
+
def self.default_dist_path
|
95
|
+
if File.exist?('.metadata') && File.directory?('.metadata')
|
96
|
+
File.expand_path './battlecode'
|
97
|
+
else
|
98
|
+
File.expand_path '~/battlecode'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Git URI to the distribution repository.
|
103
|
+
def self.repo_uri
|
104
|
+
Bcpm::Config[:dist_repo_uri] || default_repo_uri
|
105
|
+
end
|
106
|
+
|
107
|
+
# Git URI to the distribution repository.
|
108
|
+
def self.default_repo_uri
|
109
|
+
'git@git.pwnb.us:six370/battlecode2011.git'
|
110
|
+
end
|
111
|
+
|
112
|
+
# Maps installed in the battlecode distribution.
|
113
|
+
def self.maps
|
114
|
+
Dir.glob(File.join(maps_path, '*.xml')).map do |f|
|
115
|
+
File.basename(f).sub(/\.xml$/, '')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Copies a battlecode distribution map.
|
120
|
+
def self.copy_map(map_name, destination)
|
121
|
+
map_file = File.join maps_path, map_name + '.xml'
|
122
|
+
unless File.exist?(map_file)
|
123
|
+
puts "No map found at#{map_file}"
|
124
|
+
return
|
125
|
+
end
|
126
|
+
if File.exist?(destination) && File.directory?(destination)
|
127
|
+
destionation = File.join destination, map_name + '.xml'
|
128
|
+
end
|
129
|
+
FileUtils.cp map_file, destination
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end # namespace Bcpm
|
data/lib/bcpm/duel.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Bcpm
|
6
|
+
|
7
|
+
# Pits players against other players.
|
8
|
+
module Duel
|
9
|
+
# Has two players fight on all maps in both positions.
|
10
|
+
def self.duel_pair(player1_name, player2_name, show_progress = false,
|
11
|
+
maps = nil)
|
12
|
+
maps ||= Bcpm::Dist.maps.sort
|
13
|
+
|
14
|
+
# Abort in case of typos.
|
15
|
+
[player1_name, player2_name].each do |player|
|
16
|
+
unless Bcpm::Player.wired? player
|
17
|
+
puts "Player #{player} is not installed\n"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Abort in case of compile errors.
|
23
|
+
env = Bcpm::Tests::Environment.new player1_name
|
24
|
+
unless env.build
|
25
|
+
print env.build_log
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
# Compute the matches.
|
30
|
+
matches = maps.map { |map_name|
|
31
|
+
[:a, :b].map do |side|
|
32
|
+
Bcpm::Tests::TestMatch.new side, player2_name, map_name, env
|
33
|
+
end
|
34
|
+
}.flatten
|
35
|
+
|
36
|
+
# Compute stats.
|
37
|
+
score, wins, losses, errors = 0, [], [], []
|
38
|
+
multiplex_matches(matches) do |match|
|
39
|
+
score_delta = case match.winner
|
40
|
+
when :a, :b
|
41
|
+
(match.winner == match.side) ? 1 : -1
|
42
|
+
else
|
43
|
+
0
|
44
|
+
end
|
45
|
+
if show_progress
|
46
|
+
print "#{player1_name} #{match.description}: #{outcome_string(score_delta)}\n"
|
47
|
+
STDOUT.flush
|
48
|
+
end
|
49
|
+
score += score_delta if score_delta
|
50
|
+
case score_delta
|
51
|
+
when 1
|
52
|
+
wins << match
|
53
|
+
when -1
|
54
|
+
losses << match
|
55
|
+
when 0
|
56
|
+
errors << match
|
57
|
+
end
|
58
|
+
end
|
59
|
+
{ :score => score, :wins => wins, :losses => losses, :errors => errors }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Runs may matches in parallel.
|
63
|
+
#
|
64
|
+
# Returns the given matches. If given a block, also yields each match as it
|
65
|
+
# becomes available.
|
66
|
+
def self.multiplex_matches(matches)
|
67
|
+
# Schedule matches.
|
68
|
+
in_queue = Queue.new
|
69
|
+
matches.each do |match|
|
70
|
+
in_queue.push match
|
71
|
+
end
|
72
|
+
match_threads.times { in_queue.push nil }
|
73
|
+
|
74
|
+
# Execute matches.
|
75
|
+
out_queue = Queue.new
|
76
|
+
old_abort = Thread.abort_on_exception
|
77
|
+
Thread.abort_on_exception = true
|
78
|
+
match_threads.times do
|
79
|
+
Thread.new do
|
80
|
+
loop do
|
81
|
+
break unless match = in_queue.pop
|
82
|
+
match.run false
|
83
|
+
out_queue.push match
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
Thread.abort_on_exception = old_abort
|
88
|
+
|
89
|
+
matches.length.times do
|
90
|
+
match = out_queue.pop
|
91
|
+
if Kernel.block_given?
|
92
|
+
yield match
|
93
|
+
end
|
94
|
+
end
|
95
|
+
matches
|
96
|
+
end
|
97
|
+
|
98
|
+
# The string to be shown for a match outcome.
|
99
|
+
def self.outcome_string(score_delta)
|
100
|
+
# TODO(pwnall): ANSI color codes
|
101
|
+
if /mingw/ =~ RUBY_PLATFORM ||
|
102
|
+
(/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
103
|
+
{0 => "error", 1 => "won", -1 => "lost"}[score_delta]
|
104
|
+
else
|
105
|
+
{
|
106
|
+
0 => "\33[33merror\33[0m",
|
107
|
+
1 => "\33[32mwon\33[0m",
|
108
|
+
-1 => "\33[31mlost\33[0m"
|
109
|
+
}[score_delta]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Number of threads to use for simulating matches.
|
114
|
+
def self.match_threads
|
115
|
+
(Bcpm::Config[:match_threads] ||= default_match_threads).to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
# Number of threads to use for simulating matches.
|
119
|
+
def self.default_match_threads
|
120
|
+
1
|
121
|
+
end
|
122
|
+
|
123
|
+
# Has all players compete against each other.
|
124
|
+
def self.rank_players(player_list, show_progress = false, maps = nil)
|
125
|
+
scores = {}
|
126
|
+
0.upto(player_list.length - 1) do |i|
|
127
|
+
0.upto(i - 1) do |j|
|
128
|
+
outcome = duel_pair player_list[i], player_list[j], show_progress, maps
|
129
|
+
scores[i] ||= 0
|
130
|
+
scores[i] += outcome[:score]
|
131
|
+
scores[j] ||= 0
|
132
|
+
scores[j] -= outcome[:score]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
scores.map { |k, v| [v, player_list[k]] }.
|
136
|
+
sort_by { |score, player| [-score, player] }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Scores one player against the other players.
|
140
|
+
def self.score_player(player, player_list, show_progress = false, maps = nil)
|
141
|
+
player_list = player_list - [player]
|
142
|
+
scores = player_list.map do |opponent|
|
143
|
+
[
|
144
|
+
duel_pair(player, opponent, show_progress, maps)[:score],
|
145
|
+
opponent
|
146
|
+
]
|
147
|
+
end
|
148
|
+
{ :points => scores.map(&:first).inject(0) { |a, b| a + b},
|
149
|
+
:scores => scores.sort_by { |score, player| [score, player] } }
|
150
|
+
end
|
151
|
+
end # module Bcpm::Duel
|
152
|
+
|
153
|
+
end # namespace Bcpm
|
data/lib/bcpm/git.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Bcpm
|
6
|
+
|
7
|
+
# Git repository operations.
|
8
|
+
module Git
|
9
|
+
# Clones a repository.
|
10
|
+
#
|
11
|
+
# Returns true for success, false if something went wrong.
|
12
|
+
def self.clone_repo(repo_uri, repo_branch, local_path)
|
13
|
+
FileUtils.mkdir_p File.dirname(local_path)
|
14
|
+
success = nil
|
15
|
+
Dir.chdir File.dirname(local_path) do
|
16
|
+
FileUtils.rm_rf File.basename(local_path) if File.exists?(local_path)
|
17
|
+
if repo_branch == 'master'
|
18
|
+
success = Kernel.system 'git', 'clone', repo_uri,
|
19
|
+
File.basename(local_path)
|
20
|
+
else
|
21
|
+
success = Kernel.system 'git', 'clone', '--branch', repo_branch,
|
22
|
+
repo_uri, File.basename(local_path)
|
23
|
+
unless success
|
24
|
+
success = Kernel.system 'git', 'clone', repo_uri,
|
25
|
+
File.basename(local_path)
|
26
|
+
if success
|
27
|
+
Dir.chdir File.basename(local_path) do
|
28
|
+
success = Kernel.system 'git', 'checkout', '-q',
|
29
|
+
'origin/' + repo_branch
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
FileUtils.rm_rf local_path unless success
|
36
|
+
success
|
37
|
+
end
|
38
|
+
|
39
|
+
# Downloads a snapshot of a repository.
|
40
|
+
#
|
41
|
+
# Returns true for success, false if something went wrong.
|
42
|
+
def self.checkpoint_repo(repo_uri, repo_branch, local_path)
|
43
|
+
FileUtils.mkdir_p local_path
|
44
|
+
success = nil
|
45
|
+
Dir.chdir File.dirname(local_path) do
|
46
|
+
zip_file = File.basename(local_path) + '.zip'
|
47
|
+
success = Kernel.system('git', 'archive', '--format=zip',
|
48
|
+
'--remote', repo_uri, '--output', zip_file, '-9', repo_branch)
|
49
|
+
if success
|
50
|
+
Dir.chdir File.basename(File.basename(local_path)) do
|
51
|
+
success = Kernel.system 'unzip', '-qq', File.join('..', zip_file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
File.unlink zip_file if File.exist?(zip_file)
|
55
|
+
end
|
56
|
+
unless success
|
57
|
+
puts "Trying workaround for old git"
|
58
|
+
success = clone_repo repo_uri, repo_branch, local_path
|
59
|
+
FileUtils.rm_rf File.join(local_path, '.git') if success
|
60
|
+
end
|
61
|
+
|
62
|
+
FileUtils.rm_rf local_path unless success
|
63
|
+
success
|
64
|
+
end
|
65
|
+
|
66
|
+
# Checks out the working copy of the repository.
|
67
|
+
#
|
68
|
+
# Returns true for success, false if something went wrong.
|
69
|
+
def self.checkpoint_local_repo(repo_path, local_path)
|
70
|
+
return false unless File.exist?(repo_path)
|
71
|
+
FileUtils.mkdir_p local_path
|
72
|
+
Dir.entries(repo_path).each do |entry|
|
73
|
+
next if ['.', '..', '.git'].include? entry
|
74
|
+
FileUtils.cp_r File.join(repo_path, entry), local_path
|
75
|
+
end
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
# Pulls the latest changes into the repository.
|
80
|
+
def self.update_repo(local_path)
|
81
|
+
Dir.chdir local_path do
|
82
|
+
Kernel.system 'git', 'pull'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Temporary directory name.
|
87
|
+
def self.tempdir
|
88
|
+
File.join Dir.tmpdir, 'bcpm',
|
89
|
+
"update_#{Socket.hostname}_#{(Time.now.to_f * 1000).to_i}_#{$PID}"
|
90
|
+
end
|
91
|
+
end # module Bcpm::Git
|
92
|
+
|
93
|
+
end # namespace Bcpm
|
data/lib/bcpm/match.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shellwords'
|
4
|
+
require 'socket'
|
5
|
+
require 'thread'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
# :nodoc: namespace
|
9
|
+
module Bcpm
|
10
|
+
|
11
|
+
# Runs matches between players.
|
12
|
+
module Match
|
13
|
+
# Runs a match between two players.
|
14
|
+
def self.run(player1_name, player2_name, map_name, mode)
|
15
|
+
env = Bcpm::Tests::Environment.new player1_name
|
16
|
+
options = {}
|
17
|
+
if mode == :debug
|
18
|
+
Bcpm::Config[:breakpoints] ||= true
|
19
|
+
Bcpm::Config[:debugcode] ||= true
|
20
|
+
Bcpm::Config[:noupkeep] ||= false
|
21
|
+
Bcpm::Config[:debuglimit] ||= 1_000_000
|
22
|
+
options = Hash[engine_options.map { |k, v| [v, Bcpm::Config[k]] }]
|
23
|
+
end
|
24
|
+
match = Bcpm::Tests::TestMatch.new :a, player2_name, map_name, env, options
|
25
|
+
match.run(mode != :file)
|
26
|
+
"Winner side: #{match.winner.to_s.upcase}\n" + match.stash_data
|
27
|
+
end
|
28
|
+
|
29
|
+
# Key-value pairs for friendly => canonical names of battlecode engine options.
|
30
|
+
def self.engine_options
|
31
|
+
{
|
32
|
+
'breakpoints' => 'bc.engine.breakpoints',
|
33
|
+
'debugcode' => 'bc.engine.debug-methods',
|
34
|
+
'noupkeep' => 'bc.engine.upkeep',
|
35
|
+
'debuglimit' => 'bc.engine.debug-max-bytecodes'
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Runs a match between two players and returns the log data.
|
40
|
+
#
|
41
|
+
# Args:
|
42
|
+
# player1_name:: name of locally installed player (A)
|
43
|
+
# player2_name:: name of locally installed player (B)
|
44
|
+
# silence_b:: if true, B is silenced; otherwise, A is silenced
|
45
|
+
# map_name:: name of map .xml file (or full path for custom map)
|
46
|
+
# run_live:: if true, tries to run the match using the live UI
|
47
|
+
# bc_options:: hash of simulator settings to be added to bc.conf
|
48
|
+
def self.match_data(player1_name, player2_name, silence_b, map_name, run_live, bc_options = {})
|
49
|
+
uid = tempfile
|
50
|
+
tempdir = File.expand_path File.join(Dir.tmpdir, 'bcpm', 'match_' + uid)
|
51
|
+
FileUtils.mkdir_p tempdir
|
52
|
+
binfile = File.join tempdir, 'match.rms'
|
53
|
+
txtfile = File.join tempdir, 'match.txt'
|
54
|
+
build_log = File.join tempdir, 'build.log'
|
55
|
+
match_log = File.join tempdir, 'match.log'
|
56
|
+
scribe_log = File.join tempdir, 'scribe.log'
|
57
|
+
|
58
|
+
bc_config = simulator_config player1_name, player2_name, silence_b, map_name, binfile, txtfile
|
59
|
+
bc_config.merge! bc_options
|
60
|
+
conf_file = File.join tempdir, 'bc.conf'
|
61
|
+
write_config conf_file, bc_config
|
62
|
+
write_ui_config conf_file, true, bc_config if run_live
|
63
|
+
build_file = File.join tempdir, 'build.xml'
|
64
|
+
write_build build_file, conf_file
|
65
|
+
|
66
|
+
if run_live
|
67
|
+
run_build_script tempdir, build_file, match_log, 'run', 'Stop buffering match'
|
68
|
+
else
|
69
|
+
run_build_script tempdir, build_file, match_log, 'file'
|
70
|
+
end
|
71
|
+
run_build_script tempdir, build_file, scribe_log, 'transcribe'
|
72
|
+
|
73
|
+
textlog = File.exist?(txtfile) ? File.open(txtfile, 'rb') { |f| f.read } : ''
|
74
|
+
binlog = File.exist?(binfile) ? File.open(binfile, 'rb') { |f| f.read } : ''
|
75
|
+
antlog = File.exist?(match_log) ? File.open(match_log, 'rb') { |f| f.read } : ''
|
76
|
+
FileUtils.rm_rf tempdir
|
77
|
+
|
78
|
+
{ :ant => extract_ant_log(antlog), :rms => binlog, :script => textlog, :uid => uid }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Replays a match using the binlog (.rms file).
|
82
|
+
def self.replay(binfile)
|
83
|
+
tempdir = File.join Dir.tmpdir, 'bcpm', 'match_' + tempfile
|
84
|
+
FileUtils.mkdir_p tempdir
|
85
|
+
match_log = File.join tempdir, 'match.log'
|
86
|
+
|
87
|
+
bc_config = simulator_config nil, nil, true, nil, binfile, nil
|
88
|
+
conf_file = File.join tempdir, 'bc.conf'
|
89
|
+
write_config conf_file, bc_config
|
90
|
+
write_ui_config conf_file, false, bc_config
|
91
|
+
build_file = File.join tempdir, 'build.xml'
|
92
|
+
write_build build_file, conf_file
|
93
|
+
|
94
|
+
run_build_script tempdir, build_file, match_log, 'run'
|
95
|
+
FileUtils.rm_rf tempdir
|
96
|
+
end
|
97
|
+
|
98
|
+
# Options to be overridden for the battlecode simulator.
|
99
|
+
def self.simulator_config(player1_name, player2_name, silence_b, map_name, binfile, txtfile)
|
100
|
+
Bcpm::Config[:client3d] ||= 'off'
|
101
|
+
Bcpm::Config[:sound] ||= 'off'
|
102
|
+
config = {
|
103
|
+
'bc.engine.silence-a' => !silence_b,
|
104
|
+
'bc.engine.silence-b' => !!silence_b,
|
105
|
+
'bc.dialog.skip' => true,
|
106
|
+
'bc.server.throttle' => 'yield',
|
107
|
+
'bc.server.throttle-count' => 100000,
|
108
|
+
'bc.client.opengl' => Bcpm::Config[:client3d],
|
109
|
+
'bc.client.sound-on' => Bcpm::Config[:sound] || 'off',
|
110
|
+
|
111
|
+
# Healthy production defaults.
|
112
|
+
'bc.engine.breakpoints' => false,
|
113
|
+
'bc.engine.debug-methods' => false,
|
114
|
+
'bc.engine.upkeep' => true
|
115
|
+
}
|
116
|
+
map_path = nil
|
117
|
+
if map_name
|
118
|
+
if File.basename(map_name) != map_name
|
119
|
+
map_path = File.dirname map_name
|
120
|
+
map_name = File.basename(map_name).sub(/\.xml$/, '')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
config['bc.game.maps'] = map_name if map_name
|
124
|
+
config['bc.game.map-path'] = map_path if map_path
|
125
|
+
config['bc.game.team-a'] = player1_name if player1_name
|
126
|
+
config['bc.game.team-b'] = player2_name if player2_name
|
127
|
+
config['bc.server.save-file'] = binfile if binfile
|
128
|
+
config['bc.server.transcribe-input'] = binfile if binfile
|
129
|
+
config['bc.server.transcribe-output'] = txtfile if txtfile
|
130
|
+
config
|
131
|
+
end
|
132
|
+
|
133
|
+
# Memory to be dedicated to the simulation JVM (in megabytes.)
|
134
|
+
def self.vm_ram
|
135
|
+
(Bcpm::Config['vm_ram'] ||= default_vm_ram).to_i
|
136
|
+
end
|
137
|
+
|
138
|
+
# Memory to be dedicated to the simulation JVM (in megabytes.)
|
139
|
+
def self.default_vm_ram
|
140
|
+
1024
|
141
|
+
end
|
142
|
+
|
143
|
+
# Writes a patched buildfile that references the given configuration file.
|
144
|
+
def self.write_build(buildfile, conffile)
|
145
|
+
contents = Bcpm::Player.ant_config conffile
|
146
|
+
File.open(buildfile, 'wb') { |f| f.write contents }
|
147
|
+
end
|
148
|
+
|
149
|
+
# Writes a cleaned up battlecode simulator configuration file.
|
150
|
+
def self.write_config(conffile, options = {})
|
151
|
+
lines = File.read(Bcpm::Dist.conf_file).split("\n")
|
152
|
+
lines = lines.reject do |line|
|
153
|
+
key = line.split('=', 2).first
|
154
|
+
options.has_key? key
|
155
|
+
end
|
156
|
+
lines += options.map { |key, value| "#{key}=#{value}" }
|
157
|
+
File.open(conffile, 'wb') { |f| f.write lines.join("\n") + "\n" }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Writes the configuration for the battlecode UI.
|
161
|
+
#
|
162
|
+
# This is a singleton file, so only one client should run at a time.
|
163
|
+
def self.write_ui_config(conffile, run_live, options = {})
|
164
|
+
save_path = options['bc.server.save-file'] || ''
|
165
|
+
if /mingw/ =~ RUBY_PLATFORM || (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
166
|
+
save_path = save_path.dup
|
167
|
+
save_path.gsub! '\\', '\\\\\\\\'
|
168
|
+
save_path.gsub! '/', '\\\\\\\\'
|
169
|
+
if save_path[1, 2] == ':\\'
|
170
|
+
save_path[1, 2] = '\\:\\'
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
choice = run_live ? 'LOCAL' : 'FILE'
|
175
|
+
File.open(File.expand_path('~/.battlecode.ui'), 'wb') do |f|
|
176
|
+
f.write <<END_CONFIG
|
177
|
+
choice=#{choice}
|
178
|
+
save=#{run_live}
|
179
|
+
save-file=#{save_path}
|
180
|
+
file=#{save_path}
|
181
|
+
host=
|
182
|
+
analyzeFile=false
|
183
|
+
glclient=#{options['bc.client.opengl'] || 'false'}
|
184
|
+
showMinimap=false
|
185
|
+
MAP=#{options['bc.game.maps']}
|
186
|
+
maps=#{options['bc.game.maps']}
|
187
|
+
TEAM_A=#{options['bc.game.team-a']}
|
188
|
+
TEAM_B=#{options['bc.game.team-b']}
|
189
|
+
lockstep=false
|
190
|
+
END_CONFIG
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Runs the battlecode Ant script.
|
195
|
+
def self.run_build_script(target_dir, build_file, log_file, target, run_live = false)
|
196
|
+
if run_live
|
197
|
+
Dir.chdir target_dir do
|
198
|
+
command = Shellwords.shelljoin(['ant', '-noinput', '-buildfile',
|
199
|
+
build_file, target])
|
200
|
+
|
201
|
+
# Start the build as a subprocess, dump its output to the queue as
|
202
|
+
# string fragments. nil means the subprocess completed.
|
203
|
+
queue = Queue.new
|
204
|
+
thread = Thread.start do
|
205
|
+
IO.popen command do |f|
|
206
|
+
begin
|
207
|
+
loop { queue << f.readpartial(1024) }
|
208
|
+
rescue EOFError
|
209
|
+
queue << nil
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
build_output = ''
|
215
|
+
while fragment = queue.pop
|
216
|
+
# Dump the build output to the screen as the simulation happens.
|
217
|
+
print fragment
|
218
|
+
STDOUT.flush
|
219
|
+
build_output << fragment
|
220
|
+
|
221
|
+
# Let bcpm carry on when the simulation completes.
|
222
|
+
break if build_output.index(run_live)
|
223
|
+
end
|
224
|
+
build_output << "\n" if build_output[-1] != ?\n
|
225
|
+
|
226
|
+
# Pretend everything was put in a log file.
|
227
|
+
File.open(log_file, 'wb') { |f| f.write build_output }
|
228
|
+
return thread
|
229
|
+
end
|
230
|
+
else
|
231
|
+
command = Shellwords.shelljoin(['ant', '-noinput', '-buildfile',
|
232
|
+
build_file, '-logfile', log_file, target])
|
233
|
+
if /mingw/ =~ RUBY_PLATFORM ||
|
234
|
+
(/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
|
235
|
+
Dir.chdir target_dir do
|
236
|
+
output = Kernel.`(command)
|
237
|
+
# If there is no log file, dump the output to the log.
|
238
|
+
unless File.exist?(log_file)
|
239
|
+
File.open(log_file, 'wb') { |f| f.write output }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
else
|
243
|
+
pid = fork do
|
244
|
+
Dir.chdir target_dir do
|
245
|
+
output = Kernel.`(command)
|
246
|
+
# If there is no log file, dump the output to the log.
|
247
|
+
unless File.exist?(log_file)
|
248
|
+
File.open(log_file, 'wb') { |f| f.write output }
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
Process.wait pid
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Selects the battlecode simulator log out of an ant log.
|
258
|
+
def self.extract_ant_log(contents)
|
259
|
+
lines = []
|
260
|
+
contents.split("\n").each do |line|
|
261
|
+
start = line.index '[java] '
|
262
|
+
next unless start
|
263
|
+
lines << line[(start + 7)..-1]
|
264
|
+
break if line.index('- Match Finished -')
|
265
|
+
end
|
266
|
+
lines.join("\n")
|
267
|
+
end
|
268
|
+
|
269
|
+
# Temporary file name.
|
270
|
+
def self.tempfile
|
271
|
+
"#{Socket.hostname}_#{'%x' % (Time.now.to_f * 1000).to_i}_#{$PID}_#{'%x' % Thread.current.object_id}"
|
272
|
+
end
|
273
|
+
end # module Bcpm::Match
|
274
|
+
|
275
|
+
end # namespace Bcpm
|