gamefic 2.0.2 → 2.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ac3f26cc5dcb62f6b4af8f0a365a316a4324605efecac8fd852f9980f00e336
4
- data.tar.gz: 11348faf9a1c471c11ca4da93f419167db25a92ec7617fd73fb592fdf0ec7d7f
3
+ metadata.gz: 361eaf767babb3c8b5d7e47616d326a85e1b42269da33c518de33f16f9568451
4
+ data.tar.gz: 96f4d79e833f93c41714c5781cf863d7e0b8832e90c746f6bafe6164e10095b5
5
5
  SHA512:
6
- metadata.gz: 1bd78b541afec9fd2129d785442ddc811dacbc42c41f9c683329776f90ada2e105578324ef73fa7dedd7fc26ed828917f1a796765f0f1b74ade334b54c94d5ab
7
- data.tar.gz: d3a982efcb6bdd0c306ebdab70ee722fcd1040d6cf51501b738b8bca65e955689dcfe2d77aa5e8684e1f0e18d27b71579410f51a6189d23014da47156bbbe3cf
6
+ metadata.gz: b9ad845f6f0271da17b64268b911f5d3fe40fbfd7fd563b411f6ef02e15b8a117abcd924d3586072040f428aafc0a92827458639f9f2c0ba9fa4883af7f33e05
7
+ data.tar.gz: 6f65996ac0ee16805763dd25fa89b1809a89acd0e44cb5d55f83ce22636aeadb6e43b733a15d44e6a397a636d5abde899c332d3601af60bcc4322a0c0ad1fb72
data/.rubocop.yml CHANGED
@@ -10,4 +10,7 @@ Style/StringLiterals:
10
10
 
11
11
  Style/Documentation:
12
12
  Description: 'Document classes and non-namespace modules.'
13
- Enabled: false
13
+ Enabled: false
14
+
15
+ Style/MethodDefParentheses:
16
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,2 +1,16 @@
1
- # 2.0.2 - April 25, 2020
1
+ ## 2.2.0 - September 4, 2021
2
+ - Dynamically inherit default attributes
3
+
4
+ ## 2.1.1 - July 23, 2021
5
+ - Remove gamefic/scene/custom autoload
6
+
7
+ ## 2.1.0 - June 21, 2021
8
+ - Remove redundant MultipleChoice prompt
9
+ - Deprecate Scene::Custom
10
+
11
+ ## 2.0.3 - December 14, 2020
12
+ - Remove unused Index class
13
+ - Active#conclude accepts data argument
14
+
15
+ ## 2.0.2 - April 25, 2020
2
16
  - Improved snapshot serialization
@@ -1,9 +1,4 @@
1
1
  module Gamefic
2
- # Exception raised when the Action's proc arity is not compatible with the
3
- # number of queries
4
- class ActionArgumentError < ArgumentError
5
- end
6
-
7
2
  class Action
8
3
  # An array of objects on which the action will operate, e.g., an entity
9
4
  # that is a direct object of a command.
@@ -54,25 +49,32 @@ module Gamefic
54
49
  self.class.meta?
55
50
  end
56
51
 
57
- def self.subclass verb, *q, meta: false, &block
52
+ # @param verb [Symbol]
53
+ # @param queries [Array<Gamefic::Query::Base>]
54
+ # @param meta [Boolean]
55
+ # @return [Class<Action>]
56
+ def self.subclass verb, *queries, meta: false, &block
58
57
  act = Class.new(self) do
59
58
  self.verb = verb
60
59
  self.meta = meta
61
- q.each { |q|
60
+ queries.each do |q|
62
61
  add_query q
63
- }
62
+ end
64
63
  on_execute &block
65
64
  end
66
- if !block.nil? and act.queries.length + 1 != block.arity and block.arity > 0
67
- raise ActionArgumentError.new("Number of parameters is not compatible with proc arguments")
65
+ if !block.nil? && act.queries.length + 1 != block.arity && block.arity > 0
66
+ raise ArgumentError.new("Number of parameters is not compatible with proc arguments")
68
67
  end
69
68
  act
70
69
  end
71
70
 
72
71
  class << self
73
- def verb
74
- @verb
75
- end
72
+ attr_reader :verb
73
+
74
+ # The proc to call when the action is executed
75
+ #
76
+ # @return [Proc]
77
+ attr_reader :executor
76
78
 
77
79
  def meta?
78
80
  @meta ||= false
@@ -93,7 +95,7 @@ module Gamefic
93
95
 
