Module: Bcpm::Match

Defined in:
lib/bcpm/match.rb

Overview

Runs matches between players.

Class Method Summary collapse

Class Method Details

.default_vm_ramObject

Memory to be dedicated to the simulation JVM (in megabytes.)



139
140
141
# File 'lib/bcpm/match.rb', line 139

def self.default_vm_ram
  1024
end

.engine_optionsObject

Key-value pairs for friendly => canonical names of battlecode engine options.



30
31
32
33
34
35
36
37
# File 'lib/bcpm/match.rb', line 30

def self.engine_options
  {
    'breakpoints' => 'bc.engine.breakpoints',
    'debugcode' => 'bc.engine.debug-methods',
    'noupkeep' => 'bc.engine.upkeep',
    'debuglimit' => 'bc.engine.debug-max-bytecodes'
  }
end

.extract_ant_log(contents) ⇒ Object

Selects the battlecode simulator log out of an ant log.



258
259
260
261
262
263
264
265
266
267
# File 'lib/bcpm/match.rb', line 258

def self.extract_ant_log(contents)
  lines = []
  contents.split("\n").each do |line|
    start = line.index '[java] '
    next unless start
    lines << line[(start + 7)..-1]
    break if line.index('- Match Finished -')
  end
  lines.join("\n")
end

.match_data(player1_name, player2_name, silence_b, map_name, run_live, bc_options = {}) ⇒ Object

Runs a match between two players and returns the log data.

Args:

player1_name:: name of locally installed player (A)
player2_name:: name of locally installed player (B)
silence_b:: if true, B is silenced; otherwise, A is silenced
map_name:: name of map .xml file (or full path for custom map)
run_live:: if true, tries to run the match using the live UI
bc_options:: hash of simulator settings to be added to bc.conf


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/bcpm/match.rb', line 48

def self.match_data(player1_name, player2_name, silence_b, map_name, run_live, bc_options = {})
  uid = tempfile
  tempdir = File.expand_path File.join(Dir.tmpdir, 'bcpm', 'match_' + uid)
  FileUtils.mkdir_p tempdir
  binfile = File.join tempdir, 'match.rms'
  txtfile = File.join tempdir, 'match.txt'
  build_log = File.join tempdir, 'build.log'
  match_log = File.join tempdir, 'match.log'
  scribe_log = File.join tempdir, 'scribe.log' 

  bc_config = simulator_config player1_name, player2_name, silence_b, map_name, binfile, txtfile
  bc_config.merge! bc_options
  conf_file = File.join tempdir, 'bc.conf'
  write_config conf_file, bc_config
  write_ui_config conf_file, true, bc_config if run_live
  build_file = File.join tempdir, 'build.xml'
  write_build build_file, conf_file
  
  if run_live
    run_build_script tempdir, build_file, match_log, 'run', 'Stop buffering match'
  else
    run_build_script tempdir, build_file, match_log, 'file'
  end        
  run_build_script tempdir, build_file, scribe_log, 'transcribe'
  
  textlog = File.exist?(txtfile) ? File.open(txtfile, 'rb') { |f| f.read } : ''
  binlog = File.exist?(binfile) ? File.open(binfile, 'rb') { |f| f.read } : ''
  antlog = File.exist?(match_log) ? File.open(match_log, 'rb') { |f| f.read } : ''
  FileUtils.rm_rf tempdir
  
  { :ant => extract_ant_log(antlog), :rms => binlog, :script => textlog, :uid => uid }
end

.replay(binfile) ⇒ Object

Replays a match using the binlog (.rms file).



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/bcpm/match.rb', line 82

def self.replay(binfile)
  tempdir = File.join Dir.tmpdir, 'bcpm', 'match_' + tempfile
  FileUtils.mkdir_p tempdir
  match_log = File.join tempdir, 'match.log'

  bc_config = simulator_config nil, nil, true, nil, binfile, nil
  conf_file = File.join tempdir, 'bc.conf'      
  write_config conf_file, bc_config
  write_ui_config conf_file, false, bc_config
  build_file = File.join tempdir, 'build.xml'
  write_build build_file, conf_file
  
  run_build_script tempdir, build_file, match_log, 'run'
  FileUtils.rm_rf tempdir
end

.run(player1_name, player2_name, map_name, mode) ⇒ Object

Runs a match between two players.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/bcpm/match.rb', line 14

def self.run(player1_name, player2_name, map_name, mode)
  env = Bcpm::Tests::Environment.new player1_name
  options = {}
  if mode == :debug
    Bcpm::Config[:breakpoints] ||= true
    Bcpm::Config[:debugcode] ||= true
    Bcpm::Config[:noupkeep] ||= false
    Bcpm::Config[:debuglimit] ||= 1_000_000
    options = Hash[engine_options.map { |k, v| [v, Bcpm::Config[k]] }]
  end
  match = Bcpm::Tests::TestMatch.new :a, player2_name, map_name, env, options
  match.run(mode != :file)
  "Winner side: #{match.winner.to_s.upcase}\n" + match.stash_data
end

.run_build_script(target_dir, build_file, log_file, target, run_live = false) ⇒ Object

Runs the battlecode Ant script.



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/bcpm/match.rb', line 195

