behavior_tree 0.1.6 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.rubocop.yml +1 -0
 - data/Gemfile.lock +2 -2
 - data/README.md +809 -23
 - data/Rakefile +77 -0
 - data/behavior_tree.gemspec +4 -4
 - data/lib/behavior_tree.rb +3 -3
 - data/lib/behavior_tree/builder.rb +15 -45
 - data/lib/behavior_tree/concerns/dsl/randomizer.rb +78 -0
 - data/lib/behavior_tree/concerns/dsl/registration.rb +35 -0
 - data/lib/behavior_tree/concerns/dsl/spell_checker.rb +2 -0
 - data/lib/behavior_tree/concerns/dsl/utils.rb +22 -0
 - data/lib/behavior_tree/concerns/tree_structure/printer.rb +31 -8
 - data/lib/behavior_tree/control_nodes/control_node_base.rb +0 -2
 - data/lib/behavior_tree/decorator_nodes/condition.rb +0 -1
 - data/lib/behavior_tree/decorator_nodes/inverter.rb +0 -2
 - data/lib/behavior_tree/decorator_nodes/repeat_times_base.rb +0 -4
 - data/lib/behavior_tree/decorator_nodes/repeater.rb +0 -2
 - data/lib/behavior_tree/errors.rb +8 -0
 - data/lib/behavior_tree/node_base.rb +22 -10
 - data/lib/behavior_tree/node_status.rb +1 -1
 - data/lib/behavior_tree/single_child_node.rb +0 -3
 - data/lib/behavior_tree/tasks/task_base.rb +0 -3
 - data/lib/behavior_tree/tree.rb +0 -3
 - data/lib/behavior_tree/version.rb +1 -1
 - metadata +11 -8
 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -3,6 +3,9 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require 'rubocop/rake_task'
         
     | 
| 
       4 
4 
     | 
    
         
             
            require 'bundler/gem_tasks'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'rspec/core/rake_task'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'behavior_tree'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'io/console'
         
     | 
| 
       6 
9 
     | 
    
         | 
| 
       7 
10 
     | 
    
         
             
            RSpec::Core::RakeTask.new(:spec)
         
     | 
| 
       8 
11 
     | 
    
         | 
| 
         @@ -12,3 +15,77 @@ desc 'Run RuboCop' 
     | 
|
| 
       12 
15 
     | 
    
         
             
            RuboCop::RakeTask.new(:lint) do |task|
         
     | 
| 
       13 
16 
     | 
    
         
             
              task.options = ['--fail-level', 'autocorrect']
         
     | 
| 
       14 
17 
     | 
    
         
             
            end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            desc 'Run RuboCop for specs'
         
     | 
| 
      
 20 
     | 
    
         
            +
            RuboCop::RakeTask.new(:spec_lint) do |task|
         
     | 
| 
      
 21 
     | 
    
         
            +
              task.requires << 'rubocop-rspec'
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            # Utils for the visualize Rake task.
         
     | 
| 
      
 25 
     | 
    
         
            +
            # NOTE: Don't use it for anything other than the Rake task.
         
     | 
| 
      
 26 
     | 
    
         
            +
            class VisualizeUtils
         
     | 
| 
      
 27 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 28 
     | 
    
         
            +
                def random_seed
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @random_seed ||= if ENV['random_seed'].nil?
         
     | 
| 
      
 30 
     | 
    
         
            +
                                     (Time.now.to_f * 1000).to_i ^ Process.pid
         
     | 
| 
      
 31 
     | 
    
         
            +
                                   else
         
     | 
| 
      
 32 
     | 
    
         
            +
                                     ENV['random_seed'].to_i
         
     | 
| 
      
 33 
     | 
    
         
            +
                                   end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def sleep_time
         
     | 
| 
      
 37 
     | 
    
         
            +
                  result = ENV['sleep'].nil? ? 0.5 : ENV['sleep'].to_f
         
     | 
| 
      
 38 
     | 
    
         
            +
                  raise 'Sleep time must be a positive float value' if result <= 0
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  result
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def print_random_seed_info
         
     | 
| 
      
 44 
     | 
    
         
            +
                  puts ''
         
     | 
