class Theseus::CLI
Constants
- ALGO_MAP
- TYPE_MAP
Attributes
animate[RW]
delay[RW]
format[RW]
maze_opts[R]
output[RW]
png_opts[R]
solution[RW]
sparse[RW]
type[RW]
unicursal[RW]
Public Class Methods
new(*args)
click to toggle source
# File lib/theseus/cli.rb, line 40 def initialize(*args) args.flatten! @animate = false @delay = 50 @output = "maze" @sparse = 0 @unicursal = false @type = "ortho" @format = :ascii @solution = nil @png_opts = Theseus::Formatters::PNG::DEFAULTS.dup @maze_opts = { mask: nil, width: nil, height: nil, randomness: 50, weave: 0, symmetry: :none, braid: 0, wrap: :none, entrance: nil, exit: nil, algorithm: ALGO_MAP["backtrack"] } option_parser.parse!(args) if args.any? abort "extra arguments detected: #{args.inspect}" end normalize_settings! end
run(*args)
click to toggle source
# File lib/theseus/cli.rb, line 24 def self.run(*args) new(*args).run end
Public Instance Methods
run()
click to toggle source
# File lib/theseus/cli.rb, line 66 def run if @animate run_animation else run_static end end
Private Instance Methods
clear_screen()
click to toggle source
# File lib/theseus/cli.rb, line 84 def clear_screen print "\e[2J" end
cursor_home()
click to toggle source
# File lib/theseus/cli.rb, line 88 def cursor_home print "\e[H" end
normalize_settings!()
click to toggle source
# File lib/theseus/cli.rb, line 162 def normalize_settings! # default width to height, and vice-versa @maze_opts[:width] ||= @maze_opts[:height] @maze_opts[:height] ||= @maze_opts[:width] if @maze_opts[:mask].nil? && (@maze_opts[:width].nil? || @maze_opts[:height].nil?) warn "You must specify either a mask (-m) or the maze dimensions(-w or -H)." abort "Try --help for a full list of options." end if @animate abort "sparse cannot be used for animated mazes" if @sparse > 0 abort "cannot animate unicursal mazes" if @unicursal if @format != :ascii @png_opts[:background] = ChunkyPNG::Color.from_hex(@png_opts[:background]) unless Fixnum === @png_opts[:background] if @png_opts[:background] & 0xFF != 0xFF warn "if you intend to make a movie out of the frames from the animation," warn "it is HIGHLY RECOMMENDED that you use a fully opaque background color." end end # convert delay to a fraction of a second @delay = @delay / 1000.0 end if @solution abort "cannot display solution in ascii mode" if @format == :ascii end if @unicursal @unicursal_entrance = @maze_opts.delete(:entrance) @maze_opts[:entrance] = [0,0] @maze_opts[:exit] = [0,0] end @maze_opts[:mask] ||= Theseus::TransparentMask.new(@maze_opts[:width], @maze_opts[:height]) @maze_opts[:width] ||= @maze_opts[:mask].width @maze_opts[:height] ||= @maze_opts[:mask].height @maze = TYPE_MAP[@type].new(@maze_opts) if @unicursal && !@maze.respond_to?(:to_unicursal) abort "#{@type} mazes do not support the -u (unicursal) option" end end
option_parser()
click to toggle source
# File lib/theseus/cli.rb, line 209 def option_parser OptionParser.new do |opts| setup_required_options(opts) setup_output_options(opts) setup_maze_options(opts) setup_formatting_options(opts) setup_misc_options(opts) end end
run_animation()
click to toggle source
# File lib/theseus/cli.rb, line 76 def run_animation if format == :ascii run_ascii_animation else run_png_animation end end
run_ascii_animation()
click to toggle source
# File lib/theseus/cli.rb, line 97 def run_ascii_animation clear_screen @maze.generate! do show_maze sleep(@delay) end show_maze end
run_png_animation()
click to toggle source
# File lib/theseus/cli.rb, line 115 def run_png_animation step = 0 @maze.generate! do write_frame(step) step += 1 end write_frame(step) step += 1 if @solution solver = @maze.new_solver(type: @solution) while solver.step path = solver.to_path(color: @png_opts[:solution_color]) write_frame(step, paths: [path]) step += 1 end end puts puts "done, %d frames written to %s-*.png" % [step, @output] end
run_static()
click to toggle source
# File lib/theseus/cli.rb, line 139 def run_static @maze.generate! @sparse.times { @maze.sparsify! } if @unicursal enter_at = @unicursal_entrance || [-1,0] if enter_at[0] > 0 && enter_at[0] < width*2 exit_at = [enter_at[0]+1, enter_at[1]] else exit_at = [enter_at[0], enter_at[1]+1] end @maze = @maze.to_unicursal(entrance: enter_at, exit: exit_at) end if @format == :ascii puts @maze.to_s(:mode => :unicode) else @png_opts[:solution] = @solution File.open(@output + ".png", "w") { |io| io.write(@maze.to(:png, @png_opts)) } puts "maze written to #{@output}.png" end end
setup_formatting_options(opts)
click to toggle source
# File lib/theseus/cli.rb, line 319 def setup_formatting_options(opts) opts.separator "" opts.separator "Formatting options:" opts.on("-B", "--background COLOR", "rgba hex background color for maze (default %08X)" % @png_opts[:background]) do |c| @png_opts[:background] = c end opts.on("-C", "--cellcolor COLOR", "rgba hex cell color for maze (default %08X)" % @png_opts[:cell_color]) do |c| @png_opts[:cell_color] = c end opts.on("-L", "--wallcolor COLOR", "rgba hex wall color for maze (default %08X)" % @png_opts[:wall_color]) do |c| @png_opts[:wall_color] = c end opts.on("-U", "--solutioncolor COLOR", "rgba hex color for the answer path (default %08X)" % @png_opts[:solution_color]) do |c| @png_opts[:solution_color] = c end opts.on("-c", "--cell N", Integer, "size of each cell (default #{@png_opts[:cell_size]})") do |c| @png_opts[:cell_size] = c end opts.on("-b", "--border N", Integer, "border padding around outside (default #{@png_opts[:outer_padding]})") do |c| @png_opts[:outer_padding] = c end opts.on("-p", "--padding N", Integer, "padding around cell (default #{@png_opts[:cell_padding]})") do |c| @png_opts[:cell_padding] = c end opts.on("-W", "--wall N", Integer, "thickness of walls (default #{@png_opts[:wall_width]})") do |c| @png_opts[:wall_width] = c end end
setup_maze_options(opts)
click to toggle source
# File lib/theseus/cli.rb, line 264 def setup_maze_options(opts) opts.separator "" opts.separator "Maze options:" opts.on("-s", "--seed N", Integer, "random seed to use") do |s| srand(s) end opts.on("-A", "--algorithm NAME", "the algorithm to use to generate the maze.", "may be any of #{ALGO_MAP.keys.sort.join(",")}.", "defaults to `backtrack'.") do |a| @maze_opts[:algorithm] = ALGO_MAP[a] or abort "unknown algorithm `#{a}'" end opts.on("-t", "--type TYPE", "#{TYPE_MAP.keys.sort.join(",")} (default: #{@type})") do |t| @type = t end opts.on("-u", "--[no-]unicursal", "generate a unicursal maze (results in 2x size)") do |u| @unicursal = u end opts.on("-y", "--symmetry TYPE", "one of none,x,y,xy,radial (default is '#{@maze_opts[:symmetry]}')") do |s| @maze_opts[:symmetry] = s.to_sym end opts.on("-e", "--weave N", Integer, "0-100, chance of a passage to go over/under another (default #{@maze_opts[:weave]})") do |v| @maze_opts[:weave] = v end opts.on("-r", "--random N", Integer, "0-100, randomness of maze (default #{@maze_opts[:randomness]})") do |r| @maze_opts[:randomness] = r end opts.on("-S", "--sparse N", Integer, "how sparse to make the maze (default #{@sparse})") do |s| @sparse = s end opts.on("-d", "--braid N", Integer, "0-100, percentage of deadends to remove (default #{maze_opts[:braid]})") do |b| @maze_opts[:braid] = b end opts.on("-R", "--wrap axis", "none,x,y,xy (default #{@maze_opts[:wrap]})") do |w| @maze_opts[:wrap] = w.to_sym end opts.on("-E", "--enter [X,Y]", "the entrance of the maze (default -1,0)") do |s| @maze_opts[:entrance] = s.split(/,/).map { |v| v.to_i } end opts.on("-X", "--exit [X,Y]", "the exit of the maze (default width,height-1)") do |s| @maze_opts[:exit] = s.split(/,/).map { |v| v.to_i } end end
setup_misc_options(opts)
click to toggle source
# File lib/theseus/cli.rb, line 356 def setup_misc_options(opts) opts.separator "" opts.separator "Other options:" opts.on_tail("-v", "--version", "display the Theseus version and exit") do maze = Theseus::OrthogonalMaze.generate(width: 20, height: 4) s = maze.to_s(mode: :lines).strip print s.gsub(/^/, " ").sub(/^\s*/, "theseus --") require 'theseus/version' puts "--> v#{Theseus::Version::STRING}" puts "a maze generator, renderer, and solver by Jamis Buck <jamis@jamisbuck.org>" exit end opts.on_tail("-h", "--help", "this helpful list of options") do puts opts exit end end
setup_output_options(opts)
click to toggle source
# File lib/theseus/cli.rb, line 239 def setup_output_options(opts) opts.separator "" opts.separator "Output options:" opts.on("-a", "--[no-]animate", "emit frames for each step") do |v| @animate = v end opts.on("-D", "--delay N", Integer, "time to wait between animation frames, in ms, default is #{@delay}") do |d| @delay = d end opts.on("-o", "--output FILE", "where to save the file(s) (for png only)") do |f| @output = f end opts.on("-f", "--format FMT", "png, ascii (default #{@format})") do |f| @format = f.to_sym end opts.on("-V", "--solve [METHOD]", "whether to display the solution of the maze.", "METHOD is either `backtracker' (the default) or `astar'") do |s| @solution = (s || :backtracker).to_sym end end
setup_required_options(opts)
click to toggle source
# File lib/theseus/cli.rb, line 219 def setup_required_options(opts) opts.separator "" opts.separator "Required options:" opts.on("-w", "--width N", Integer, "width of the maze (default 20, or mask width)") do |w| @maze_opts[:width] = w end opts.on("-H", "--height N", Integer, "height of the maze (default 20 or mask height)") do |h| @maze_opts[:height] = h end opts.on("-m", "--mask FILE", "png file to use as mask") do |m| case m when /^triangle:(\d+)$/ then @maze_opts[:mask] = Theseus::TriangleMask.new($1.to_i) else @maze_opts[:mask] = Theseus::Mask.from_png(m) end end end
show_maze()
click to toggle source
# File lib/theseus/cli.rb, line 92 def show_maze cursor_home puts @maze.to_s(:mode => :unicode) end
write_frame(step, options={})
click to toggle source
# File lib/theseus/cli.rb, line 108 def write_frame(step, options={}) f = "%s-%04d.png" % [@output, step] step += 1 File.open(f, "w") { |io| io.write(@maze.to(:png, @png_opts.merge(options))) } print "." end