gamefic 2.0.2 → 2.2.0

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: 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