def self.run_build_script(target_dir, build_file, log_file, target, run_live = false)
  if run_live
    Dir.chdir target_dir do
      command = Shellwords.shelljoin(['ant', '-noinput', '-buildfile',
                                      build_file, target])

      # Start the build as a subprocess, dump its output to the queue as
      # string fragments. nil means the subprocess completed.
      queue = Queue.new
      thread = Thread.start do
        IO.popen command do |f|
          begin
            loop { queue << f.readpartial(1024) }
          rescue EOFError
            queue << nil
          end
        end
      end

      build_output = ''
      while fragment = queue.pop
        # Dump the build output to the screen as the simulation happens.
        print fragment
        STDOUT.flush
        build_output << fragment
      
        # Let bcpm carry on when the simulation completes.
        break if build_output.index(run_live)
      end
      build_output << "\n" if build_output[-1] != ?\n
      
      # Pretend everything was put in a log file.
      File.open(log_file, 'wb') { |f| f.write build_output }
      return thread
    end
  else
    command = Shellwords.shelljoin(['ant', '-noinput', '-buildfile',
                                    build_file, '-logfile', log_file, target])
    if /mingw/ =~ RUBY_PLATFORM ||
        (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
      Dir.chdir target_dir do
        output = Kernel.`(command)
        # If there is no log file, dump the output to the log.
        unless File.exist?(log_file)
          File.open(log_file, 'wb') { |f| f.write output }
        end
      end
    else
      pid = fork do
        Dir.chdir target_dir do
          output = Kernel.`(command)
          # If there is no log file, dump the output to the log.
          unless File.exist?(log_file)
            File.open(log_file, 'wb') { |f| f.write output }
          end
        end
      end
      Process.wait pid
    end
  end
end

.simulator_config(player1_name, player2_name, silence_b, map_name, binfile, txtfile) ⇒ Object

Options to be overridden for the battlecode simulator.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/bcpm/match.rb', line 99

def self.simulator_config(player1_name, player2_name, silence_b, map_name, binfile, txtfile)
  Bcpm::Config[:client3d] ||= 'off'
  Bcpm::Config[:sound] ||= 'off'
  config = {
    'bc.engine.silence-a' => !silence_b,
    'bc.engine.silence-b' => !!silence_b,
    'bc.dialog.skip' => true,
    'bc.server.throttle' => 'yield',
    'bc.server.throttle-count' => 100000,
    'bc.client.opengl' => Bcpm::Config[:client3d],
    'bc.client.sound-on' => Bcpm::Config[:sound] || 'off',
    
    # Healthy production defaults.
    'bc.engine.breakpoints' => false,
    'bc.engine.debug-methods' => false,
    'bc.engine.upkeep' => true
  }
  map_path = nil
  if map_name
    if File.basename(map_name) != map_name
      map_path = File.dirname map_name
      map_name = File.basename(map_name).sub(/\.xml$/, '')
    end
  end
  config['bc.game.maps'] = map_name if map_name
  config['bc.game.map-path'] = map_path if map_path
  config['bc.game.team-a'] = player1_name if player1_name
  config['bc.game.team-b'] = player2_name if player2_name
  config['bc.server.save-file'] = binfile if binfile
  config['bc.server.transcribe-input'] = binfile if binfile
  config['bc.server.transcribe-output'] = txtfile if txtfile
  config
end

.tempfileObject

Temporary file name.



270
271
272
# File 'lib/bcpm/match.rb', line 270

def self.tempfile
  "#{Socket.hostname}_#{'%x' % (Time.now.to_f * 1000).to_i}_#{$PID}_#{'%x' % Thread.current.object_id}"
end

.vm_ramObject

Memory to be dedicated to the simulation JVM (in megabytes.)



134
135
136
# File 'lib/bcpm/match.rb', line 134

def self.vm_ram
  (Bcpm::Config['vm_ram'] ||= default_vm_ram).to_i
end

.write_build(buildfile, conffile) ⇒ Object

Writes a patched buildfile that references the given configuration file.



144
145
146
147
# File 'lib/bcpm/match.rb', line 144

def self.write_build(buildfile, conffile)
  contents = Bcpm::Player.ant_config conffile
  File.open(buildfile, 'wb') { |f| f.write contents }
end

.write_config(conffile, options = {}) ⇒ Object

Writes a cleaned up battlecode simulator configuration file.



150
151
152
153
154
155
156
157
158
# File 'lib/bcpm/match.rb', line 150

def self.write_config(conffile, options = {})
  lines = File.read(Bcpm::Dist.conf_file).split("\n")
  lines = lines.reject do |line|
    key = line.split('=', 2).first
    options.has_key? key
  end
  lines += options.map { |key, value| "#{key}=#{value}" }
  File.open(conffile, 'wb') { |f| f.write lines.join("\n") + "\n" }
end

.write_ui_config(conffile, run_live, options = {}) ⇒ Object

Writes the configuration for the battlecode UI.

This is a singleton file, so only one client should run at a time.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/bcpm/match.rb', line 163

def self.write_ui_config(conffile, run_live, options = {})
  save_path = options['bc.server.save-file'] || ''
  if /mingw/ =~ RUBY_PLATFORM || (/win/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM)
    save_path = save_path.dup
    save_path.gsub! '\\', '\\\\\\\\'
    save_path.gsub! '/', '\\\\\\\\'
    if save_path[1, 2] == ':\\'
      save_path[1, 2] = '\\:\\'
    end
  end
  
  choice = run_live ? 'LOCAL' : 'FILE'
  File.open(File.expand_path('~/.battlecode.ui'), 'wb') do |f|
    f.write <<END_CONFIG
choice=#{choice}
save=#{run_live}
save-file=#{save_path}
file=#{save_path}
host=
analyzeFile=false
glclient=#{options['bc.client.opengl'] || 'false'}
showMinimap=false
MAP=#{options['bc.game.maps']}
maps=#{options['bc.game.maps']}
TEAM_A=#{options['bc.game.team-a']}
TEAM_B=#{options['bc.game.team-b']}
lockstep=false
END_CONFIG
  end
end