gamefic 2.3.0 → 3.0.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 +4 -4
- data/.github/workflows/rspec.yml +41 -0
- data/.rspec-opal +2 -0
- data/.rubocop.yml +4 -1
- data/.solargraph.yml +20 -3
- data/CHANGELOG.md +15 -0
- data/Gemfile +0 -4
- data/Rakefile +11 -1
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gamefic.gemspec +5 -2
- data/lib/gamefic/action.rb +53 -173
- data/lib/gamefic/active/cue.rb +25 -0
- data/lib/gamefic/active/epic.rb +68 -0
- data/lib/gamefic/active/messaging.rb +43 -0
- data/lib/gamefic/active/take.rb +69 -0
- data/lib/gamefic/active.rb +97 -192
- data/lib/gamefic/actor.rb +2 -0
- data/lib/gamefic/block.rb +28 -0
- data/lib/gamefic/command.rb +16 -6
- data/lib/gamefic/core_ext/array.rb +4 -4
- data/lib/gamefic/core_ext/string.rb +10 -5
- data/lib/gamefic/describable.rb +39 -65
- data/lib/gamefic/dispatcher.rb +67 -29
- data/lib/gamefic/entity.rb +44 -19
- data/lib/gamefic/logging.rb +32 -0
- data/lib/gamefic/messenger.rb +66 -0
- data/lib/gamefic/narrative.rb +104 -0
- data/lib/gamefic/node.rb +44 -53
- data/lib/gamefic/plot.rb +60 -93
- data/lib/gamefic/props/default.rb +41 -0
- data/lib/gamefic/props/multiple_choice.rb +65 -0
- data/lib/gamefic/props/pause.rb +11 -0
- data/lib/gamefic/props/yes_or_no.rb +21 -0
- data/lib/gamefic/props.rb +10 -0
- data/lib/gamefic/query/base.rb +45 -126
- data/lib/gamefic/query/general.rb +46 -0
- data/lib/gamefic/query/result.rb +20 -0
- data/lib/gamefic/query/scoped.rb +41 -0
- data/lib/gamefic/query/text.rb +30 -31
- data/lib/gamefic/query.rb +7 -15
- data/lib/gamefic/response.rb +118 -0
- data/lib/gamefic/rulebook/calls.rb +90 -0
- data/lib/gamefic/rulebook/events.rb +79 -0
- data/lib/gamefic/rulebook/hooks.rb +57 -0
- data/lib/gamefic/rulebook/scenes.rb +68 -0
- data/lib/gamefic/rulebook.rb +139 -0
- data/lib/gamefic/scanner.rb +103 -0
- data/lib/gamefic/scene/activity.rb +9 -17
- data/lib/gamefic/scene/conclusion.rb +6 -5
- data/lib/gamefic/scene/default.rb +88 -0
- data/lib/gamefic/scene/multiple_choice.rb +14 -69
- data/lib/gamefic/scene/pause.rb +9 -13
- data/lib/gamefic/scene/yes_or_no.rb +6 -46
- data/lib/gamefic/scene.rb +11 -7
- data/lib/gamefic/scope/base.rb +44 -0
- data/lib/gamefic/scope/children.rb +16 -0
- data/lib/gamefic/scope/family.rb +20 -0
- data/lib/gamefic/scope/myself.rb +13 -0
- data/lib/gamefic/scope/parent.rb +13 -0
- data/lib/gamefic/scope/siblings.rb +14 -0
- data/lib/gamefic/scope.rb +8 -0
- data/lib/gamefic/scriptable/actions.rb +156 -0
- data/lib/gamefic/scriptable/entities.rb +76 -0
- data/lib/gamefic/scriptable/events.rb +65 -0
- data/lib/gamefic/scriptable/proxy.rb +55 -0
- data/lib/gamefic/scriptable/queries.rb +73 -0
- data/lib/gamefic/scriptable/scenes.rb +162 -0
- data/lib/gamefic/scriptable.rb +167 -68
- data/lib/gamefic/snapshot.rb +36 -0
- data/lib/gamefic/stage.rb +51 -0
- data/lib/gamefic/subplot.rb +51 -79
- data/lib/gamefic/syntax/template.rb +67 -0
- data/lib/gamefic/syntax.rb +102 -83
- data/lib/gamefic/vault.rb +50 -0
- data/lib/gamefic/version.rb +3 -1
- data/lib/gamefic.rb +26 -15
- data/spec-opal/spec_helper.rb +24 -0
- metadata +92 -29
- data/lib/gamefic/element.rb +0 -46
- data/lib/gamefic/keywords.rb +0 -52
- data/lib/gamefic/messaging.rb +0 -43
- data/lib/gamefic/plot/darkroom.rb +0 -120
- data/lib/gamefic/plot/host.rb +0 -42
- data/lib/gamefic/plot/snapshot.rb +0 -27
- data/lib/gamefic/query/children.rb +0 -9
- data/lib/gamefic/query/descendants.rb +0 -15
- data/lib/gamefic/query/external.rb +0 -39
- data/lib/gamefic/query/family.rb +0 -18
- data/lib/gamefic/query/itself.rb +0 -13
- data/lib/gamefic/query/matches.rb +0 -75
- data/lib/gamefic/query/parent.rb +0 -9
- data/lib/gamefic/query/siblings.rb +0 -13
- data/lib/gamefic/query/tree.rb +0 -17
- data/lib/gamefic/scene/base.rb +0 -142
- data/lib/gamefic/scene/multiple_scene.rb +0 -29
- data/lib/gamefic/serialize.rb +0 -196
- data/lib/gamefic/world/callbacks.rb +0 -135
- data/lib/gamefic/world/commands.rb +0 -173
- data/lib/gamefic/world/entities.rb +0 -98
- data/lib/gamefic/world/playbook.rb +0 -225
- data/lib/gamefic/world/players.rb +0 -37
- data/lib/gamefic/world/scenes.rb +0 -226
- data/lib/gamefic/world.rb +0 -18
data/lib/gamefic/active.rb
CHANGED
|
@@ -1,135 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'set'
|
|
4
|
+
require 'gamefic/active/cue'
|
|
5
|
+
require 'gamefic/active/epic'
|
|
6
|
+
require 'gamefic/active/messaging'
|
|
7
|
+
require 'gamefic/active/take'
|
|
2
8
|
|
|
3
9
|
module Gamefic
|
|
4
|
-
class NotConclusionError < RuntimeError; end
|
|
5
|
-
|
|
6
10
|
# The Active module gives entities the ability to perform actions and
|
|
7
11
|
# participate in scenes. The Actor class, for example, is an Entity
|
|
8
12
|
# subclass that includes this module.
|
|
9
13
|
#
|
|
10
14
|
module Active
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# @return [Gamefic::Scene::Base]
|
|
14
|
-
attr_reader :scene
|
|
15
|
+
include Logging
|
|
16
|
+
include Messaging
|
|
15
17
|
|
|
16
|
-
# The
|
|
17
|
-
#
|
|
18
|
+
# The cue that will be used to create a scene at the beginning of the next
|
|
19
|
+
# turn.
|
|
18
20
|
#
|
|
19
|
-
# @return [
|
|
20
|
-
attr_reader :
|
|
21
|
+
# @return [Active::Cue, nil]
|
|
22
|
+
attr_reader :next_cue
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# @return [String]
|
|
27
|
-
attr_accessor :last_prompt
|
|
24
|
+
# @return [Symbol, nil]
|
|
25
|
+
def next_scene
|
|
26
|
+
next_cue&.scene
|
|
27
|
+
end
|
|
28
28
|
|
|
29
|
-
# The
|
|
29
|
+
# The rulebooks that will be used to perform commands. Every plot and
|
|
30
|
+
# subplot has its own rulebook.
|
|
30
31
|
#
|
|
31
|
-
# @return [
|
|
32
|
-
|
|
32
|
+
# @return [Set<Gamefic::World::Rulebook>]
|
|
33
|
+
# def rulebooks
|
|
34
|
+
# @rulebooks ||= Set.new
|
|
35
|
+
# end
|
|
33
36
|
|
|
34
|
-
# The
|
|
37
|
+
# The narratives in which the entity is participating.
|
|
35
38
|
#
|
|
36
|
-
# @return [
|
|
37
|
-
def
|
|
38
|
-
@
|
|
39
|
+
# @return [Epic]
|
|
40
|
+
def epic
|
|
41
|
+
@epic ||= Epic.new
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
playbooks.flat_map(&:syntaxes)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# An array of actions waiting to be performed.
|
|
44
|
+
# An array of commands waiting to be executed.
|
|
46
45
|
#
|
|
47
46
|
# @return [Array<String>]
|
|
48
47
|
def queue
|
|
49
48
|
@queue ||= []
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
# A hash of
|
|
51
|
+
# A hash of data that will be sent to the user. The output is typically
|
|
52
|
+
# sent after a scene has started and before the user is prompted for input.
|
|
53
53
|
#
|
|
54
|
-
# @return [Hash
|
|
55
|
-
def state
|
|
56
|
-
@state ||= {}
|
|
57
|
-
end
|
|
58
|
-
|
|
54
|
+
# @return [Hash]
|
|
59
55
|
def output
|
|
60
56
|
@output ||= {}
|
|
61
57
|
end
|
|
62
58
|
|
|
63
|
-
# Send a message to the entity.
|
|
64
|
-
# This method will automatically wrap the message in HTML paragraphs.
|
|
65
|
-
# To send a message without paragraph formatting, use #stream instead.
|
|
66
|
-
#
|
|
67
|
-
# @param message [String]
|
|
68
|
-
def tell(message)
|
|
69
|
-
if buffer_stack > 0
|
|
70
|
-
append_buffer format(message)
|
|
71
|
-
else
|
|
72
|
-
super
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Send a message to the Character as raw text.
|
|
77
|
-
# Unlike #tell, this method will not wrap the message in HTML paragraphs.
|
|
78
|
-
#
|
|
79
|
-
# @param message [String]
|
|
80
|
-
def stream(message)
|
|
81
|
-
if buffer_stack > 0
|
|
82
|
-
append_buffer message
|
|
83
|
-
else
|
|
84
|
-
super
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
59
|
# Perform a command.
|
|
89
60
|
#
|
|
90
|
-
# The command's action will be executed immediately regardless of the
|
|
61
|
+
# The command's action will be executed immediately, regardless of the
|
|
91
62
|
# entity's state.
|
|
92
63
|
#
|
|
93
64
|
# @example Send a command as a string
|
|
94
65
|
# character.perform "take the key"
|
|
95
66
|
#
|
|
96
|
-
# @param command [String
|
|
97
|
-
# @return [
|
|
98
|
-
def perform(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else
|
|
103
|
-
dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
|
|
104
|
-
proceed
|
|
105
|
-
dispatchers.pop
|
|
106
|
-
end
|
|
67
|
+
# @param command [String]
|
|
68
|
+
# @return [void]
|
|
69
|
+
def perform(command)
|
|
70
|
+
dispatchers.push Dispatcher.dispatch(self, command)
|
|
71
|
+
dispatchers.last.execute
|
|
72
|
+
dispatchers.pop
|
|
107
73
|
end
|
|
108
74
|
|
|
109
75
|
# Quietly perform a command.
|
|
110
76
|
# This method executes the command exactly as #perform does, except it
|
|
111
|
-
# buffers the resulting output instead of sending it to
|
|
77
|
+
# buffers the resulting output instead of sending it to messages.
|
|
112
78
|
#
|
|
113
|
-
# @param command [String
|
|
79
|
+
# @param command [String]
|
|
114
80
|
# @return [String] The output that resulted from performing the command.
|
|
115
|
-
def quietly(
|
|
116
|
-
|
|
117
|
-
STDERR.puts "#{caller[0]}: Passing a verb and arguments to #quietly is deprecated. Use #execute instead"
|
|
118
|
-
execute command.first, *command[1..-1], quietly: true
|
|
119
|
-
else
|
|
120
|
-
dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
|
|
121
|
-
result = proceed quietly: true
|
|
122
|
-
dispatchers.pop
|
|
123
|
-
result
|
|
124
|
-
end
|
|
81
|
+
def quietly(command)
|
|
82
|
+
messenger.buffer { perform command }
|
|
125
83
|
end
|
|
126
84
|
|
|
127
85
|
# Perform an action.
|
|
128
86
|
# This is functionally identical to the `perform` method, except the
|
|
129
|
-
# action must be declared as a verb with a list of
|
|
87
|
+
# action must be declared as a verb with a list of arguments. Use
|
|
130
88
|
# `perform` if you need to parse a string as a command.
|
|
131
89
|
#
|
|
132
|
-
# The command will be executed immediately regardless of the entity's
|
|
90
|
+
# The command will be executed immediately, regardless of the entity's
|
|
133
91
|
# state.
|
|
134
92
|
#
|
|
135
93
|
# @example
|
|
@@ -137,11 +95,10 @@ module Gamefic
|
|
|
137
95
|
#
|
|
138
96
|
# @param verb [Symbol]
|
|
139
97
|
# @param params [Array]
|
|
140
|
-
# @params quietly [Boolean]
|
|
141
98
|
# @return [Gamefic::Action]
|
|
142
|
-
def execute(verb, *params
|
|
99
|
+
def execute(verb, *params)
|
|
143
100
|
dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
|
|
144
|
-
|
|
101
|
+
dispatchers.last.execute
|
|
145
102
|
dispatchers.pop
|
|
146
103
|
end
|
|
147
104
|
|
|
@@ -168,138 +125,86 @@ module Gamefic
|
|
|
168
125
|
# end
|
|
169
126
|
# end
|
|
170
127
|
#
|
|
171
|
-
# @
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return if dispatchers.empty?
|
|
175
|
-
a = dispatchers.last.next
|
|
176
|
-
return if a.nil?
|
|
177
|
-
prepare_buffer quietly
|
|
178
|
-
a.execute
|
|
179
|
-
flush_buffer quietly
|
|
128
|
+
# @return [void]
|
|
129
|
+
def proceed
|
|
130
|
+
dispatchers.last&.proceed&.execute
|
|
180
131
|
end
|
|
181
132
|
|
|
182
|
-
#
|
|
183
|
-
# Use #prepare if you want to declare a scene to be started at the
|
|
184
|
-
# beginning of the next turn.
|
|
133
|
+
# Cue a scene to start in the next turn.
|
|
185
134
|
#
|
|
186
|
-
# @
|
|
187
|
-
# @param data [Hash] Additional scene data
|
|
188
|
-
def cue new_scene, **data
|
|
189
|
-
@next_scene = nil
|
|
190
|
-
if new_scene.nil?
|
|
191
|
-
@scene = nil
|
|
192
|
-
else
|
|
193
|
-
@scene = new_scene.new(self, **data)
|
|
194
|
-
@scene.start
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Prepare a scene to be started for this character at the beginning of the
|
|
199
|
-
# next turn. As opposed to #cue, a prepared scene will not start until the
|
|
200
|
-
# current scene finishes.
|
|
135
|
+
# @raise [ArgumentError] if the scene is not valid
|
|
201
136
|
#
|
|
202
|
-
# @param
|
|
203
|
-
# @
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
@
|
|
207
|
-
end
|
|
137
|
+
# @param scene [Symbol]
|
|
138
|
+
# @param context [Hash] Extra data to pass to the scene's props
|
|
139
|
+
# @return [Cue]
|
|
140
|
+
def cue scene, **context
|
|
141
|
+
return @next_cue if @next_cue&.scene == scene && @next_cue&.context == context
|
|
208
142
|
|
|
209
|
-
|
|
210
|
-
# the next turn.
|
|
211
|
-
#
|
|
212
|
-
# @return [Boolean]
|
|
213
|
-
def will_cue? scene
|
|
214
|
-
(@scene.class == scene and @next_scene.nil?) || @next_scene == scene
|
|
215
|
-
end
|
|
143
|
+
logger.debug "Overwriting existing cue `#{@next_cue.scene}` with `#{scene}`" if @next_cue
|
|
216
144
|
|
|
217
|
-
|
|
218
|
-
# NotConclusionError if the scene is not a Scene::Conclusion.
|
|
219
|
-
#
|
|
220
|
-
# @param new_scene [Class<Scene::Base>]
|
|
221
|
-
# @oaram data [Hash] Additional scene data
|
|
222
|
-
def conclude new_scene, **data
|
|
223
|
-
raise NotConclusionError unless new_scene <= Scene::Conclusion
|
|
224
|
-
cue new_scene, **data
|
|
145
|
+
@next_cue = Cue.new(scene, **context)
|
|
225
146
|
end
|
|
147
|
+
alias prepare cue
|
|
226
148
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def accessible?
|
|
235
|
-
false
|
|
149
|
+
def start_take
|
|
150
|
+
ensure_cue
|
|
151
|
+
@last_cue = @next_cue
|
|
152
|
+
cue :default_scene
|
|
153
|
+
@props = Take.start(self, @last_cue)
|
|
236
154
|
end
|
|
237
155
|
|
|
238
|
-
def
|
|
239
|
-
|
|
240
|
-
end
|
|
156
|
+
def finish_take
|
|
157
|
+
return unless @last_cue
|
|
241
158
|
|
|
242
|
-
|
|
243
|
-
#
|
|
244
|
-
def entered scene
|
|
245
|
-
klass = (scene.is_a?(Gamefic::Scene::Base) ? scene.class : scene)
|
|
246
|
-
entered_scenes.add klass
|
|
159
|
+
Take.finish(self, @last_cue, @props)
|
|
247
160
|
end
|
|
248
161
|
|
|
249
|
-
#
|
|
162
|
+
# Restart the scene from the most recent cue.
|
|
250
163
|
#
|
|
251
|
-
# @return [
|
|
252
|
-
def
|
|
253
|
-
|
|
254
|
-
entered_scenes.include?(klass)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
private
|
|
164
|
+
# @return [Cue, nil]
|
|
165
|
+
def recue
|
|
166
|
+
logger.warn "No scene to recue" unless @last_cue
|
|
258
167
|
|
|
259
|
-
|
|
260
|
-
if quietly
|
|
261
|
-
if buffer_stack == 0
|
|
262
|
-
@buffer = ""
|
|
263
|
-
end
|
|
264
|
-
set_buffer_stack(buffer_stack + 1)
|
|
265
|
-
end
|
|
168
|
+
@next_cue = @last_cue
|
|
266
169
|
end
|
|
267
170
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
171
|
+
# Cue a conclusion. This method works like #cue, except it will raise an
|
|
172
|
+
# error if the scene is not a conclusion.
|
|
173
|
+
#
|
|
174
|
+
# @raise [ArgumentError] if the requested scene is not a conclusion
|
|
175
|
+
#
|
|
176
|
+
# @param new_scene [Symbol]
|
|
177
|
+
# @oaram context [Hash] Additional scene data
|
|
178
|
+
def conclude scene, **context
|
|
179
|
+
cue scene, **context
|
|
180
|
+
available = epic.select_scene(scene)
|
|
181
|
+
raise ArgumentError, "`#{scene}` is not a conclusion" unless available.conclusion?
|
|
274
182
|
|
|
275
|
-
|
|
276
|
-
def entered_scenes
|
|
277
|
-
@entered_scenes ||= Set.new
|
|
183
|
+
@next_cue
|
|
278
184
|
end
|
|
279
185
|
|
|
280
|
-
|
|
281
|
-
|
|
186
|
+
# True if the actor is ready to leave the game.
|
|
187
|
+
#
|
|
188
|
+
def concluding?
|
|
189
|
+
epic.empty? || (@last_cue && epic.conclusion?(@last_cue.scene))
|
|
282
190
|
end
|
|
283
191
|
|
|
284
|
-
def
|
|
285
|
-
|
|
192
|
+
def accessible?
|
|
193
|
+
false
|
|
286
194
|
end
|
|
287
195
|
|
|
288
|
-
|
|
289
|
-
def buffer
|
|
290
|
-
@buffer ||= ''
|
|
291
|
-
end
|
|
196
|
+
private
|
|
292
197
|
|
|
293
|
-
|
|
294
|
-
|
|
198
|
+
# @return [Array<Dispatcher>]
|
|
199
|
+
def dispatchers
|
|
200
|
+
@dispatchers ||= []
|
|
295
201
|
end
|
|
296
202
|
|
|
297
|
-
def
|
|
298
|
-
|
|
299
|
-
end
|
|
203
|
+
def ensure_cue
|
|
204
|
+
return if next_cue
|
|
300
205
|
|
|
301
|
-
|
|
302
|
-
|
|
206
|
+
logger.debug "Using default scene for actor without cue"
|
|
207
|
+
cue :default_scene
|
|
303
208
|
end
|
|
304
209
|
end
|
|
305
210
|
end
|
data/lib/gamefic/actor.rb
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
# A code container for seeds and scripts.
|
|
5
|
+
#
|
|
6
|
+
class Block
|
|
7
|
+
# @return [Symbol]
|
|
8
|
+
attr_reader :type
|
|
9
|
+
|
|
10
|
+
# @return [Proc]
|
|
11
|
+
attr_reader :code
|
|
12
|
+
|
|
13
|
+
# @param type [Symbol]
|
|
14
|
+
# @param code [Proc]
|
|
15
|
+
def initialize type, code
|
|
16
|
+
@type = type
|
|
17
|
+
@code = code
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def script?
|
|
21
|
+
type == :script
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def seed?
|
|
25
|
+
type == :seed
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/gamefic/command.rb
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Gamefic
|
|
2
|
-
# A
|
|
3
|
-
#
|
|
4
|
+
# A decomposition of a text-based command into its verb and arguments.
|
|
5
|
+
#
|
|
6
|
+
# Commands are typically derived from tokenization against syntaxes.
|
|
4
7
|
#
|
|
5
8
|
class Command
|
|
6
|
-
# A Symbol representing the command's verb or verbal phrase.
|
|
7
|
-
#
|
|
8
9
|
# @return [Symbol]
|
|
9
10
|
attr_reader :verb
|
|
10
11
|
|
|
11
|
-
# An Array of arguments to be mapped to an Action's Queries.
|
|
12
|
-
#
|
|
13
12
|
# @return [Array<String>]
|
|
14
13
|
attr_reader :arguments
|
|
15
14
|
|
|
@@ -17,5 +16,16 @@ module Gamefic
|
|
|
17
16
|
@verb = verb
|
|
18
17
|
@arguments = arguments
|
|
19
18
|
end
|
|
19
|
+
|
|
20
|
+
# Compare two syntaxes for the purpose of ordering them by relevance while
|
|
21
|
+
# dispatching.
|
|
22
|
+
#
|
|
23
|
+
def compare other
|
|
24
|
+
if verb == other.verb
|
|
25
|
+
other.arguments.compact.length <=> arguments.compact.length
|
|
26
|
+
else
|
|
27
|
+
(other.verb ? 1 : 0) <=> (verb ? 1 : 0)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
20
30
|
end
|
|
21
31
|
end
|
|
@@ -14,7 +14,7 @@ class Array
|
|
|
14
14
|
#
|
|
15
15
|
# @return [Array]
|
|
16
16
|
def that_are(*cls)
|
|
17
|
-
result =
|
|
17
|
+
result = dup
|
|
18
18
|
cls.each do |c|
|
|
19
19
|
_keep result, c, true
|
|
20
20
|
end
|
|
@@ -26,7 +26,7 @@ class Array
|
|
|
26
26
|
#
|
|
27
27
|
# @return [Array]
|
|
28
28
|
def that_are_not(*cls)
|
|
29
|
-
result =
|
|
29
|
+
result = dup
|
|
30
30
|
cls.each do |c|
|
|
31
31
|
_keep result, c, false
|
|
32
32
|
end
|
|
@@ -72,8 +72,8 @@ class Array
|
|
|
72
72
|
case cls
|
|
73
73
|
when Class, Module
|
|
74
74
|
arr.keep_if { |i| i.is_a?(cls) == bool }
|
|
75
|
-
when
|
|
76
|
-
arr.keep_if { |i|
|
|
75
|
+
when Proc
|
|
76
|
+
arr.keep_if { |i| !!cls.call(i) == bool }
|
|
77
77
|
else
|
|
78
78
|
arr.keep_if { |i| (i == cls) == bool }
|
|
79
79
|
end
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
include Gamefic::Keywords
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
3
|
+
class String
|
|
4
4
|
# Capitalize the first letter without changing the rest of the string.
|
|
5
5
|
# (String#capitalize makes the rest of the string lower-case.)
|
|
6
6
|
#
|
|
7
7
|
# @return [String] The capitalized text
|
|
8
8
|
def capitalize_first
|
|
9
|
-
"#{self[0, 1].upcase}#{self[1,
|
|
9
|
+
"#{self[0, 1].upcase}#{self[1, length]}"
|
|
10
10
|
end
|
|
11
11
|
alias cap_first capitalize_first
|
|
12
12
|
|
|
13
13
|
# Get an array of words split by any whitespace.
|
|
14
14
|
#
|
|
15
15
|
# @return [Array]
|
|
16
|
-
def
|
|
17
|
-
|
|
16
|
+
def keywords
|
|
17
|
+
gsub(/[\s-]+/, ' ').strip.downcase.split - %w[a an the]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [String]
|
|
21
|
+
def normalize
|
|
22
|
+
keywords.join(' ')
|
|
18
23
|
end
|
|
19
24
|
end
|