gamefic 2.1.0 → 2.2.2

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: ad70329086d7b1e9dd06a8459c5b1ee95952ab5453f63afd4e7a87df6ba90e69
4
- data.tar.gz: 443a8642312dd1da33612a78ce1598723a9336b46e55b840636c2f41804eb8c5
3
+ metadata.gz: 7d03f9cc05efbae7569a6dfa66f5ffeccc629180581e8b6acabeddd40f8dca12
4
+ data.tar.gz: f48f8890c0d894be3c2e888a4d7325b936d66ebda39c5e2dd1c1e324dd1c091b
5
5
  SHA512:
6
- metadata.gz: 6c207722b0d00906579e9a221d0f8fad831a8f35694ac2252569d427d2846e113beda49c3e160083013284e6f5c85354f9959b49fceb4383100c451de61dc882
7
- data.tar.gz: 899b4605d74e236a6904d80a6218c6c607c78594357124bb2b36ea12557bee1c485e76bde2eb8ed1f29359f7d6a6f32ca1d7c2294b0d56f9d1109ad195f7c890
6
+ metadata.gz: 7776a069549d44db264d012a00306f95edcf890988cbf2669edd61de97d8cf9527306136bb8c7d07ccb7975c5a48c04029f7401a2e7ea94baf372df58c454722
7
+ data.tar.gz: ed07e4a417207f63b6bd3f84467efb2a0cd32e73d07ecc57a642b05e6dab4bf788f98b2ee3184e42a7ac0cacbb7e2985d2cdade1df513f6f7d3539f5e7ef56c2
data/CHANGELOG.md CHANGED
@@ -1,10 +1,22 @@
1
- # 2.1.0 - June 21, 2021
1
+ ## 2.2.2 - September 6, 2021
2
+ - Darkroom indexes non-static elements
3
+
4
+ ## 2.2.1 - September 5, 2021
5
+ - Retain unknown values in restored snapshots
6
+
7
+ ## 2.2.0 - September 4, 2021
8
+ - Dynamically inherit default attributes
9
+
10
+ ## 2.1.1 - July 23, 2021
11
+ - Remove gamefic/scene/custom autoload
12
+
13
+ ## 2.1.0 - June 21, 2021
2
14
  - Remove redundant MultipleChoice prompt
3
15
  - Deprecate Scene::Custom
4
16
 
5
- # 2.0.3 - December 14, 2020
17
+ ## 2.0.3 - December 14, 2020
6
18
  - Remove unused Index class
7
19
  - Active#conclude accepts data argument
8
20
 
9
- # 2.0.2 - April 25, 2020
21
+ ## 2.0.2 - April 25, 2020
10
22
  - 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
@@ -3,9 +3,10 @@ module Gamefic
3
3
  #
4
4
  class Plot
5
5
  class Darkroom
6
- # @return [Gamefic::Plot]
6
+ # @return [Plot]
7
7
  attr_reader :plot
8
8
 
9
+ # @param plot [Plot]
9
10
  def initialize plot
10
11
  @plot = plot
11
12
  end
@@ -14,20 +15,9 @@ module Gamefic
14
15
  #
15
16
  # @return [Hash]
16
17
  def save
17
- index = plot.static + plot.players
18
- plot.to_serial(index)
19
18
  {
20
19
  'program' => {}, # @todo Metadata for version control, etc.
21
- 'index' => index.map do |i|
22
- if i.is_a?(Gamefic::Serialize)
23
- {
24
- 'class' => i.class.to_s,
25
- 'ivars' => i.serialize_instance_variables(index)
26
- }
27
- else
28
- i.to_serial(index)
29
- end
30
- end
20
+ 'index' => index.map { |obj| serialize_indexed(obj) }
31
21
  }
32
22
  end
33
23
 
@@ -75,6 +65,56 @@ module Gamefic
75
65
  end
76
66
  end
77
67
  end
68
+
69
+ private
70
+
71
+ def index
72
+ @index ||= begin
73
+ populate_full_index_from(plot)
74
+ Set.new(plot.static + plot.players).merge(full_index).to_a
75
+ end
76
+ end
77
+
78
+ def full_index
79
+ @full_index ||= Set.new
80
+ end
81
+
82
+ def populate_full_index_from(object)
83
+ return if full_index.include?(object)
84
+ if object.is_a?(Array) || object.is_a?(Set)
85
+ object.each { |ele| populate_full_index_from(ele) }
86
+ elsif object.is_a?(Hash)
87
+ object.each_pair do |k, v|
88
+ populate_full_index_from(k)
89
+ populate_full_index_from(v)
90
+ end
91
+ else
92
+ if object.is_a?(Gamefic::Serialize)
93
+ full_index.add object unless object.is_a?(Module) && object.name
94
+ object.instance_variables.each do |v|
95
+ next if object.class.excluded_from_serial.include?(v)
96
+ populate_full_index_from(object.instance_variable_get(v))
97
+ end
98
+ else
99
+ object.instance_variables.each do |v|
100
+ populate_full_index_from(object.instance_variable_get(v))
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def serialize_indexed object
107
+ if object.is_a?(Gamefic::Serialize)
108
+ # Serialized objects in the index should be a full serialization.
109
+ # Serialize#to_serial rturns a reference to the indexed object.
110
+ {
111
+ 'class' => object.class.to_s,
112
+ 'ivars' => object.serialize_instance_variables(index)
113
+ }
114
+ else
115
+ object.to_serial(index)
116
+ end
117
+ end
78
118
  end
79
119
  end
80
120
  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
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'
@@ -15,7 +15,6 @@ module Gamefic
15
15
  'name' => name
16
16
  }
17
17
  else
18
- index.push self if self.is_a?(Gamefic::Serialize)
19
18
  {
20
19
  'class' => serialized_class(index),
21
20
  'ivars' => serialize_instance_variables(index)
@@ -32,13 +31,6 @@ module Gamefic
32
31
  end
33
32
  end
34
33
 
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
34
  # @param string [String]
43
35
  # @return [Object]
44
36
  def self.string_to_constant string
@@ -71,7 +63,7 @@ class Object
71
63
  end
72
64
 
73
65
  def from_serial(index = [])
74
- if self.is_a?(Hash) && (self['class'] || self['instance'])
66
+ if self.is_a?(Hash)
75
67
  if self['instance']
76
68
  elematch = self['instance'].match(/^#<ELE_([\d]+)>$/)
77
69
  object = index[elematch[1].to_i]
@@ -96,7 +88,6 @@ class Object
96
88
  end
97
89
  raise "Unable to find class #{self['class']} #{self}" if klass.nil?
98
90
  object = klass.allocate
99
- index.push object if object.is_a?(Gamefic::Serialize)
100
91
  end
101
92
  end
102
93
  self['ivars'].each_pair do |k, v|
@@ -110,25 +101,7 @@ class Object
110
101
  return index.index(match[1].to_i) if match
111
102
  match = self.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
112
103
  return match[1].to_sym if match
113
- # return nil if self == '#<UNKNOWN>'
114
104
  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
105
  else
133
106
  # true, false, or nil
134
107
  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.1.0'
2
+ VERSION = '2.2.2'
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
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.1.0
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-21 00:00:00.000000000 Z
11
+ date: 2021-09-06 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