94
96
  def signature
95
97
  # @todo This is clearly unfinished
96
- "#{verb} #{queries.map{|m| m.signature}.join(', ')}"
98
+ "#{verb} #{queries.map {|m| m.signature}.join(', ')}"
97
99
  end
98
100
 
99
101
  # True if this action is not intended to be performed directly by a
@@ -107,19 +109,13 @@ module Gamefic
107
109
  verb.to_s.start_with?('_')
108
110
  end
109
111
 
110
- # The proc to call when the action is executed
111
- #
112
- # @return [Proc]
113
- def executor
114
- @executor
115
- end
116
-
112
+ # @return [Integer]
117
113
  def rank
118
114
  if @rank.nil?
119
115
  @rank = 0
120
- queries.each { |q|
116
+ queries.each do |q|
121
117
  @rank += (q.rank + 1)
122
- }
118
+ end
123
119
  @rank -= 1000 if verb.nil?
124
120
  end
125
121
  @rank
@@ -127,23 +123,28 @@ module Gamefic
127
123
 
128
124
  def valid? actor, objects
129
125
  return false if objects.length != queries.length
130
- i = 0
131
- queries.each { |p|
126
+ queries.each_with_index do |p, i|
132
127
  return false unless p.include?(actor, objects[i])
133
- i += 1
134
- }
128
+ end
135
129
  true
136
130
  end
137
131
 
138
- def attempt actor, tokens
139
- i = 0
132
+ # Return an instance of this Action if the actor can execute it with the
133
+ # provided tokens, or nil if the tokens are invalid.
134
+ #
135
+ # @param action [Gamefic::Entity]
136
+ # @param command [Command]
137
+ # @return [self, nil]
138
+ def attempt actor, command
139
+ return nil if command.verb != verb
140
+ tokens = command.arguments
140
141
  result = []
141
142
  matches = Gamefic::Query::Matches.new([], '', '')
142
- queries.each { |p|
143
- return nil if tokens[i].nil? and matches.remaining == ''
143
+ queries.each_with_index do |p, i|
144
+ return nil if tokens[i].nil? && matches.remaining == ''
144
145
  matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
145
146
  return nil if matches.objects.empty?
146
- accepted = matches.objects.select{|o| p.accept?(o)}
147
+ accepted = matches.objects.select { |o| p.accept?(o) }
147
148
  return nil if accepted.empty?
148
149
  if p.ambiguous?
149
150
  result.push accepted
@@ -151,20 +152,15 @@ module Gamefic
151
152
  return nil if accepted.length != 1
152
153
  result.push accepted.first
153
154
  end
154
- i += 1
155
- }
156
- self.new(actor, result)
155
+ end
156
+ new(actor, result)
157
157
  end
158
158
 
159
159
  protected
160
160
 
161
- def verb= sym
162
- @verb = sym
163
- end
161
+ attr_writer :verb
164
162
 
165
- def meta= bool
166
- @meta = bool
167
- end
163
+ attr_writer :meta
168
164
  end
169
165
  end
170
166
  end
@@ -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,22 +98,32 @@ 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
- if buffer_stack == 0
121
- clear_buffer
122
- end
126
+ clear_buffer if buffer_stack == 0
123
127
  set_buffer_stack buffer_stack + 1
124
128
  self.perform *command
125
129
  set_buffer_stack buffer_stack - 1
@@ -139,9 +143,9 @@ module Gamefic
139
143
  #
140
144
  # @return [Gamefic::Action]
141
145
  def execute(verb, *params, quietly: false)
142
- actions = []
143
- playbooks.reverse.each { |p| actions.concat p.dispatch_from_params(self, verb, params) }
144
- execute_stack actions, quietly: quietly
146
+ dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
147
+ proceed quietly: quietly
148
+ dispatchers.pop
145
149
  end
146
150
 
147
151
  # Proceed to the next Action in the current stack.
@@ -152,10 +156,12 @@ module Gamefic
152
156
  # introduction do |actor|
153
157
  # actor[:has_eaten] = false # Initial value
154
158
  # end
159
+ #
155
160
  # respond :eat do |actor|
156
161
  # actor.tell "You eat something."
157
162
  # actor[:has_eaten] = true
158
163
  # end
164
+ #
159
165
  # respond :eat do |actor|
160
166
  # # This version will be executed first because it was implemented last
161
167
  # if actor[:has_eaten]
@@ -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
 
@@ -187,13 +192,14 @@ module Gamefic
187
192
  # Use #prepare if you want to declare a scene to be started at the