| 
      
 45 
     | 
    
         
            +
                  puts "Generate the same tree by adding: random_seed=#{random_seed}"
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def random_tree
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @random_tree ||= BehaviorTree::Builder.build_random_tree(recursion_amount: 2)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def update_and_draw
         
     | 
| 
      
 53 
     | 
    
         
            +
                  $stdout.clear_screen
         
     | 
| 
      
 54 
     | 
    
         
            +
                  random_tree.print
         
     | 
| 
      
 55 
     | 
    
         
            +
                  print_random_seed_info
         
     | 
| 
      
 56 
     | 
    
         
            +
                  random_tree.tick!
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def setup_console
         
     | 
| 
      
 60 
     | 
    
         
            +
                  srand(random_seed)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  $stdout.sync = true
         
     | 
| 
      
 62 
     | 
    
         
            +
                  $stdout.clear_screen
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  trap 'INT' do
         
     | 
| 
      
 65 
     | 
    
         
            +
                    puts ''
         
     | 
| 
      
 66 
     | 
    
         
            +
                    exit 0
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
            end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            namespace :visualize do
         
     | 
| 
      
 73 
     | 
    
         
            +
              desc 'Visualize a random tree animated (optional parameters sleep=0.5 random_seed=12345678)'
         
     | 
| 
      
 74 
     | 
    
         
            +
              task :auto do
         
     | 
| 
      
 75 
     | 
    
         
            +
                VisualizeUtils.setup_console
         
     | 
| 
      
 76 
     | 
    
         
            +
                loop do
         
     | 
| 
      
 77 
     | 
    
         
            +
                  VisualizeUtils.update_and_draw
         
     | 
| 
      
 78 
     | 
    
         
            +
                  sleep VisualizeUtils.sleep_time
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              desc 'Visualize a random tree, press key to tick (optional parameter random_seed=12345678)'
         
     | 
| 
      
 83 
     | 
    
         
            +
              task :manual do
         
     | 
| 
      
 84 
     | 
    
         
            +
                VisualizeUtils.setup_console
         
     | 
| 
      
 85 
     | 
    
         
            +
                loop do
         
     | 
| 
      
 86 
     | 
    
         
            +
                  VisualizeUtils.update_and_draw
         
     | 
| 
      
 87 
     | 
    
         
            +
                  puts 'Press Enter key to tick. Press CTRL+C (SIGINT) to exit.'
         
     | 
| 
      
 88 
     | 
    
         
            +
                  $stdin.getc
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
            end
         
     | 
    
        data/behavior_tree.gemspec
    CHANGED
    
    | 
         @@ -3,13 +3,13 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require_relative 'lib/behavior_tree/version'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            Gem::Specification.new do |spec|
         
     | 
| 
       6 
     | 
    
         
            -
              spec.name          = 'behavior_tree' 
     | 
| 
      
 6 
     | 
    
         
            +
              spec.name          = 'behavior_tree'
         
     | 
| 
       7 
7 
     | 
    
         
             
              spec.version       = BehaviorTree::VERSION
         
     | 
| 
       8 
8 
     | 
    
         
             
              spec.authors       = ['Felo Vilches']
         
     | 
| 
       9 
9 
     | 
    
         
             
              spec.email         = ['felovilches@gmail.com']
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
              spec.summary       = ' 
     | 
| 
       12 
     | 
    
         
            -
              spec.homepage      = 'https://github.com/FeloVilches/Ruby-Behavior-Tree' 
     | 
| 
      
 11 
     | 
    
         
            +
              spec.summary       = 'A robust and customizable Ruby gem for creating Behavior Trees.'
         
     | 
| 
      
 12 
     | 
    
         
            +
              spec.homepage      = 'https://github.com/FeloVilches/Ruby-Behavior-Tree'
         
     | 
| 
       13 
13 
     | 
    
         
             
              spec.license       = 'MIT'
         
     | 
| 
       14 
14 
     | 
    
         
             
              spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
         @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       19 
19 
     | 
    
         
             
              # spec.metadata['changelog_uri'] = 'TODO: Put your gem's CHANGELOG.md URL here.'
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
              spec.files = Dir.chdir(File.expand_path(__dir__)) do
         
     | 
