gamefic 2.0.3 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ac7da649347939f4c5baadc676151b8a8c73714552c48a290de75bd072bb801
4
- data.tar.gz: 6d83150ed62df24a4056e3e54ff5b499bc5f7a69ebe8d5cfaddb261f64573af4
3
+ metadata.gz: daf118f55bd232d6f2025016b109da61fa5fdb8b15b2ca32c3d43e45b42fa0c1
4
+ data.tar.gz: 12fcfc441c70b972b87a50f2a056ad6fd9fadcd0dae2eefd276a86fdf31ba26a
5
5
  SHA512:
6
- metadata.gz: e068317caa9fbfb1f8a73bde4725f5df3f3bcad95ba87d44c8f973759cdc991e0fbbcea874fd451eacbd6ffd2ea2ade9144e61f2d358f31fec6a7f1e97ca3a10
7
- data.tar.gz: 1ab813a5cb14307bc844c42424c9503bae6f73a48d12979a5369812619e5a2e80dfab2ac6181401045511286743fc20f35968bd46e5071cf24f296e3c1c20e36
6
+ metadata.gz: 5ab94298bc3c7bfc03e688a4dea078c2317002c2da7c73d529a61356931b25f8f86b4fd154e75bd8e1fd515aeabb1fcbef4cf24737968f570cc850ee2d08f473
7
+ data.tar.gz: aa069d35340b8372c67988a106281fc185241e888293519a878ca4811c1d8976ccec4315772b32ce095f8be2a7be332b3e9ad5b25aef2cb5d9e230f5b7b69fca
data/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
- # 2.0.3 - December 14, 2020
1
+ ## 2.2.1 - September 5, 2021
2
+ - Retain unknown values in restored snapshots
3
+
4
+ ## 2.2.0 - September 4, 2021
5
+ - Dynamically inherit default attributes
6
+
7
+ ## 2.1.1 - July 23, 2021
8
+ - Remove gamefic/scene/custom autoload
9
+
10
+ ## 2.1.0 - June 21, 2021
11
+ - Remove redundant MultipleChoice prompt
12
+ - Deprecate Scene::Custom
13
+
14
+ ## 2.0.3 - December 14, 2020
2
15
  - Remove unused Index class
3
16
  - Active#conclude accepts data argument
4
17
 
5
- # 2.0.2 - April 25, 2020
18
+ ## 2.0.2 - April 25, 2020
6
19
  - Improved snapshot serialization
@@ -123,10 +123,8 @@ module Gamefic
123
123
 
124
124
  def valid? actor, objects
125
125
  return false if objects.length != queries.length
126
- i = 0
127
- queries.each do |p|
126
+ queries.each_with_index do |p, i|
128
127
  return false unless p.include?(actor, objects[i])
129
- i += 1
130
128
  end
131
129
  true
132
130
  end
@@ -135,9 +133,11 @@ module Gamefic
135
133
  # provided tokens, or nil if the tokens are invalid.
136
134
  #
137
135
  # @param action [Gamefic::Entity]
138
- # @param tokens [Array<String>]
136
+ # @param command [Command]
139
137
  # @return [self, nil]
140
- def attempt actor, tokens
138
+ def attempt actor, command
139
+ return nil if command.verb != verb
140
+ tokens = command.arguments
141
141
  result = []
142
142
  matches = Gamefic::Query::Matches.new([], '', '')
143
143
  queries.each_with_index do |p, i|
@@ -6,12 +6,6 @@ module Gamefic
6
6
  # subclass that includes this module.
7
7
  #
8
8
  module Active
9
- # The last action executed by the entity, as reported by the
10
- # Active#performed method.
11
- #
12
- # @return [Gamefic::Action]
13
- attr_reader :last_action
14
-
15
9
  # The scene in which the entity is currently participating.
16
10
  #
17
11
  # @return [Gamefic::Scene::Base]
@@ -104,17 +98,29 @@ module Gamefic
104
98
  # @example Send a command as a verb with parameters
105
99
  # character.perform :take, @key
106
100
  #
