lib/gamefic/dispatcher.rb in gamefic-3.0.0 vs lib/gamefic/dispatcher.rb in gamefic-3.1.0

- old
+ new

@@ -1,105 +1,112 @@ # frozen_string_literal: true module Gamefic - # The action selector for character commands. + # The action executor for character commands. # class Dispatcher # @param actor [Actor] - # @param commands [Array<Command>] - # @param responses [Array<Response>] - def initialize actor, commands = [], responses = [] + # @param command [Command] + def initialize actor, command @actor = actor - @commands = commands - @responses = responses + @command = command @executed = false + @finalized = false end # Run the dispatcher. # + # @return [Action, nil] def execute return if @executed - action = proceed + @executed = true + action = next_action return unless action - @executed = action.arguments run_before_action_hooks action return if action.cancelled? action.execute run_after_action_hooks action + action end - # Get the next executable action. + # Execute the next available action. # + # Actors should run #execute first. + # # @return [Action, nil] def proceed - while (response = responses.shift) - commands.each do |cmd| - action = response.attempt(actor, cmd) - next unless action && arguments_match?(action.arguments) + return unless @executed - return action - end - end - nil # Without this, return value in Opal is undefined + next_action&.execute end # @param actor [Active] # @param input [String] # @return [Dispatcher] def self.dispatch actor, input - commands = Syntax.tokenize(input, actor.epic.rulebooks.flat_map(&:syntaxes)) - verbs = commands.map(&:verb).uniq - responses = actor.epic - .rulebooks - .to_a - .reverse - .flat_map { |pb| pb.responses_for(*verbs) } - .reject(&:hidden?) - new(actor, commands, responses) + expressions = Syntax.tokenize(input, actor.epic.syntaxes) + command = Composer.compose(actor, expressions) + new(actor, command) end # @param actor [Active] # @param verb [Symbol] # @param params [Array<Object>] # @return [Dispatcher] def self.dispatch_from_params actor, verb, params command = Command.new(verb, params) - responses = actor.epic - .rulebooks - .to_a - .reverse - .flat_map { |pb| pb.responses_for(verb) } - new(actor, [command], responses) + new(actor, command) end protected # @return [Actor] attr_reader :actor - # @return [Array<Command>] - attr_reader :commands + # @return [Command] + attr_reader :command # @return [Array<Response>] - attr_reader :responses + def responses + @responses ||= actor.epic.responses_for(command.verb) + end private - # After the first action gets selected, subsequent actions need to use the - # same arguments. - # - def arguments_match? arguments - !@executed || arguments == @executed + # @return [Action, nil] + def next_action + while (response = responses.shift) + next if response.queries.length < @command.arguments.length + + return Action.new(actor, @command.arguments, response) if response.accept?(actor, @command) + end + finalize end + # @return [void] def run_before_action_hooks action actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_before_actions action } end + # @return [void] def run_after_action_hooks action actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_after_actions action } + end + + # If the dispatcher proceeds through all possible responses, it can fall + # back to a nil response as a catchall for commands that could not be + # completed. + # + # @return [Action, nil] + def finalize + return nil if @finalized + + @finalized = true + @command = Command.new(nil, ["#{command.verb} #{command.arguments.join(' ').strip}"]) + @responses = actor.epic.responses_for(nil) + next_action end end end