| 
       22 
     | 
    
         
            -
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
         
     | 
| 
      
 22 
     | 
    
         
            +
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|assets)/}) }
         
     | 
| 
       23 
23 
     | 
    
         
             
              end
         
     | 
| 
       24 
24 
     | 
    
         
             
              spec.bindir        = 'exe'
         
     | 
| 
       25 
25 
     | 
    
         
             
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         
     | 
    
        data/lib/behavior_tree.rb
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            # Gem.find_files('behavior_tree/**/*.rb').each { |path| require path }
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'set'
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
            # Load all files from lib.
         
     | 
| 
      
 6 
     | 
    
         
            +
            Dir[File.join(__dir__, 'behavior_tree', '**', '*.rb')].sort.each { |file| require_relative file }
         
     | 
| 
         @@ -2,6 +2,9 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require_relative './concerns/dsl/spell_checker'
         
     | 
| 
       4 
4 
     | 
    
         
             
            require_relative './concerns/dsl/initial_config'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative './concerns/dsl/randomizer'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative './concerns/dsl/registration'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative './concerns/dsl/utils'
         
     | 
| 
       5 
8 
     | 
    
         | 
| 
       6 
9 
     | 
    
         
             
            module BehaviorTree
         
     | 
| 
       7 
10 
     | 
    
         
             
              # DSL for building a tree.
         
     | 
| 
         @@ -10,6 +13,9 @@ module BehaviorTree 
     | 
|
| 
       10 
13 
     | 
    
         
             
                class << self
         
     | 
| 
       11 
14 
     | 
    
         
             
                  include Dsl::SpellChecker
         
     | 
| 
       12 
15 
     | 
    
         
             
                  include Dsl::InitialConfig
         
     | 
| 
      
 16 
     | 
    
         
            +
                  include Dsl::Randomizer
         
     | 
| 
      
 17 
     | 
    
         
            +
                  include Dsl::Registration
         
     | 
| 
      
 18 
     | 
    
         
            +
                  include Dsl::Utils
         
     | 
| 
       13 
19 
     | 
    
         | 
| 
       14 
20 
     | 
    
         
             
                  def build(&block)
         
     | 
| 
       15 
21 
     | 
    
         
             
                    # Stack of lists. When a method like 'sequence' is executed, the resulting
         
     | 
| 
         @@ -27,33 +33,6 @@ module BehaviorTree 
     | 
|
| 
       27 
33 
     | 
    
         
             
                    BehaviorTree::Tree.new tree_main_nodes.first
         
     | 
| 
       28 
34 
     | 
    
         
             
                  end
         
     | 
| 
       29 
35 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                  # Don't validate class_name, because in some situations the user wants it to be evaluated
         
     | 
| 
       31 
     | 
    
         
            -
                  # in runtime.
         
     | 
| 
       32 
     | 
    
         
            -
                  def register(node_name, class_name, children: :none)
         
     | 
| 
       33 
     | 
    
         
            -
                    valid_children_values = %i[none single multiple]
         
     | 
| 
       34 
     | 
    
         
            -
                    raise "Children value must be in: #{valid_children_values}" unless valid_children_values.include?(children)
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                    node_name = node_name.to_sym
         
     | 
| 
       37 
     | 
    
         
            -
                    raise RegisterDSLNodeAlreadyExistsError, node_name if @node_type_mapping.key?(node_name)
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                    @node_type_mapping[node_name] = {
         
     | 
| 
       40 
     | 
    
         
            -
                      class:    class_name,
         
     | 
| 
       41 
     | 
    
         
            -
                      children: children
         
     | 
| 
       42 
     | 
    
         
            -
                    }
         
     | 
| 
       43 
     | 
    
         
            -
                  end
         
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                  def register_alias(original, alias_key)
         
     | 
| 
       46 
     | 
    
         
            -
                    unless @node_type_mapping.key?(original)
         
     | 
| 
       47 
     | 
    
         
            -
                      raise "Cannot register alias for '#{original}', since it doesn't exist."
         
     | 