101
+ # @todo Modify this method so it only accepts a single command.
102
+ # Verbs with parameters should use Active#execute instead.
103
+ # It might be necessary to support command splats with a deprecation
104
+ # warning until version 3.
105
+ #
107
106
  # @return [Gamefic::Action]
108
107
  def perform(*command)
109
- actions = []
110
- playbooks.reverse.each { |p| actions.concat p.dispatch(self, *command) }
111
- execute_stack actions
108
+ if command.length > 1
109
+ execute command.first, *command[1..-1]
110
+ else
111
+ dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
112
+ proceed
113
+ dispatchers.pop
114
+ end
112
115
  end
113
116
 
114
117
  # Quietly perform a command.
115
118
  # This method executes the command exactly as #perform does, except it
116
119
  # buffers the resulting output instead of sending it to the user.
117
120
  #
121
+ # @todo Modify this method so it only accepts a single command.
122
+ # See Active#perform for more information.
123
+ #
118
124
  # @return [String] The output that resulted from performing the command.
119
125
  def quietly(*command)
120
126
  clear_buffer if buffer_stack == 0
@@ -137,9 +143,9 @@ module Gamefic
137
143
  #
138
144
  # @return [Gamefic::Action]
139
145
  def execute(verb, *params, quietly: false)
140
- actions = []
141
- playbooks.reverse.each { |p| actions.concat p.dispatch_from_params(self, verb, params) }
142
- execute_stack actions, quietly: quietly
146
+ dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
147
+ proceed quietly: quietly
148
+ dispatchers.pop
143
149
  end
144
150
 
145
151
  # Proceed to the next Action in the current stack.
@@ -166,20 +172,19 @@ module Gamefic
166
172
  # end
167
173
  #
168
174
  def proceed quietly: false
169
- return if performance_stack.empty?
170
- a = performance_stack.last.shift
171
- unless a.nil?
172
- if quietly
173
- if buffer_stack == 0
174
- @buffer = ""
175
- end
176
- set_buffer_stack(buffer_stack + 1)
177
- end
178
- a.execute
179
- if quietly
180
- set_buffer_stack(buffer_stack - 1)
181
- @buffer
175
+ return if dispatchers.empty?
176
+ a = dispatchers.last.next
177
+ return if a.nil?
178
+ if quietly
179
+ if buffer_stack == 0
180
+ @buffer = ""
182
181
  end
182
+ set_buffer_stack(buffer_stack + 1)
183
+ end
184
+ a.execute
185
+ if quietly
186
+ set_buffer_stack(buffer_stack - 1)
187
+ @buffer
183
188
  end
184
189
  end
185
190
 
@@ -235,14 +240,6 @@ module Gamefic
235
240
  !scene.nil? && scene.kind_of?(Scene::Conclusion)
236
241
  end
237
242
 
238
- # Record the last action the entity executed. This method is typically
239
- # called when the entity performs an action in response to user input.
240
- #
241
- def performed action
242
- action.freeze
243
- @last_action = action
244
- end
245
-
246
243
  def accessible?
247
244
  false
248
245
  end
@@ -270,37 +267,7 @@ module Gamefic
270
267
 
271
268
  # @return [Array<Gamefic::Scene::Base>]
272
269
  def entered_scenes
273
- @entered_scenes ||= []
274
- end
275
-
276
- # @param actions [Array<Gamefic::Action>]
277
- # @param quietly [Boolean]
278
- def execute_stack actions, quietly: false
279
- return nil if actions.empty?
280
- a = actions.first
281
- okay = true
282
- unless a.meta?
283
- playbooks.reverse.each do |playbook|
284
- okay = validate_playbook playbook, a
285
- break unless okay
286
- end
287
- end
288
- if okay
289
- performance_stack.push actions
290
- proceed quietly: quietly
291
- performance_stack.pop
292
- end
293
- a
294
- end
295
-
296
- def validate_playbook playbook, action
297
- okay = true
298
- playbook.validators.each { |v|
299
- result = v.call(self, action.verb, action.parameters)
300
- okay = (result != false)
301
- break unless okay
302
- }
303
- okay
270
+ @entered_scenes ||= []
304
271
  end
305
272
 