188
193
  # beginning of the next turn.
189
194
  #
190
- # @param new_scene [Class]
191
- def cue new_scene, **options
195
+ # @param new_scene [Class<Scene::Base>]
196
+ # @param data [Hash] Additional scene data
197
+ def cue new_scene, **data
192
198
  @next_scene = nil
193
199
  if new_scene.nil?
194
200
  @scene = nil
195
201
  else
196
- @scene = new_scene.new(self, **options)
202
+ @scene = new_scene.new(self, **data)
197
203
  @scene.start
198
204
  end
199
205
  end
@@ -202,10 +208,11 @@ module Gamefic
202
208
  # next turn. As opposed to #cue, a prepared scene will not start until the
203
209
  # current scene finishes.
204
210
  #
205
- # @param new_scene [Class]
206
- def prepare new_scene, **options
211
+ # @param new_scene [Class<Scene::Base>]
212
+ # @oaram data [Hash] Additional scene data
213
+ def prepare new_scene, **data
207
214
  @next_scene = new_scene
208
- @next_options = options
215
+ @next_options = data
209
216
  end
210
217
 
211
218
  # Return true if the character is expected to be in the specified scene on
@@ -219,9 +226,11 @@ module Gamefic
219
226
  # Cue a conclusion. This method works like #cue, except it will raise a
220
227
  # NotConclusionError if the scene is not a Scene::Conclusion.
221
228
  #
222
- def conclude scene
223
- raise NotConclusionError unless scene <= Scene::Conclusion
224
- cue scene
229
+ # @param new_scene [Class<Scene::Base>]
230
+ # @oaram data [Hash] Additional scene data
231
+ def conclude new_scene, **data
232
+ raise NotConclusionError unless new_scene <= Scene::Conclusion
233
+ cue new_scene, **data
225
234
  end
226
235
 
227
236
  # True if the character is in a conclusion.
@@ -231,14 +240,6 @@ module Gamefic
231
240
  !scene.nil? && scene.kind_of?(Scene::Conclusion)
232
241
  end
233
242
 
234
- # Record the last action the entity executed. This method is typically
235
- # called when the entity performs an action in response to user input.
236
- #
237
- def performed action
238
- action.freeze
239
- @last_action = action
240
- end
241
-
242
243
  def accessible?
243
244
  false
244
245
  end
@@ -266,35 +267,7 @@ module Gamefic
266
267
 
267
268
  # @return [Array<Gamefic::Scene::Base>]
268
269
  def entered_scenes
269
- @entered_scenes ||= []
270
- end
271
-
272
- def execute_stack actions, quietly: false
273
- return nil if actions.empty?
274
- a = actions.first
275
- okay = true
276
- unless a.meta?
277
- playbooks.reverse.each do |playbook|
278
- okay = validate_playbook playbook, a
279
- break unless okay
280
- end
281
- end
282
- if okay
283
- performance_stack.push actions
284
- proceed quietly: quietly
285
- performance_stack.pop
286
- end
287
- a
288
- end
289
-
290
- def validate_playbook playbook, action
291
- okay = true
292
- playbook.validators.each { |v|
293
- result = v.call(self, action.verb, action.parameters)
294
- okay = (result != false)
295
- break unless okay
296
- }
297
- okay
270
+ @entered_scenes ||= []
298
271
  end
299
272
 
300
273
  def buffer_stack
@@ -318,8 +291,8 @@ module Gamefic
318
291
  @buffer = ''
319
292
  end
320
293
 
321
- def performance_stack
322
- @performance_stack ||= []
294
+ def dispatchers
295
+ @dispatchers ||= []
323
296
  end
324
297
  end
325
298
  end
@@ -44,6 +44,9 @@ class Array
44
44
  # animals = ['a dog', 'a cat', 'a mouse']
45
45
  # animals.join_and #=> 'a dog, a cat, and a mouse'
46
46
  #
47
+ # @param sep [String] The separator for all but the last element
48
+ # @param andSep [String] The separator for the last element
49
+ # @param serial [Boolean] Use serial separators (e.g., serial commas)
47
50
  # @return [String]
48
51
  def join_and(sep = ', ', andSep = ' and ', serial = true)
49
52
  if self.length < 3
@@ -61,7 +64,7 @@ class Array
61
64
  join_and(sep, orSep, serial)
62
65
  end
63
66
 
64
- # private
67
+ private
65
68
 
66
69
  def _keep(arr, cls, bool)
67
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