lib/rtanque/runner.rb
# Add the working directory so that loading of bots works as expected
$LOAD_PATH << Dir.pwd
# Add the gem root dir so that sample_bots can be loaded
$LOAD_PATH << File.expand_path('../../../', __FILE__)
module RTanque
# Runner manages running an {RTanque::Match}
class Runner
LoadError = Class.new(::LoadError)
attr_reader :match
# @param [Integer] width
# @param [Integer] height
# @param [*match_args] args provided to {RTanque::Match#initialize}
def initialize(width, height, *match_args)
@match = RTanque::Match.new(RTanque::Arena.new(width, height), *match_args)
end
# Attempts to load given {RTanque::Bot::Brain} given its path
# @param [String] brain_path
# @raise [RTanque::Runner::LoadError] if brain could not be loaded
def add_brain_path(brain_path)
parsed_path = self.parse_brain_path(brain_path)
bots = parsed_path.multiplier.times.map { self.new_bots_from_brain_path(parsed_path.path) }.flatten
self.match.add_bots(bots)
end
# Starts the match
# @param [Boolean] gui if false, runs headless match
def start(gui = true)
if gui
require 'rtanque/gui'
window = RTanque::Gui::Window.new(self.match)
trap(:INT) { window.close }
window.show
else
trap(:INT) { self.match.stop }
self.match.start
end
end
protected
def new_bots_from_brain_path(brain_path)
self.fetch_brain_klasses(brain_path).map do |brain_klass|
RTanque::Bot.new_random_location(self.match.arena, brain_klass)
end
end
def fetch_brain_klasses(brain_path)
@load_brain_klass_cache ||= Hash.new do |cache, path|
cache[path] = self.get_diff_in_object_space(RTanque::Bot::Brain) {
begin
require(path)
rescue ::LoadError => e
raise LoadError, e.message
end
}
raise LoadError, "No class of type #{RTanque::Bot::Brain} found in #{path}" if cache[path].empty?
cache[path]
end
@load_brain_klass_cache[brain_path]
end
def get_diff_in_object_space(klass)
current_object_space = self.get_descendants_of_class(klass)
yield
self.get_descendants_of_class(klass) - current_object_space
end
def get_descendants_of_class(klass)
::ObjectSpace.each_object(::Class).select {|k| k < klass }
end
BRAIN_PATH_PARSER = /\A(.+?)\:[x|X](\d+)\z/
# @!visibility private
ParsedBrainPath = Struct.new(:path, :multiplier)
def parse_brain_path(brain_path)
path = brain_path.gsub('\.rb$', '')
multiplier = 1
brain_path.match(BRAIN_PATH_PARSER) { |m|
path = m[1]
multiplier = m[2].to_i
}
ParsedBrainPath.new(path, multiplier)
end
end
end