306
273
  def buffer_stack
@@ -324,8 +291,8 @@ module Gamefic
324
291
  @buffer = ''
325
292
  end
326
293
 
327
- def performance_stack
328
- @performance_stack ||= []
294
+ def dispatchers
295
+ @dispatchers ||= []
329
296
  end
330
297
  end
331
298
  end
@@ -64,7 +64,7 @@ class Array
64
64
  join_and(sep, orSep, serial)
65
65
  end
66
66
 
67
- # private
67
+ private
68
68
 
69
69
  def _keep(arr, cls, bool)
70
70
  if (cls.kind_of?(Class) or cls.kind_of?(Module))
@@ -6,15 +6,9 @@ class String
6
6
  #
7
7
  # @return [String] The capitalized text
8
8
  def capitalize_first
9
- "#{self[0,1].upcase}#{self[1,self.length]}"
10
- end
11
-
12
- # An alias for #capitalize_first.
13
- #
14
- # @return [String]
15
- def cap_first
16
- self.capitalize_first
9
+ "#{self[0, 1].upcase}#{self[1, self.length]}"
17
10
  end
11
+ alias cap_first capitalize_first
18
12
 
19
13
  # Get an array of words split by any whitespace.
20
14
  #
@@ -0,0 +1,76 @@
1
+ module Gamefic
2
+ # The action selector for character commands.
3
+ #
4
+ class Dispatcher
5
+ # @param actor [Actor]
6
+ # @param commands [Array<Command>]
7
+ # @param actions [Array<Action>]
8
+ def initialize actor, commands = [], actions = []
9
+ @actor = actor
10
+ @commands = commands
11
+ @actions = actions
12
+ end
13
+
14
+ def merge dispatcher
15
+ commands.concat dispatcher.commands
16
+ actions.concat dispatcher.actions
17
+ end
18
+
19
+ def next
20
+ instance = nil
21
+ while instance.nil? && !@actions.empty?
22
+ action = actions.shift
23
+ commands.each do |cmd|
24
+ instance = action.attempt(actor, cmd)
25
+ if instance
26
+ unless instance.meta?
27
+ actor.playbooks.reverse.each do |playbook|
28
+ return nil unless validate_playbook(playbook, instance)
29
+ end
30
+ end
31
+ break
32
+ end
33
+ end
34
+ end
35
+ instance
36
+ end
37
+
38
+ # @param actor [Active]
39
+ # @param command [String]
40
+ # @return [Dispatcher]
41
+ def self.dispatch actor, command
42
+ group = actor.playbooks.reverse.map { |p| p.dispatch(actor, command) }
43
+ dispatcher = Dispatcher.new(actor)
44
+ group.each { |d| dispatcher.merge d }
45
+ dispatcher
46
+ end
47
+
48
+ # @param actor [Active]
49
+ # @param verb [Symbol]
50
+ # @param params [Array<Object>]
51
+ # @return [Dispatcher]
52
+ def self.dispatch_from_params actor, verb, params
53
+ group = actor.playbooks.reverse.map { |p| p.dispatch_from_params(actor, verb, params) }
54
+ dispatcher = Dispatcher.new(actor)
55
+ group.each { |d| dispatcher.merge d }
56
+ dispatcher
57
+ end
58
+
59
+ protected
60
+
61
+ # @return [Actor]
62
+ attr_reader :actor
63
+
64
+ # @return [Array<Command>]
65
+ attr_reader :commands
66
+
67
+ # @return [Array<Action>]
68
+ attr_reader :actions
69
+
70
+ private
71
+
72
+ def validate_playbook playbook, action
73
+ playbook.validators.all? { |v| v.call(actor, action.verb, action.parameters) != false }
74
+ end
75
+ end
76
+ end
@@ -7,13 +7,16 @@ module Gamefic
7
7
  #
8
8
  class Element
9
9
  include Gamefic::Describable
10
- # include Gamefic::Index
11
10
  include Gamefic::Serialize
12
11
 
13
- # @todo It would be nice if this initialization wasn't necessary.
14
12
  def initialize(args = {})
