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