| 
       48 
     | 
    
         
            -
                    end
         
     | 
| 
       49 
     | 
    
         
            -
                    raise RegisterDSLNodeAlreadyExistsError, alias_key if @node_type_mapping.key?(alias_key)
         
     | 
| 
       50 
     | 
    
         
            -
                    raise 'Alias key cannot be empty' if alias_key.to_s.empty?
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                    @node_type_mapping[original][:alias] = alias_key
         
     | 
| 
       53 
     | 
    
         
            -
                    @node_type_mapping[alias_key] = @node_type_mapping[original].dup
         
     | 
| 
       54 
     | 
    
         
            -
                    @node_type_mapping[alias_key][:alias] = original
         
     | 
| 
       55 
     | 
    
         
            -
                  end
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
36 
     | 
    
         
             
                  private
         
     | 
| 
       58 
37 
     | 
    
         | 
| 
       59 
38 
     | 
    
         
             
                  def stack(obj)
         
     | 
| 
         @@ -79,26 +58,17 @@ module BehaviorTree 
     | 
|
| 
       79 
58 
     | 
    
         
             
                    @node_type_mapping.key? method_name
         
     | 
| 
       80 
59 
     | 
    
         
             
                  end
         
     | 
| 
       81 
60 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
                   
     | 
| 
       83 
     | 
    
         
            -
                  # It returns the class itself if it's already a class.
         
     | 
| 
       84 
     | 
    
         
            -
                  # @param class_name [String]
         
     | 
| 
       85 
     | 
    
         
            -
                  # @return [Class]
         
     | 
| 
       86 
     | 
    
         
            -
                  def constantize(class_name)
         
     | 
| 
       87 
     | 
    
         
            -
                    return class_name if class_name.is_a?(Class)
         
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
                    class_name.split('::').compact.inject(Object) { |o, c| o.const_get c }
         
     | 
| 
       90 
     | 
    
         
            -
                  rescue NameError
         
     | 
| 
       91 
     | 
    
         
            -
                    nil
         
     | 
| 
       92 
     | 
    
         
            -
                  end
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                  def dynamic_method_with_children(node_class, children, args, block)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  def exec_with_children(node_class, children_type, args, block)
         
     | 
| 
       95 
62 
     | 
    
         
             
                    stack_children_from_block(block)
         
     | 
| 
       96 
     | 
    
         
            -
                     
     | 
| 
       97 
     | 
    
         
            -
                     
     | 
| 
      
 63 
     | 
    
         
            +
                    children_nodes = @stack.pop
         
     | 
| 
      
 64 
     | 
    
         
            +
                    raise DSLStandardError, "Node #{node_class} has no children." if children_nodes.empty?
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    final_args = [children_nodes] + args # @stack.pop is already an Array
         
     | 
| 
      
 67 
     | 
    
         
            +
                    final_args.flatten! unless children_type == :multiple
         
     | 
| 
       98 
68 
     | 
    
         
             
                    stack node_class.new(*final_args)
         
     | 
| 
       99 
69 
     | 
    
         
             
                  end
         
     | 
| 
       100 
70 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
                  def  
     | 
| 
      
 71 
     | 
    
         
            +
                  def exec_leaf(node_class, args, block)
         
     | 
| 
       102 
72 
     | 
    
         
             
                    stack node_class.new(*args, &block)
         
     | 
| 
       103 
73 
     | 
    
         
             
                  end
         
     | 
| 
       104 
74 
     | 
    
         | 
| 
         @@ -114,9 +84,9 @@ module BehaviorTree 
     | 
|
| 
       114 
84 
     | 
    
         | 
| 
       115 
85 
     | 
    
         
             
                    # Nodes that have children are executed differently from leaf nodes.
         
     | 
| 
       116 
86 
     | 
    
         
             
                    if children == :none
         
     | 
| 
       117 
     | 
    
         
            -
                       
     | 
| 
      
 87 
     | 
    
         
            +
                      exec_leaf(node_class, args, block)
         
     | 
| 
       118 
88 
     | 
    
         
             
                    else
         
     | 
| 
       119 
     | 
    
         
            -
                       
     | 