15
- # super self.class.default_attributes.merge(args)
16
- self.class.default_attributes.merge(args).each_pair do |k, v|
13
+ klass = self.class
14
+ defaults = {}
15
+ while klass <= Element
16
+ defaults = klass.default_attributes.merge(defaults)
17
+ klass = klass.superclass
18
+ end
19
+ defaults.merge(args).each_pair do |k, v|
17
20
  public_send "#{k}=", v
18
21
  end
19
22
  post_initialize
@@ -38,10 +41,6 @@ module Gamefic
38
41
  def default_attributes
39
42
  @default_attributes ||= {}
40
43
  end
41
-
42
- def inherited subclass
43
- subclass.set_default default_attributes
44
- end
45
44
  end
46
45
  end
47
46
  end
@@ -13,10 +13,10 @@ module Gamefic
13
13
 
14
14
  # Set the Entity's parent.
15
15
  #
16
- # @param node [Gamefic::Entity] The new parent.
16
+ # @param node [Gamefic::Entity, nil] The new parent.
17
17
  def parent=(node)
18
- if node != nil and node.kind_of?(Entity) == false
19
- raise "Entity's parent must be an Entity"
18
+ if node && node.is_a?(Entity) == false
19
+ raise ArgumentError, "Entity's parent must be an Entity"
20
20
  end
21
21
  super
22
22
  end
data/lib/gamefic/plot.rb CHANGED
@@ -74,7 +74,6 @@ module Gamefic
74
74
  entities.each { |e| e.flush }
75
75
  call_before_player_update
76
76
  players.each do |p|
77
- p.performed nil
78
77
  next unless p.scene
79
78
  p.last_input = p.queue.last
80
79
  p.last_prompt = p.scene.prompt
@@ -10,9 +10,7 @@ module Gamefic
10
10
 
11
11
  def finish
12
12
  super
13
- o = nil
14
- o = actor.perform input.strip unless input.to_s.strip.empty?
15
- actor.performed o
13
+ actor.perform input.strip unless input.to_s.strip.empty?
16
14
  end
17
15
 
18
16
  class << self
@@ -1,6 +1,8 @@
1
1
  module Gamefic
2
- # The Base Scene is not intended for instantiation. Other Scene classes
3
- # should inherit from it.
2
+ # An abstract class for building different types of scenes. It can be
3
+ # extended either through concrete subclasses or by creating anonymous
4
+ # subclasses through a scene helper method like
5
+ # `Gamefic::World::Scenes#custom`.
4
6
  #
5
7
  class Scene::Base
6
8
  include Gamefic::Serialize
@@ -1,7 +1,7 @@
1
1
  module Gamefic
2
2
  # A Conclusion ends the Plot (or the character's participation in it).
3
3
  #
4
- class Scene::Conclusion < Scene::Custom
4
+ class Scene::Conclusion < Scene::Base
5
5
  def type
6
6
  @type ||= 'Conclusion'
7
7
  end
@@ -6,7 +6,7 @@ module Gamefic
6
6
  # The finish block's input parameter receives a MultipleChoice::Input object
7
7
  # instead of a String.
8
8
  #
9
- class Scene::MultipleChoice < Scene::Custom
9
+ class Scene::MultipleChoice < Scene::Base
10
10
  # The zero-based index of the selected option.
11
11
  #
12
12
  # @return [Integer]
@@ -33,7 +33,6 @@ module Gamefic
33
33
  get_choice
34
34
  if selection.nil?
35
35
  actor.tell invalid_message
36
- tell_options
37
36
  else
38
37
  super
39
38
  end
@@ -77,14 +76,5 @@ module Gamefic
77
76
  }
78
77
  end
79
78
  end
80
-
81
- def tell_options
82
- list = '<ol class="multiple_choice">'
83
- options.each { |o|
84
- list += "<li><a href=\"#\" rel=\"gamefic\" data-command=\"#{o}\">#{o}</a></li>"
85
- }
86
- list += "</ol>"
87
- actor.tell list
88
- end
89
79
  end
90
80
  end
@@ -1,7 +1,7 @@
1
1
  module Gamefic
2
2
  # Pause for user input.
3
3
  #
