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