| 
      
 89 
     | 
    
         
            +
                      exec_with_children(node_class, children, args, block)
         
     | 
| 
       120 
90 
     | 
    
         
             
                    end
         
     | 
| 
       121 
91 
     | 
    
         
             
                  end
         
     | 
| 
       122 
92 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module BehaviorTree
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Dsl
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Generates random trees.
         
     | 
| 
      
 6 
     | 
    
         
            +
                module Randomizer
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def build_random_tree(recursion_amount: 10)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    raise ArgumentError, 'Recursion amount must be greater than 0' if recursion_amount < 1
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    build do
         
     | 
| 
      
 11 
     | 
    
         
            +
                      send(%i[sel seq].sample) do
         
     | 
| 
      
 12 
     | 
    
         
            +
                        rand(3..5).times { recurse(recursion_amount).() }
         
     | 
| 
      
 13 
     | 
    
         
            +
                      end
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def recurse(recursions_left)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return random_leaf_blocks.sample if recursions_left.zero?
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    recursions_left -= 1
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    # Repeated values in order to increase the weight for some type of nodes.
         
     | 
| 
      
 25 
     | 
    
         
            +
                    %i[
         
     | 
| 
      
 26 
     | 
    
         
            +
                      control decorated condition
         
     | 
| 
      
 27 
     | 
    
         
            +
                      control decorated condition
         
     | 
| 
      
 28 
     | 
    
         
            +
                      leaf
         
     | 
| 
      
 29 
     | 
    
         
            +
                    ].map { |type| send("random_#{type}_blocks", recursions_left) }
         
     | 
| 
      
 30 
     | 
    
         
            +
                      .concat
         
     | 
| 
      
 31 
     | 
    
         
            +
                      .flatten
         
     | 