4
- class Scene::Pause < Scene::Custom
4
+ class Scene::Pause < Scene::Base
5
5
  def post_initialize
6
6
  self.type = 'Pause'
7
7
  self.prompt = 'Press enter to continue...'
@@ -4,7 +4,7 @@ module Gamefic
4
4
  # block. After the scene is finished, the :active scene will be cued if no
5
5
  # other scene has been prepared or cued.
6
6
  #
7
- class Scene::YesOrNo < Scene::Custom
7
+ class Scene::YesOrNo < Scene::Base
8
8
  attr_writer :invalid_message
9
9
 
10
10
  def post_initialize
data/lib/gamefic/scene.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  module Gamefic
2
2
  module Scene
3
3
  autoload :Base, 'gamefic/scene/base'
4
- autoload :Custom, 'gamefic/scene/custom'
5
4
  autoload :Activity, 'gamefic/scene/activity'
6
5
  autoload :Pause, 'gamefic/scene/pause'
7
6
  autoload :Conclusion, 'gamefic/scene/conclusion'
@@ -32,13 +32,6 @@ module Gamefic
32
32
  end
33
33
  end
34
34
 
35
- def self.instances
36
- GC.start
37
- result = []
38
- ObjectSpace.each_object(Gamefic::Serialize) { |obj| result.push obj }
39
- result
40
- end
41
-
42
35
  # @param string [String]
43
36
  # @return [Object]
44
37
  def self.string_to_constant string
@@ -71,7 +64,7 @@ class Object
71
64
  end
72
65
 
73
66
  def from_serial(index = [])
74
- if self.is_a?(Hash) && (self['class'] || self['instance'])
67
+ if self.is_a?(Hash)
75
68
  if self['instance']
