bin/rtanque
#!/usr/bin/env ruby
require 'rubygems'
require 'thor'
require 'rtanque'
require 'rtanque/runner'
require 'octokit'
class RTanqueCLI < Thor
include Thor::Actions
BOT_DIR = 'bots'
source_root(File.expand_path('../../', __FILE__))
desc "start <path_to_brain> <path_to_brain>:x3 ...", "Starts match with given bots"
long_desc <<-LONGDESC
Start an RTanque match. Provide as arguments paths to the brains.
Note that multiple of the same bot can be loaded by appending ':x<int>' to the path. Eg 'bot/my_bot:x2'
There exist a few sample bots to help get started:
\x5 * sample_bots/keyboard (special bot controlled with keyboard: a/s/d/f and arrow keys)
\x5 * sample_bots/seek_and_destroy
\x5 * sample_bots/camper
LONGDESC
method_option :width, :aliases => '-w', :default => 1200, :type => :numeric, :banner => 'width of window'
method_option :height, :aliases => '-h', :default => 700, :type => :numeric, :banner => 'height of window'
method_option :max_ticks, :aliases => '-m', :default => Float::INFINITY, :type => :numeric, :banner => 'max ticks allowed per match'
method_option :teams, :default => false, :type => :boolean, :banner => 'true to do a team based match based on tank names'
method_option :gui, :default => true, :type => :boolean, :banner => 'false to run headless'
method_option :gc, :default => true, :type => :boolean, :banner => 'disable GC (EXPERIMENTAL)'
method_option :quiet, :aliases => '-q', :default => false, :type => :boolean, :banner => 'disable chatter'
method_option :seed, :default => Kernel.srand, :type => :numeric, :banner => 'random number seed value'
def start(*brain_paths)
Kernel.srand(options[:seed])
runner = RTanque::Runner.new(options[:width], options[:height], options[:max_ticks], options[:teams])
brain_paths.each { |brain_path|
begin
runner.add_brain_path(brain_path)
rescue RTanque::Runner::LoadError => e
say e.message, :red
exit false
end
}
self.print_start_banner(runner) unless options[:quiet]
self.set_gc(options[:gc]) { runner.start(options[:gui]) }
self.print_runner_stats(runner) unless options[:quiet]
end
desc "new_bot <bot_name>", "Creates a new bot"
long_desc <<-LONGDESC
Helper to create a basic brain template in the bots directory
LONGDESC
def new_bot(bot_name)
@bot_name = bot_name
@bot_class_name = Thor::Util.camel_case(bot_name)
template('templates/bot.erb', "#{BOT_DIR}/#{Thor::Util.snake_case(bot_name)}.rb")
end
desc "get_gist <gist_id> <gist_id> ...", "Downloads files from given gist ids into #{BOT_DIR} directory"
long_desc <<-LONGDESC
Helper to download tanks from github gists for easier sharing. Gists can be both 'secret' and 'public'.
LONGDESC
method_option :force, :aliases => '-f', :default => false, :type => :boolean, :banner => 'overwrite existing file without prompt'
def get_gist(*gist_ids)
gist_ids.each { |gist_id| self.download_gist(gist_id, options) }
end
protected
def print_start_banner(runner)
self.print_stats{ |table|
# print options
options.each { |opts| table << [set_color(opts[0], :yellow), opts[1].to_s] }
# print bots
table << [set_color('Bots', :green), runner.match.bots.map { |bot| bot.name }]
}
end
def print_runner_stats(runner)
say '='*30
self.print_stats{ |table|
table << [set_color('Ticks', :blue), runner.match.ticks.to_s]
table << [set_color('Survivors', :green)] + runner.match.bots.map { |bot| "#{bot.name} [#{bot.health.round}]" }
}
end
def set_gc(gc = true)
if gc
yield
else
GC.disable
begin
yield
ensure
GC.enable
end
end
end
def download_gist(gist_id, options)
client = Octokit::Client.new
begin
gist = client.gist(gist_id)
rescue Octokit::NotFound
puts set_color("Error! Gist #{gist_id} not found. Please ensure the gist id is correct.", :red)
else
gist.files.attrs.each do |name, gist_file|
gist_path = "#{BOT_DIR}/#{gist.owner.login}.#{gist_id}/#{name}"
create_file(gist_path, gist_file.content, options)
end
end
end
def print_stats(indent = 2, &block)
self.print_table([].tap(&block), :indent => indent)
end
end
RTanqueCLI.start