bcpm 0.11
Sign up to get free protection for your applications and to get access to all the features.
- 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
|