76
69
  elematch = self['instance'].match(/^#<ELE_([\d]+)>$/)
77
70
  object = index[elematch[1].to_i]
@@ -110,25 +103,7 @@ class Object
110
103
  return index.index(match[1].to_i) if match
111
104
  match = self.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
112
105
  return match[1].to_sym if match
113
- # return nil if self == '#<UNKNOWN>'
114
106
  self
115
- elsif self.is_a?(Hash)
116
- result = {}
117
- unknown = false
118
- self.each_pair do |k, v|
119
- k2 = k.from_serial(index)
120
- v2 = v.from_serial(index)
121
- if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
122
- unknown = true
123
- break
124
- end
125
- result[k2] = v2
126
- end
127
- result = "#<UNKNOWN>" if unknown
128
- result
129
- elsif self && self != true
130
- STDERR.puts "Unable to unserialize #{self.class}"
131
- nil
132
107
  else
133
108
  # true, false, or nil
134
109
  self
@@ -1,5 +1,3 @@
1
- require 'gamefic/command'
2
-
3
1
  module Gamefic
4
2
  class Syntax
5
3
  attr_reader :token_count, :first_word, :verb, :template, :command
@@ -57,17 +55,18 @@ module Gamefic
57
55
  m = text.match(@regexp)
58
56
  return nil if m.nil?
59
57
  arguments = []
60
- # HACK: Skip the first word if the verb is not nil.
61
- # This is ugly.
62
58
  b = @verb.nil? ? 0 : 1
59
+ xverb = @verb
63
60
  @replace.to_s.split_words[b..-1].each { |r|
64
61
  if r.match(/^\{\$[0-9]+\}$/)
65
62
  arguments.push m[r[2..-2].to_i]
63
+ elsif arguments.empty? && xverb.nil?
64
+ xverb = r.to_sym
66
65
  else
67
66
  arguments.push r
68
67
  end
69
68
  }
70
- Command.new @verb, arguments
69
+ Command.new xverb, arguments
71
70
  end
72
71
 
73
72
  # Determine if the specified text matches the syntax's expected pattern.
@@ -90,25 +89,32 @@ module Gamefic
90
89
  signature == other.signature
91
90
  end
92
91
 
93
- # Tokenize an Array of Commands from the specified text.
92
+ def eql?(other)
93
+ self == other
94
+ end
95
+
96
+ def hash
97
+ signature.hash
98
+ end
99
+
100
+ # Tokenize an Array of Commands from the specified text. The resulting
101
+ # array is in descending order of specificity, i.e., most to least matched
102
+ # tokens.
94
103
  #
95
104
  # @param text [String] The text to tokenize.
96
105
  # @param syntaxes [Array<Gamefic::Syntax>] The Syntaxes to use.
97
106
  # @return [Array<Gamefic::Command>] The tokenized commands.
98
107
  def self.tokenize text, syntaxes
99
- matches = []
100
- syntaxes.each { |syntax|
101
- result = syntax.tokenize text
102
- matches.push(result) if !result.nil?
103
- }
104
- matches.sort! { |a,b|
105
- if a.arguments.length == b.arguments.length
106
- b.verb.to_s <=> a.verb.to_s
107
- else
108
- b.arguments.length <=> a.arguments.length
108
+ syntaxes
109
+ .map { |syn| syn.tokenize(text) }
110
+ .reject(&:nil?)
111
+ .sort do |a, b|
112
+ if a.arguments.length == b.arguments.length
113
+ b.verb.to_s <=> a.verb.to_s
114
+ else
115
+ b.arguments.length <=> a.arguments.length
116
+ end
109
117
  end
110
- }
111
- matches
112
118
  end
113
119
  end
114
120
  end
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '2.0.3'
2
+ VERSION = '2.2.1'
3
3
  end
@@ -95,25 +95,6 @@ module Gamefic
95
95
  playbook.meta command, *queries, &proc
96
96
  end
97
97
 
98
- # Declare a dismabiguation response for actions.
99
- # The disambiguator is executed when an action expects an argument to
100
- # reference a specific entity but its query matched more than one. For
101
- # example, "red" might refer to either a red key or a red book.
102
- #
103
- # If a disambiguator is not defined, the playbook will use its default
104
- # implementation.
105
- #
106
- # @example Tell the player the list of ambiguous entities.
107
- # disambiguate do |actor, entities|
108
- # actor.tell "I don't know which you mean: #{entities.join_or}."
109
- # end
110
- #
111
- # @yieldparam [Gamefic::Actor]
112
- # @yieldparam [Array<Gamefic::Entity>]
113
- def disambiguate &block
114
- playbook.disambiguate &block
115
- end
116
-
117
98
  # Validate an order before a character can execute its command.
118
99
  #
119
100
  # @yieldparam [Gamefic::Director::Order]
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Gamefic
2
4
  module World
3
5
  # A collection of rules for performing commands.
@@ -13,11 +15,14 @@ module Gamefic
13
15
  # @return [Array<Proc>]
14
16
  attr_reader :validators
15
17
 
16
- def initialize commands: {}, syntaxes: [], validators: [], disambiguator: nil
18
+ # @param commands [Hash]
19
+ # @param syntaxes [Array<Syntax>, Set<Syntax>]
20
+ # @param validators [Array]
21
+ def initialize commands: {}, syntaxes: [], validators: []
17
22
  @commands = commands
18
- @syntaxes = syntaxes
23
+ @syntax_set = syntaxes.to_set
24
+ sort_syntaxes
19
25
  @validators = validators
20
- @disambiguator = disambiguator
21
26
  end
22
27
 
23
28
  # An array of available actions.
@@ -34,25 +39,6 @@ module Gamefic
34
39
  @commands.keys
35
40
  end
36
41
 
37
- # Get the action for handling ambiguous entity references.
38
- #
39
- def disambiguator
40
- @disambiguator ||= Action.subclass(nil, Query::Base.new) do |actor, entities|
41
- definites = []
42
- entities.each do |entity|
43
- definites.push entity.definitely
44
- end
45
- actor.tell "I don't know which you mean: #{definites.join_or}."
46
- end
47
- end
48
-
49
- # Set the action for handling ambiguous entity references.
50
- #
51
- def disambiguate &block
52
- @disambiguator = Action.subclass(nil, Query::Base.new, meta: true, &block)
53
- @disambiguator
54
- end
55
-
56
42
  # Add a block that determines whether an action can be executed.
57
43
  #
58
44
  def validate &block
@@ -138,35 +124,16 @@ module Gamefic
138
124
  syn
139
125
  end
140
126
 
141
- # Get an array of actions, derived from the specified command, that the
142
- # actor can potentially execute.
143
- # The command can either be a single string (e.g., "examine book") or a
144
- # list of tokens (e.g., :examine, @book).
145
- #
146
- # @return [Array<Gamefic::Action>]
147
- def dispatch(actor, *command)
148
- result = []
149
- result.concat dispatch_from_params(actor, command[0], command[1..-1]) if command.length > 1
150
- result.concat dispatch_from_string(actor, command.join(' ')) if result.empty?
151
- result
152
- end
153
-
154
- # Get an array of actions, derived from the specified command, that the
155
- # actor can potentially execute.
156
- # The command should be a plain-text string, e.g., "examine the book."
127
+ # Get a Dispatcher to select actions that can potentially be executed
128
+ # from the specified command string.
157
129
  #
158
- # @return [Array<Gamefic::Action>]
159
- def dispatch_from_string actor, text
160
- result = []
130
+ # @param actor [Actor]
131
+ # @param text [String]
132
+ # @return [Dispatcher]
133
+ def dispatch(actor, text)
161
134
  commands = Syntax.tokenize(text, actor.syntaxes)
162
- commands.each do |c|
163
- actions_for(c.verb).each do |a|
164
- next if a.hidden?
165
- o = a.attempt(actor, c.arguments)
166
- result.unshift o unless o.nil?
167
- end
168
- end
169
- sort_and_reduce_actions result
135
+ actions = commands.flat_map { |cmd| actions_for(cmd.verb).reject(&:hidden?) }
136
+ Dispatcher.new(actor, commands, sort_and_reduce_actions(actions))
170
137
  end
171
138
 
172
139
  # Get an array of actions, derived from the specified verb and params,
@@ -174,12 +141,8 @@ module Gamefic
174
141
  #
175
142
  # @return [Array<Gamefic::Action>]
176
143
  def dispatch_from_params actor, verb, params
177
- result = []
178
144
  available = actions_for(verb)
179
- available.each do |a|
180
- result.unshift a.new(actor, params) if a.valid?(actor, params)
181
- end
182
- sort_and_reduce_actions result
145
+ Dispatcher.new(actor, [Command.new(verb, params)], sort_and_reduce_actions(available))
183
146
  end
184
147
 
185
148
  # Duplicate the playbook.
@@ -224,22 +187,23 @@ module Gamefic
224
187
 
225
188
  def add_syntax syntax
226
189
  raise "No actions exist for \"#{syntax.verb}\"" if @commands[syntax.verb].nil?
190
+ sort_syntaxes if @syntax_set.add?(syntax)
191
+ end
227
192
 
228
- @syntaxes.unshift syntax
229
- @syntaxes.uniq!(&:signature)
230
- @syntaxes.sort! do |a, b|
193
+ def sort_and_reduce_actions arr
194
+ arr.sort_by.with_index { |a, i| [a.rank, i] }.reverse.uniq
195
+ end
196
+
197
+ def sort_syntaxes
198
+ @syntaxes = @syntax_set.sort do |a, b|
231
199
  if a.token_count == b.token_count
232
- # For syntaxes of the same length, length of action takes precedence
200
+ # For syntaxes of the same length, sort first word
233
201
  b.first_word <=> a.first_word
234
202
  else
235
203
  b.token_count <=> a.token_count
236
204
  end
237
205
  end
238
206
  end
239
-
240
- def sort_and_reduce_actions arr
241
- arr.sort_by.with_index { |a, i| [a.rank, -i]}.reverse.uniq(&:class)
242
- end
243
207
  end
244
208
  end
245
209
  end
@@ -95,10 +95,10 @@ module Gamefic
95
95
  #
96
96
  # @param prompt [String]
97
97
  # @yieldparam [Gamefic::Actor]
98
- # @yieldparam [Gamefic::Scene::Custom]
99
- # @return [Class<Gamefic::Scene::Custom>]
98
+ # @yieldparam [Gamefic::Scene::Base]
99
+ # @return [Class<Gamefic::Scene::Base>]
100
100
  def question prompt = 'What is your answer?', &block
101
- s = Scene::Custom.subclass do |actor, scene|
101
+ s = Scene::Base.subclass do |actor, scene|
102
102
  scene.prompt = prompt
103
103
  scene.on_finish &block
104
104
  end
@@ -150,23 +150,23 @@ module Gamefic
150
150
  # Custom scenes should always specify the next scene to be cued or
151
151
  # prepared. If not, the scene will get repeated on the next turn.
152
152
  #
153
- # This method creates a Scene::Custom by default. You can customize other
153
+ # This method creates a Scene::Base by default. You can customize other
154
154
  # scene types by specifying the class to create.
155
155
  #
156
156
  # @example Ask the user for a name
157
- # @scene = custom do |scene|
158
- # data.prompt = "What's your name?"
159
- # scene.on_finish do |actor, data|
160
- # actor.name = data.input
157
+ # @scene = custom do |actor, scene|
158
+ # scene.prompt = "What's your name?"
159
+ # scene.on_finish do
160
+ # actor.name = scene.input
161
161
  # actor.tell "Hello, #{actor.name}!"
162
- # actor.cue :active
162
+ # actor.cue default_scene
163
163
  # end
164
164
  # end
165
165
  #
166
166
  # @param cls [Class] The class of scene to be instantiated.
167
167
  # @yieldparam [Gamefic::Actor]
168
- # @return [Class<Gamefic::Scene::Custom>]
169
- def custom cls = Scene::Custom, &block
168
+ # @return [Class<Gamefic::Scene::Base>]
169
+ def custom cls = Scene::Base, &block
170
170
  s = cls.subclass &block
171
171
  scene_classes.push s
172
172
  s
data/lib/gamefic.rb CHANGED
@@ -14,6 +14,8 @@ require "gamefic/scene"
14
14
  require "gamefic/query"
15
15
  require "gamefic/action"
16
16
  require "gamefic/syntax"
17
+ require "gamefic/command"
18
+ require "gamefic/dispatcher"
17
19
  require 'gamefic/world'
18
20
  require 'gamefic/scriptable'
19
21
  require 'gamefic/plot'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamefic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-14 00:00:00.000000000 Z
11
+ date: 2021-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -88,6 +88,7 @@ files:
88
88
  - lib/gamefic/core_ext/array.rb
89
89
  - lib/gamefic/core_ext/string.rb
90
90
  - lib/gamefic/describable.rb
91
+ - lib/gamefic/dispatcher.rb
91
92
  - lib/gamefic/element.rb
92
93
  - lib/gamefic/entity.rb
93
94
  - lib/gamefic/keywords.rb
@@ -113,7 +114,6 @@ files:
113
114
  - lib/gamefic/scene/activity.rb
114
115
  - lib/gamefic/scene/base.rb
115
116
  - lib/gamefic/scene/conclusion.rb
116
- - lib/gamefic/scene/custom.rb
117
117
  - lib/gamefic/scene/multiple_choice.rb
118
118
  - lib/gamefic/scene/multiple_scene.rb
119
119
  - lib/gamefic/scene/pause.rb
@@ -134,7 +134,7 @@ homepage: http://gamefic.com
134
134
  licenses:
135
135
  - MIT
136
136
  metadata: {}
137
- post_install_message:
137
+ post_install_message:
138
138
  rdoc_options: []
139
139
  require_paths:
140
140
  - lib
@@ -149,8 +149,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  - !ruby/object:Gem::Version
150
150
  version: '0'
151
151
  requirements: []
152
- rubygems_version: 3.1.2
153
- signing_key:
152
+ rubygems_version: 3.1.6
153
+ signing_key:
154
154
  specification_version: 4
155
155
  summary: Gamefic
156
156
  test_files: []
@@ -1,7 +0,0 @@
1
- module Gamefic
2
- # A Custom Scene allows for complete configuration of its behavior upon
3
- # instantiation. It is suitable for direct instantiation or subclassing.
4
- #
5
- class Scene::Custom < Scene::Base
6
- end
7
- end