| 
      
 32 
     | 
    
         
            +
                      .sample
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def random_control_blocks(recursions_left)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    [
         
     | 
| 
      
 37 
     | 
    
         
            +
                      proc { sel { rand(2..3).times { recurse(recursions_left).() } } },
         
     | 
| 
      
 38 
     | 
    
         
            +
                      proc { seq { rand(2..3).times { recurse(recursions_left).() } } }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def random_decorated_blocks(recursions_left)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    [
         
     | 
| 
      
 44 
     | 
    
         
            +
                      proc { force_success(&recurse(recursions_left)) },
         
     | 
| 
      
 45 
     | 
    
         
            +
                      proc { force_failure(&recurse(recursions_left)) },
         
     | 
| 
      
 46 
     | 
    
         
            +
                      proc { inv(&recurse(recursions_left)) },
         
     | 
| 
      
 47 
     | 
    
         
            +
                      proc { re_try(20, &recurse(recursions_left)) },
         
     | 
| 
      
 48 
     | 
    
         
            +
                      proc { repeater(20, &recurse(recursions_left)) }
         
     | 
| 
      
 49 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def random_condition_blocks(recursions_left)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    [
         
     | 
| 
      
 54 
     | 
    
         
            +
                      proc {
         
     | 
| 
      
 55 
     | 
    
         
            +
                        cond(-> { rand > 0.2 }, &recurse(recursions_left))
         
     | 
| 
      
 56 
     | 
    
         
            +
                      },
         
     | 
| 
      
 57 
     | 
    
         
            +
                      proc {
         
     | 
| 
      
 58 
     | 
    
         
            +
                        cond(-> { rand > 0.8 }, &recurse(recursions_left))
         
     | 
| 
      
 59 
     | 
    
         
            +
                      }
         
     | 
| 
      
 60 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def random_leaf_blocks(_recursions_left = nil)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    task = proc do
         
     | 
| 
      
 65 
     | 
    
         
            +
                      task do
         
     | 
| 
      
 66 
     | 
    
         
            +
                        # Weights.
         
     | 
| 
      
 67 
     | 
    
         
            +
                        running_w = 3
         
     | 
| 
      
 68 
     | 
    
         
            +
                        success_w = 1
         
     | 
| 
      
 69 
     | 
    
         
            +
                        failure_w = 2
         
     | 
| 
      
 70 
     | 
    
         
            +
                        new_status = (([:running] * running_w) + ([:success] * success_w) + ([:failure] * failure_w)).sample
         
     | 
| 
      
 71 
     | 
    
         
            +
                        status.send("#{new_status}!")
         
     | 
| 
      
 72 
     | 
    
         
            +
                      end
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    [task]
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module BehaviorTree
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Dsl
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Register DSL commands.
         
     | 
| 
      
 6 
     | 
    
         
            +
                module Registration
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # Don't validate class_name, because in some situations the user wants it to be evaluated
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # in runtime.
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def register(node_name, class_name, children: :none)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    valid_children_values = %i[none single multiple]
         
     | 
| 
      
 11 
     | 
    
         
            +
                    raise "Children value must be in: #{valid_children_values}" unless valid_children_values.include?(children)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    node_name = node_name.to_sym
         
     | 
| 
      
 14 
     | 
    
         
            +
                    raise RegisterDSLNodeAlreadyExistsError, node_name if @node_type_mapping.key?(node_name)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    @node_type_mapping[node_name] = {
         
     | 
| 
      
 17 
     | 
    
         
            +
                      class:    class_name,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      children: children
         
     | 
| 
      
 19 
     | 
    
         
            +
                    }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def register_alias(original, alias_key)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    unless @node_type_mapping.key?(original)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      raise "Cannot register alias for '#{original}', since it doesn't exist."
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    raise RegisterDSLNodeAlreadyExistsError, alias_key if @node_type_mapping.key?(alias_key)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    raise 'Alias key cannot be empty' if alias_key.to_s.empty?
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    @node_type_mapping[original][:alias] = alias_key
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @node_type_mapping[alias_key] = @node_type_mapping[original].dup
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @node_type_mapping[alias_key][:alias] = original
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -4,6 +4,8 @@ module BehaviorTree 
     | 
|
| 
       4 
4 
     | 
    
         
             
              module Dsl
         
     | 
| 
       5 
5 
     | 
    
         
             
                # Helpers for spellchecking, and correcting user input in the DSL builder.
         
     | 
| 
       6 
6 
     | 
    
         
             
                module SpellChecker
         
     | 
| 
      
 7 
     | 
    
         
            +
                  private
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       7 
9 
     | 
    
         
             
                  def raise_node_type_not_exists(missing_method)
         
     | 
| 
       8 
10 
     | 
    
         
             
                    suggestion = most_similar_name missing_method
         
     | 
| 
       9 
11 
     | 
    
         
             
                    method_alias = @node_type_mapping.dig suggestion, :alias
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module BehaviorTree
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Dsl
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Helpers for DSL.
         
     | 
| 
      
 6 
     | 
    
         
            +
                module Utils
         
     | 
| 
      
 7 
     | 
    
         
            +
                  private
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  # Convert a class name with namespace into a constant.
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # It returns the class itself if it's already a class.
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @param class_name [String]
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @return [Class]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def constantize(class_name)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    return class_name if class_name.is_a?(Class)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    class_name.split('::').compact.inject(Object) { |o, c| o.const_get c }
         
     | 
| 
      
 17 
     | 
    
         
            +
                  rescue NameError
         
     | 
| 
      
 18 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -7,12 +7,16 @@ module BehaviorTree 
     | 
|
| 
       7 
7 
     | 
    
         
             
                # Algorithm to print tree.
         
     | 
| 
       8 
8 
     | 
    
         
             
                module Printer
         
     | 
| 
       9 
9 
     | 
    
         
             
                  def print
         
     | 
| 
       10 
     | 
    
         
            -
                     
     | 
| 
       11 
     | 
    
         
            -
                     
     | 
| 
       12 
     | 
    
         
            -
                     
     | 
| 
       13 
     | 
    
         
            -
                     
     | 
| 
       14 
     | 
    
         
            -
                     
     | 
| 
       15 
     | 
    
         
            -
                     
     | 
| 
      
 10 
     | 
    
         
            +
                    lines = []
         
     | 
| 
      
 11 
     | 
    
         
            +
                    lines << '∅' # Style for the root node.
         
     | 
| 
      
 12 
     | 
    
         
            +
                    lines += tree_lines
         
     | 
| 
      
 13 
     | 
    
         
            +
                    lines << ''
         
     | 
| 
      
 14 
     | 
    
         
            +
                    lines << cycle_string
         
     | 
| 
      
 15 
     | 
    
         
            +
                    lines << uniq_nodes_string
         
     | 
| 
      
 16 
     | 
    
         
            +
                    lines << size_string
         
     | 
| 
      
 17 
     | 
    
         
            +
                    lines << tree_tick_count_string
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    puts lines.join "\n"
         
     | 
| 
       16 
20 
     | 
    
         
             
                  end
         
     | 
| 
       17 
21 
     | 
    
         | 
| 
       18 
22 
     | 
    
         
             
                  private
         
     | 
| 
         @@ -27,7 +31,7 @@ module BehaviorTree 
     | 
|
| 
       27 
31 
     | 
    
         | 
| 
       28 
32 
     | 
    
         
             
                      last_child ? vertical_lines_continues.delete(depth) : vertical_lines_continues << depth
         
     | 
| 
       29 
33 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                      space = (0...depth).map { |d| vertical_lines_continues.include?(d) ? '│ 
     | 
| 
      
 34 
     | 
    
         
            +
                      space = (0...depth).map { |d| vertical_lines_continues.include?(d) ? '│     ' : '      ' }.join
         
     | 
| 
       31 
35 
     | 
    
         
             
                      connector = last_child ? '└─' : '├─'
         
     | 
| 
       32 
36 
     | 
    
         | 
| 
       33 
37 
     | 
    
         
             
                      "#{space}#{connector}#{class_simple_name(node)} #{status_string(node)} #{tick_count_string(node)}"
         
     | 
| 
         @@ -66,6 +70,10 @@ module BehaviorTree 
     | 
|
| 
       66 
70 
     | 
    
         
             
                    ColorizedString["(#{node.tick_count} ticks)"].colorize(color)
         
     | 
| 
       67 
71 
     | 
    
         
             
                  end
         
     | 
| 
       68 
72 
     | 
    
         | 
| 
      
 73 
     | 
    
         
            +
                  def tree_tick_count_string
         
     | 
| 
      
 74 
     | 
    
         
            +
                    "Tree has been ticked #{tick_count} times."
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
       69 
77 
     | 
    
         
             
                  # Copied from Rails' ActiveSupport.
         
     | 
| 
       70 
78 
     | 
    
         
             
                  def snake_case(str)
         
     | 
| 
       71 
79 
     | 
    
         
             
                    str.gsub(/::/, '/')
         
     | 
| 
         @@ -76,7 +84,22 @@ module BehaviorTree 
     | 
|
| 
       76 
84 
     | 
    
         
             
                  end
         
     | 
| 
       77 
85 
     | 
    
         | 
| 
       78 
86 
     | 
    
         
             
                  def class_simple_name(node)
         
     | 
| 
       79 
     | 
    
         
            -
                    snake_case(node.class.name.split('::').last)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    pretty_name snake_case(node.class.name.split('::').last)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  # Changes the name of some classes (maps it to a better name).
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # Mapping is simply based on taste.
         
     | 
| 
      
 92 
     | 
    
         
            +
                  def pretty_name(name)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    case name
         
     | 
| 
      
 94 
     | 
    
         
            +
                    when 'task_base'
         
     | 
| 
      
 95 
     | 
    
         
            +
                      'task'
         
     | 
| 
      
 96 
     | 
    
         
            +
                    when 'force_success'
         
     | 
| 
      
 97 
     | 
    
         
            +
                      'forcesuccess'
         
     | 
| 
      
 98 
     | 
    
         
            +
                    when 'force_failure'
         
     | 
| 
      
 99 
     | 
    
         
            +
                      'forcefailure'
         
     | 
| 
      
 100 
     | 
    
         
            +
                    else
         
     | 
| 
      
 101 
     | 
    
         
            +
                      name
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
       80 
103 
     | 
    
         
             
                  end
         
     | 
| 
       81 
104 
     | 
    
         
             
                end
         
     | 
| 
       82 
105 
     | 
    
         
             
              end
         
     |