gamefic 3.6.0 → 4.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/.rubocop.yml +0 -3
- data/CHANGELOG.md +19 -0
- data/Rakefile +1 -0
- data/gamefic.gemspec +1 -1
- data/lib/gamefic/action.rb +68 -54
- data/lib/gamefic/active/cue.rb +84 -6
- data/lib/gamefic/active/messaging.rb +8 -0
- data/lib/gamefic/active/narratives.rb +101 -0
- data/lib/gamefic/active.rb +80 -92
- data/lib/gamefic/binding.rb +44 -0
- data/lib/gamefic/chapter.rb +30 -46
- data/lib/gamefic/command.rb +22 -40
- data/lib/gamefic/core_ext/array.rb +7 -7
- data/lib/gamefic/core_ext/string.rb +2 -2
- data/lib/gamefic/describable.rb +13 -0
- data/lib/gamefic/dispatcher.rb +35 -55
- data/lib/gamefic/entity.rb +6 -5
- data/lib/gamefic/expression.rb +1 -11
- data/lib/gamefic/logging.rb +3 -10
- data/lib/gamefic/match.rb +23 -0
- data/lib/gamefic/messenger.rb +1 -1
- data/lib/gamefic/narrative.rb +38 -74
- data/lib/gamefic/narrator.rb +77 -0
- data/lib/gamefic/node.rb +40 -8
- data/lib/gamefic/order.rb +53 -0
- data/lib/gamefic/plot.rb +41 -59
- data/lib/gamefic/props/default.rb +5 -17
- data/lib/gamefic/props/multiple_choice.rb +5 -2
- data/lib/gamefic/props/multiple_partial.rb +16 -0
- data/lib/gamefic/props/output.rb +7 -5
- data/lib/gamefic/props/yes_or_no.rb +2 -2
- data/lib/gamefic/props.rb +1 -0
- data/lib/gamefic/proxy/attr.rb +11 -0
- data/lib/gamefic/proxy/base.rb +3 -15
- data/lib/gamefic/proxy/config.rb +2 -2
- data/lib/gamefic/proxy/pick.rb +3 -3
- data/lib/gamefic/proxy/pick_ex.rb +11 -0
- data/lib/gamefic/proxy.rb +3 -71
- data/lib/gamefic/query/ascendants.rb +16 -0
- data/lib/gamefic/query/base.rb +47 -73
- data/lib/gamefic/query/children.rb +15 -0
- data/lib/gamefic/query/descendants.rb +17 -0
- data/lib/gamefic/query/extended.rb +20 -0
- data/lib/gamefic/query/family.rb +27 -0
- data/lib/gamefic/query/global.rb +22 -0
- data/lib/gamefic/query/integer.rb +32 -0
- data/lib/gamefic/query/myself.rb +13 -0
- data/lib/gamefic/query/parent.rb +13 -0
- data/lib/gamefic/query/result.rb +1 -1
- data/lib/gamefic/query/siblings.rb +12 -0
- data/lib/gamefic/query/subqueries.rb +17 -0
- data/lib/gamefic/query/text.rb +8 -9
- data/lib/gamefic/query.rb +11 -3
- data/lib/gamefic/request.rb +60 -0
- data/lib/gamefic/response.rb +46 -72
- data/lib/gamefic/scanner/nesting.rb +6 -6
- data/lib/gamefic/scanner/result.rb +3 -0
- data/lib/gamefic/scanner/strict.rb +14 -4
- data/lib/gamefic/scanner.rb +11 -6
- data/lib/gamefic/scene/active_choice.rb +75 -0
- data/lib/gamefic/scene/activity.rb +7 -3
- data/lib/gamefic/scene/base.rb +123 -0
- data/lib/gamefic/scene/conclusion.rb +4 -1
- data/lib/gamefic/scene/multiple_choice.rb +14 -11
- data/lib/gamefic/scene/pause.rb +5 -1
- data/lib/gamefic/scene/yes_or_no.rb +9 -0
- data/lib/gamefic/scene.rb +2 -1
- data/lib/gamefic/scriptable/hooks.rb +161 -0
- data/lib/gamefic/scriptable/queries.rb +38 -29
- data/lib/gamefic/scriptable/responses.rb +70 -0
- data/lib/gamefic/scriptable/scenes.rb +88 -115
- data/lib/gamefic/scriptable/seeds.rb +69 -0
- data/lib/gamefic/scriptable/syntaxes.rb +29 -0
- data/lib/gamefic/scriptable.rb +14 -199
- data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
- data/lib/gamefic/scripting/hooks.rb +45 -0
- data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
- data/lib/gamefic/scripting/responses.rb +21 -0
- data/lib/gamefic/scripting/scenes.rb +57 -0
- data/lib/gamefic/scripting/seeds.rb +10 -0
- data/lib/gamefic/scripting/syntaxes.rb +13 -0
- data/lib/gamefic/scripting.rb +43 -0
- data/lib/gamefic/subplot.rb +11 -22
- data/lib/gamefic/syntax.rb +39 -24
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +6 -7
- metadata +38 -41
- data/lib/gamefic/active/epic.rb +0 -74
- data/lib/gamefic/active/take.rb +0 -67
- data/lib/gamefic/block.rb +0 -28
- data/lib/gamefic/callback.rb +0 -16
- data/lib/gamefic/proxy/plot_pick.rb +0 -11
- data/lib/gamefic/query/abstract.rb +0 -12
- data/lib/gamefic/query/general.rb +0 -41
- data/lib/gamefic/query/scoped.rb +0 -27
- data/lib/gamefic/rulebook/calls.rb +0 -86
- data/lib/gamefic/rulebook/events.rb +0 -65
- data/lib/gamefic/rulebook/hooks.rb +0 -57
- data/lib/gamefic/rulebook/scenes.rb +0 -68
- data/lib/gamefic/rulebook.rb +0 -125
- data/lib/gamefic/scene/default.rb +0 -88
- data/lib/gamefic/scope/base.rb +0 -44
- data/lib/gamefic/scope/children.rb +0 -16
- data/lib/gamefic/scope/descendants.rb +0 -16
- data/lib/gamefic/scope/family.rb +0 -43
- data/lib/gamefic/scope/myself.rb +0 -13
- data/lib/gamefic/scope/parent.rb +0 -13
- data/lib/gamefic/scope/siblings.rb +0 -14
- data/lib/gamefic/scope.rb +0 -9
- data/lib/gamefic/scriptable/actions.rb +0 -137
- data/lib/gamefic/scriptable/events.rb +0 -71
- data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
- data/lib/gamefic/snapshot.rb +0 -44
- data/lib/gamefic/stage.rb +0 -51
- data/lib/gamefic/syntax/template.rb +0 -67
- data/lib/gamefic/vault.rb +0 -52
data/lib/gamefic/active.rb
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'set'
|
|
4
4
|
require 'gamefic/active/cue'
|
|
5
|
-
require 'gamefic/active/epic'
|
|
6
5
|
require 'gamefic/active/messaging'
|
|
7
|
-
require 'gamefic/active/
|
|
6
|
+
require 'gamefic/active/narratives'
|
|
8
7
|
|
|
9
8
|
module Gamefic
|
|
10
9
|
# The Active module gives entities the ability to perform actions and
|
|
@@ -15,33 +14,21 @@ module Gamefic
|
|
|
15
14
|
include Logging
|
|
16
15
|
include Messaging
|
|
17
16
|
|
|
18
|
-
# The
|
|
19
|
-
# turn.
|
|
17
|
+
# The most recently started cue.
|
|
20
18
|
#
|
|
21
|
-
# @return [
|
|
22
|
-
attr_reader :
|
|
23
|
-
|
|
24
|
-
# @return [String, nil]
|
|
25
|
-
attr_reader :last_input
|
|
26
|
-
|
|
27
|
-
# @return [Symbol, nil]
|
|
28
|
-
def next_scene
|
|
29
|
-
next_cue&.scene
|
|
30
|
-
end
|
|
19
|
+
# @return [Cue, nil]
|
|
20
|
+
attr_reader :last_cue
|
|
31
21
|
|
|
32
|
-
# The
|
|
33
|
-
# subplot has its own rulebook.
|
|
22
|
+
# The cue that will be started on the next turn.
|
|
34
23
|
#
|
|
35
|
-
# @return [
|
|
36
|
-
|
|
37
|
-
# @rulebooks ||= Set.new
|
|
38
|
-
# end
|
|
24
|
+
# @return [Cue, nil]
|
|
25
|
+
attr_reader :next_cue
|
|
39
26
|
|
|
40
27
|
# The narratives in which the entity is participating.
|
|
41
28
|
#
|
|
42
|
-
# @return [
|
|
43
|
-
def
|
|
44
|
-
@
|
|
29
|
+
# @return [Narratives]
|
|
30
|
+
def narratives
|
|
31
|
+
@narratives ||= Narratives.new
|
|
45
32
|
end
|
|
46
33
|
|
|
47
34
|
# An array of commands waiting to be executed.
|
|
@@ -59,14 +46,7 @@ module Gamefic
|
|
|
59
46
|
#
|
|
60
47
|
# @return [Props::Output]
|
|
61
48
|
def output
|
|
62
|
-
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# The output from the previous turn.
|
|
66
|
-
#
|
|
67
|
-
# @return [Props::Output]
|
|
68
|
-
def last_output
|
|
69
|
-
@last_output ||= output
|
|
49
|
+
last_cue&.output || Props::Output::EMPTY
|
|
70
50
|
end
|
|
71
51
|
|
|
72
52
|
# Perform a command.
|
|
@@ -77,21 +57,24 @@ module Gamefic
|
|
|
77
57
|
# @example Send a command as a string
|
|
78
58
|
# character.perform "take the key"
|
|
79
59
|
#
|
|
80
|
-
# @param
|
|
81
|
-
# @return [
|
|
82
|
-
def perform(
|
|
83
|
-
dispatchers.push Dispatcher.
|
|
84
|
-
dispatchers.last.execute.tap
|
|
60
|
+
# @param input [String]
|
|
61
|
+
# @return [Command, nil]
|
|
62
|
+
def perform(input)
|
|
63
|
+
dispatchers.push Dispatcher.new(Request.new(self, input))
|
|
64
|
+
dispatchers.last.execute.tap do |command|
|
|
65
|
+
dispatchers.pop
|
|
66
|
+
@acting = true if command&.active?
|
|
67
|
+
end
|
|
85
68
|
end
|
|
86
69
|
|
|
87
70
|
# Quietly perform a command.
|
|
88
71
|
# This method executes the command exactly as #perform does, except it
|
|
89
72
|
# buffers the resulting output instead of sending it to messages.
|
|
90
73
|
#
|
|
91
|
-
# @param
|
|
74
|
+
# @param input [String]
|
|
92
75
|
# @return [String] The output that resulted from performing the command.
|
|
93
|
-
def quietly(
|
|
94
|
-
messenger.buffer { perform
|
|
76
|
+
def quietly(input)
|
|
77
|
+
messenger.buffer { perform input }
|
|
95
78
|
end
|
|
96
79
|
|
|
97
80
|
# Perform an action.
|
|
@@ -107,10 +90,13 @@ module Gamefic
|
|
|
107
90
|
#
|
|
108
91
|
# @param verb [Symbol]
|
|
109
92
|
# @param params [Array]
|
|
110
|
-
# @return [
|
|
93
|
+
# @return [Command, nil]
|
|
111
94
|
def execute(verb, *params)
|
|
112
|
-
dispatchers.push Dispatcher.
|
|
113
|
-
dispatchers.last.execute.tap
|
|
95
|
+
dispatchers.push Dispatcher.new(Order.new(self, verb, params))
|
|
96
|
+
dispatchers.last.execute.tap do |command|
|
|
97
|
+
dispatchers.pop
|
|
98
|
+
@acting = true if command&.active?
|
|
99
|
+
end
|
|
114
100
|
end
|
|
115
101
|
|
|
116
102
|
# Proceed to the next Action in the current stack.
|
|
@@ -145,89 +131,91 @@ module Gamefic
|
|
|
145
131
|
#
|
|
146
132
|
# @raise [ArgumentError] if the scene is not valid
|
|
147
133
|
#
|
|
148
|
-
# @param scene [Symbol]
|
|
134
|
+
# @param scene [Class<Scene::Base>, Symbol]
|
|
149
135
|
# @param context [Hash] Extra data to pass to the scene's props
|
|
150
136
|
# @return [Cue]
|
|
151
137
|
def cue scene, **context
|
|
152
|
-
return @next_cue if @next_cue&.
|
|
138
|
+
return @next_cue if @next_cue&.key == scene && @next_cue&.context == context
|
|
153
139
|
|
|
154
|
-
logger.debug "Overwriting existing cue `#{@next_cue.
|
|
140
|
+
logger.debug "Overwriting existing cue `#{@next_cue.key}` with `#{scene}`" if @next_cue
|
|
155
141
|
|
|
156
|
-
@next_cue = Cue.new(scene, **context)
|
|
142
|
+
@next_cue = Cue.new(self, scene, current, **context)
|
|
157
143
|
end
|
|
158
144
|
alias prepare cue
|
|
159
145
|
|
|
160
|
-
# @return [void]
|
|
161
|
-
def start_take
|
|
162
|
-
ensure_cue
|
|
163
|
-
@last_cue = @next_cue
|
|
164
|
-
cue :default_scene
|
|
165
|
-
@props = Take.start(self, @last_cue)
|
|
166
|
-
@last_output = self.output
|
|
167
|
-
@props.output[:last_prompt] = @last_output.prompt
|
|
168
|
-
@props.output[:last_input] = @last_input
|
|
169
|
-
@output = @props.output.dup.freeze
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# @return [void]
|
|
173
|
-
def finish_take
|
|
174
|
-
return unless @last_cue
|
|
175
|
-
|
|
176
|
-
Take.finish(self, @last_cue, @props)
|
|
177
|
-
@last_input = @props.input
|
|
178
|
-
end
|
|
179
|
-
|
|
180
146
|
# Restart the scene from the most recent cue.
|
|
181
147
|
#
|
|
182
148
|
# @return [Cue, nil]
|
|
183
149
|
def recue
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@next_cue = @last_cue
|
|
150
|
+
(@next_cue = @last_cue&.restart) || warn_nil('No scene to recue')
|
|
187
151
|
end
|
|
188
152
|
|
|
189
|
-
#
|
|
190
|
-
# error if the scene is not a conclusion.
|
|
191
|
-
#
|
|
192
|
-
# @raise [ArgumentError] if the requested scene is not a conclusion
|
|
153
|
+
# True if the actor is ready to leave the game.
|
|
193
154
|
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def conclude scene, **context
|
|
198
|
-
cue scene, **context
|
|
199
|
-
available = epic.select_scene(scene)
|
|
200
|
-
raise ArgumentError, "`#{scene}` is not a conclusion" unless available.conclusion?
|
|
155
|
+
def concluding?
|
|
156
|
+
narratives.empty? || last_cue&.type == 'Conclusion'
|
|
157
|
+
end
|
|
201
158
|
|
|
202
|
-
|
|
159
|
+
def accessible
|
|
160
|
+
[]
|
|
203
161
|
end
|
|
204
162
|
|
|
205
|
-
# True if the actor is
|
|
163
|
+
# True if the actor is participating in any narratives.
|
|
206
164
|
#
|
|
207
|
-
def
|
|
208
|
-
|
|
165
|
+
def participating?
|
|
166
|
+
!narratives.empty?
|
|
209
167
|
end
|
|
210
168
|
|
|
211
|
-
|
|
212
|
-
|
|
169
|
+
# True if the actor can perform the verb (i.e., an active narrative
|
|
170
|
+
# understands it).
|
|
171
|
+
#
|
|
172
|
+
# @param verb [String, Symbol]
|
|
173
|
+
def can?(verb)
|
|
174
|
+
narratives.understand?(verb)
|
|
213
175
|
end
|
|
214
176
|
|
|
177
|
+
# Move next_cue into last_cue. This method is typically called by the
|
|
178
|
+
# narrator at the start of a turn. It returns the last cue.
|
|
179
|
+
#
|
|
180
|
+
# @return [Cue, nil]
|
|
181
|
+
def rotate_cue
|
|
182
|
+
@acting = false
|
|
183
|
+
@last_cue = @next_cue
|
|
184
|
+
@next_cue = nil
|
|
185
|
+
@last_cue
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# True if the actor performed a command this turn. False if the actor has
|
|
189
|
+
# not performed a command yet or has only performed meta commands.
|
|
190
|
+
#
|
|
215
191
|
def acting?
|
|
216
|
-
|
|
192
|
+
@acting ||= false
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The input from the last finished cue.
|
|
196
|
+
#
|
|
197
|
+
# @return [String, nil]
|
|
198
|
+
def last_input
|
|
199
|
+
output.last_input
|
|
217
200
|
end
|
|
218
201
|
|
|
219
202
|
private
|
|
220
203
|
|
|
204
|
+
# Get the currently bound or primary narrative.
|
|
205
|
+
#
|
|
206
|
+
# @return [Narrative, nil]
|
|
207
|
+
def current
|
|
208
|
+
Binding.for(self) || narratives.first
|
|
209
|
+
end
|
|
210
|
+
|
|
221
211
|
# @return [Array<Dispatcher>]
|
|
222
212
|
def dispatchers
|
|
223
213
|
@dispatchers ||= []
|
|
224
214
|
end
|
|
225
215
|
|
|
226
|
-
def
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
logger.debug "Using default scene for actor without cue"
|
|
230
|
-
cue :default_scene
|
|
216
|
+
def warn_nil(message)
|
|
217
|
+
logger.warn message
|
|
218
|
+
nil
|
|
231
219
|
end
|
|
232
220
|
end
|
|
233
221
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
class Binding
|
|
5
|
+
class << self
|
|
6
|
+
def registry
|
|
7
|
+
@registry ||= {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def push(object, narrative)
|
|
11
|
+
registry[object] ||= []
|
|
12
|
+
registry[object].push narrative
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def pop(object)
|
|
16
|
+
registry[object].pop
|
|
17
|
+
registry.delete(object) if registry[object].empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def for(object)
|
|
21
|
+
registry.fetch(object, []).last
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :narrative
|
|
26
|
+
|
|
27
|
+
attr_reader :code
|
|
28
|
+
|
|
29
|
+
# @param narrative [Narrative]
|
|
30
|
+
# @param code [Proc]
|
|
31
|
+
def initialize(narrative, code)
|
|
32
|
+
@narrative = narrative
|
|
33
|
+
@code = code
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(*args)
|
|
37
|
+
args.each { |arg| Binding.push arg, @narrative }
|
|
38
|
+
@narrative.instance_exec(*args, &@code)
|
|
39
|
+
ensure
|
|
40
|
+
args.each { |arg| Binding.pop arg }
|
|
41
|
+
end
|
|
42
|
+
alias [] call
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/gamefic/chapter.rb
CHANGED
|
@@ -1,71 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Gamefic
|
|
4
|
-
# Chapters are plot extensions that manage their own namespaces. Authors can
|
|
5
|
-
# use them to encapsulate related content in a separate object instead of
|
|
6
|
-
# adding the required instance variables, methods, and attributes to the
|
|
7
|
-
# plot.
|
|
8
|
-
#
|
|
9
|
-
# Chapters are similar to subplots with three important exceptions:
|
|
10
|
-
# * Chapters normally persist for the duration of a plot.
|
|
11
|
-
# * Players do not need to be introduced to a chapter.
|
|
12
|
-
# * Chapters share their plot's entities, players, and rulebook.
|
|
13
|
-
#
|
|
14
|
-
# @example
|
|
15
|
-
# class MyChapter < Gamefic::Chapter
|
|
16
|
-
# def thing
|
|
17
|
-
# @thing ||= make Gamefic::Entity, name: 'chapter thing'
|
|
18
|
-
# end
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# class MyPlot < Gamefic::Plot
|
|
22
|
-
# append MyChapter
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# plot = MyPlot.new
|
|
26
|
-
# plot.entities #=> [<#Gamefic::Entity a chapter thing>]
|
|
27
|
-
# plot.thing # raises NoMethodError
|
|
28
|
-
# plot.chapters.first.thing #=> <#Gamefic::Entity a chapter thing>
|
|
29
|
-
#
|
|
30
4
|
class Chapter < Narrative
|
|
31
|
-
extend Scriptable::PlotProxies
|
|
32
|
-
|
|
33
5
|
# @return [Plot]
|
|
34
6
|
attr_reader :plot
|
|
35
7
|
|
|
8
|
+
# @return [Hash]
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
36
11
|
# @param plot [Plot]
|
|
37
|
-
def initialize(plot)
|
|
12
|
+
def initialize(plot, **config)
|
|
38
13
|
@plot = plot
|
|
39
|
-
|
|
40
|
-
|
|
14
|
+
@concluding = false
|
|
15
|
+
@config = config
|
|
16
|
+
configure
|
|
17
|
+
@config.freeze
|
|
18
|
+
super()
|
|
41
19
|
end
|
|
42
20
|
|
|
43
|
-
def
|
|
44
|
-
|
|
21
|
+
def players
|
|
22
|
+
plot.players
|
|
45
23
|
end
|
|
46
24
|
|
|
47
|
-
def
|
|
48
|
-
|
|
25
|
+
def conclude
|
|
26
|
+
# @todo Void entities?
|
|
27
|
+
@concluding = true
|
|
49
28
|
end
|
|
50
29
|
|
|
51
|
-
def
|
|
52
|
-
|
|
30
|
+
def concluding?
|
|
31
|
+
@concluding
|
|
53
32
|
end
|
|
54
33
|
|
|
55
|
-
def
|
|
56
|
-
|
|
34
|
+
def self.bind_from_plot *methods
|
|
35
|
+
methods.flatten.each do |method|
|
|
36
|
+
define_method(method) { plot.send(method) }
|
|
37
|
+
define_singleton_method(method) { Proxy::Attr.new(method) }
|
|
38
|
+
end
|
|
57
39
|
end
|
|
58
40
|
|
|
59
|
-
def
|
|
60
|
-
plot.
|
|
41
|
+
def included_scripts
|
|
42
|
+
super - plot.included_scripts
|
|
61
43
|
end
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
45
|
+
# Subclasses can override this method to handle additional configuration.
|
|
46
|
+
#
|
|
47
|
+
def configure; end
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
class << self
|
|
50
|
+
def config
|
|
51
|
+
Proxy::Config.new
|
|
52
|
+
end
|
|
69
53
|
end
|
|
70
54
|
end
|
|
71
55
|
end
|
data/lib/gamefic/command.rb
CHANGED
|
@@ -10,59 +10,41 @@ module Gamefic
|
|
|
10
10
|
# @return [Array<Array<Entity>, Entity, String>]
|
|
11
11
|
attr_reader :arguments
|
|
12
12
|
|
|
13
|
-
# @return [
|
|
14
|
-
attr_reader :
|
|
15
|
-
|
|
16
|
-
# @return [Integer]
|
|
17
|
-
attr_reader :precision
|
|
13
|
+
# @return [String, nil]
|
|
14
|
+
attr_reader :input
|
|
18
15
|
|
|
19
16
|
# @param verb [Symbol]
|
|
20
17
|
# @param arguments [Array<Array<Entity>, Entity, String>]
|
|
21
|
-
# @param
|
|
22
|
-
# @param
|
|
23
|
-
|
|
24
|
-
# @todo Consider making strictness and precision required or providing
|
|
25
|
-
# another generator
|
|
26
|
-
def initialize verb, arguments, strictness = 0, precision = 0
|
|
18
|
+
# @param meta [Boolean]
|
|
19
|
+
# @param input [String, nil]
|
|
20
|
+
def initialize(verb, arguments, meta = false, input = nil)
|
|
27
21
|
@verb = verb
|
|
28
22
|
@arguments = arguments
|
|
29
|
-
@
|
|
30
|
-
@
|
|
23
|
+
@meta = meta
|
|
24
|
+
@input = input
|
|
25
|
+
@cancelled = false
|
|
31
26
|
end
|
|
32
27
|
|
|
33
|
-
def
|
|
34
|
-
@
|
|
28
|
+
def cancel
|
|
29
|
+
@cancelled = true
|
|
35
30
|
end
|
|
31
|
+
alias stop cancel
|
|
36
32
|
|
|
37
|
-
def
|
|
38
|
-
|
|
33
|
+
def cancelled?
|
|
34
|
+
@cancelled
|
|
39
35
|
end
|
|
36
|
+
alias stopped? cancelled?
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# @param actor [Actor]
|
|
45
|
-
# @param input [String]
|
|
46
|
-
# @return [Command]
|
|
47
|
-
def compose actor, input
|
|
48
|
-
expressions = Syntax.tokenize(input, actor.epic.syntaxes)
|
|
49
|
-
expressions.flat_map { |expression| expression_to_commands(actor, expression) }
|
|
50
|
-
.first || Command.new(nil, [])
|
|
51
|
-
end
|
|
38
|
+
def meta?
|
|
39
|
+
@meta
|
|
40
|
+
end
|
|
52
41
|
|
|
53
|
-
|
|
42
|
+
def active?
|
|
43
|
+
!meta?
|
|
44
|
+
end
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
# @return [Array<Command>]
|
|
58
|
-
def expression_to_commands actor, expression
|
|
59
|
-
Gamefic.logger.info "Evaluating #{expression.inspect}"
|
|
60
|
-
actor.epic
|
|
61
|
-
.responses_for(expression.verb)
|
|
62
|
-
.map { |response| response.to_command(actor, expression) }
|
|
63
|
-
.compact
|
|
64
|
-
.sort_by.with_index { |result, idx| [-result.substantiality, -result.strictness, -result.precision, idx] }
|
|
65
|
-
end
|
|
46
|
+
def inspect
|
|
47
|
+
"#<#{self.class} #{([verb] + arguments).map(&:inspect).join(', ')}>"
|
|
66
48
|
end
|
|
67
49
|
end
|
|
68
50
|
end
|
|
@@ -46,24 +46,24 @@ class Array
|
|
|
46
46
|
# animals = ['a dog', 'a cat', 'a mouse']
|
|
47
47
|
# animals.join_and #=> 'a dog, a cat, and a mouse'
|
|
48
48
|
#
|
|
49
|
-
# @param
|
|
50
|
-
# @param
|
|
49
|
+
# @param separator [String] The separator for all but the last element
|
|
50
|
+
# @param and_separator [String] The separator for the last element
|
|
51
51
|
# @param serial [Boolean] Use serial separators (e.g., serial commas)
|
|
52
52
|
# @return [String]
|
|
53
|
-
def join_and(
|
|
53
|
+
def join_and(separator: ', ', and_separator: ' and ', serial: true)
|
|
54
54
|
if length < 3
|
|
55
|
-
join(
|
|
55
|
+
join(and_separator)
|
|
56
56
|
else
|
|
57
57
|
start = self[0..-2]
|
|
58
|
-
start.join(
|
|
58
|
+
start.join(separator) + "#{serial ? separator.strip : ''}#{and_separator}#{last}"
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# @see Array#join_and
|
|
63
63
|
#
|
|
64
64
|
# @return [String]
|
|
65
|
-
def join_or(
|
|
66
|
-
join_and(
|
|
65
|
+
def join_or(separator: ', ', or_separator: ' or ', serial: true)
|
|
66
|
+
join_and(separator: separator, and_separator: or_separator, serial: serial)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
private
|
|
@@ -12,9 +12,9 @@ class String
|
|
|
12
12
|
|
|
13
13
|
# Get an array of words split by any whitespace.
|
|
14
14
|
#
|
|
15
|
-
# @return [Array]
|
|
15
|
+
# @return [Array<String>]
|
|
16
16
|
def keywords
|
|
17
|
-
gsub(/[\s-]+/, ' ').strip.downcase.split
|
|
17
|
+
gsub(/[\s-]+/, ' ').strip.downcase.split
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# @return [String]
|
data/lib/gamefic/describable.rb
CHANGED
|
@@ -19,6 +19,9 @@ module Gamefic
|
|
|
19
19
|
# @return [String]
|
|
20
20
|
attr_reader :synonyms
|
|
21
21
|
|
|
22
|
+
# @return [String]
|
|
23
|
+
attr_writer :nuance
|
|
24
|
+
|
|
22
25
|
# The object's indefinite article (usually "a" or "an").
|
|
23
26
|
#
|
|
24
27
|
# @return [String]
|
|
@@ -33,6 +36,16 @@ module Gamefic
|
|
|
33
36
|
"#{name} #{synonyms}".keywords
|
|
34
37
|
end
|
|
35
38
|
|
|
39
|
+
# Optional words that shouldn't match an object on their own but might be
|
|
40
|
+
# used in a larger phrase. For example, if you have an entity named "dog"
|
|
41
|
+
# and its description calls it "sleepy," you might add "sleepy" to nuance
|
|
42
|
+
# so the phrase "sleepy dog" matches but the word "sleepy" alone does not.
|
|
43
|
+
#
|
|
44
|
+
# @return [String]
|
|
45
|
+
def nuance
|
|
46
|
+
@nuance ||= ''
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
# The name of the object with an indefinite article.
|
|
37
50
|
# Note: proper-named objects never append an article, though an article
|
|
38
51
|
# may be included in its proper name.
|
data/lib/gamefic/dispatcher.rb
CHANGED
|
@@ -4,33 +4,21 @@ module Gamefic
|
|
|
4
4
|
# The action executor for character commands.
|
|
5
5
|
#
|
|
6
6
|
class Dispatcher
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# @param command [Command]
|
|
11
|
-
def initialize actor, command
|
|
12
|
-
@actor = actor
|
|
13
|
-
@command = command
|
|
14
|
-
@executed = false
|
|
15
|
-
Gamefic.logger.info "Dispatching #{command.inspect}"
|
|
7
|
+
# @param actionable [#to_actions]
|
|
8
|
+
def initialize(actionable)
|
|
9
|
+
@actions = actionable.to_actions
|
|
16
10
|
end
|
|
17
11
|
|
|
18
|
-
#
|
|
12
|
+
# Start executing actions in the dispatcher.
|
|
19
13
|
#
|
|
20
|
-
# @return [
|
|
14
|
+
# @return [Command, nil]
|
|
21
15
|
def execute
|
|
22
|
-
return if
|
|
23
|
-
|
|
24
|
-
@executed = true
|
|
25
|
-
action = next_action
|
|
26
|
-
return unless action
|
|
16
|
+
return if action || actions.empty?
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_after_actions action }
|
|
33
|
-
action
|
|
18
|
+
@action = actions.shift
|
|
19
|
+
Gamefic.logger.info "Dispatching #{actor.inspect} #{command.inspect}"
|
|
20
|
+
run_hooks_and_response
|
|
21
|
+
command
|
|
34
22
|
end
|
|
35
23
|
|
|
36
24
|
# Execute the next available action.
|
|
@@ -39,51 +27,43 @@ module Gamefic
|
|
|
39
27
|
#
|
|
40
28
|
# @return [Action, nil]
|
|
41
29
|
def proceed
|
|
42
|
-
return
|
|
30
|
+
return if !action || command.cancelled?
|
|
43
31
|
|
|
44
|
-
|
|
32
|
+
actions.shift&.execute
|
|
45
33
|
end
|
|
46
34
|
|
|
47
|
-
|
|
48
|
-
# @param input [String]
|
|
49
|
-
# @return [Dispatcher]
|
|
50
|
-
def self.dispatch actor, input
|
|
51
|
-
# expressions = Syntax.tokenize(input, actor.epic.syntaxes)
|
|
52
|
-
# new(actor, Command.compose(actor, expressions))
|
|
53
|
-
new(actor, Command.compose(actor, input))
|
|
54
|
-
end
|
|
35
|
+
private
|
|
55
36
|
|
|
56
|
-
# @
|
|
57
|
-
|
|
58
|
-
# @param params [Array<Object>]
|
|
59
|
-
# @return [Dispatcher]
|
|
60
|
-
def self.dispatch_from_params actor, verb, params
|
|
61
|
-
command = Command.new(verb, params)
|
|
62
|
-
new(actor, command)
|
|
63
|
-
end
|
|
37
|
+
# @return [Array<Action>]
|
|
38
|
+
attr_reader :actions
|
|
64
39
|
|
|
65
|
-
|
|
40
|
+
# @return [Action, nil]
|
|
41
|
+
attr_reader :action
|
|
66
42
|
|
|
67
|
-
# @return [Actor]
|
|
68
|
-
|
|
43
|
+
# @return [Actor, nil]
|
|
44
|
+
def actor
|
|
45
|
+
action.actor
|
|
46
|
+
end
|
|
69
47
|
|
|
70
48
|
# @return [Command]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# @return [Array<Response>]
|
|
74
|
-
def responses
|
|
75
|
-
@responses ||= actor.epic.responses_for(command.verb)
|
|
49
|
+
def command
|
|
50
|
+
action.command
|
|
76
51
|
end
|
|
77
52
|
|
|
78
|
-
|
|
53
|
+
def run_hooks(list)
|
|
54
|
+
list.each do |blk|
|
|
55
|
+
blk[actor, command]
|
|
56
|
+
break if command.cancelled?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
79
59
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
60
|
+
def run_hooks_and_response
|
|
61
|
+
run_hooks actor.narratives.before_commands
|
|
62
|
+
command.freeze
|
|
63
|
+
return if command.cancelled?
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
65
|
+
action.execute
|
|
66
|
+
run_hooks actor.narratives.after_commands
|
|
87
67
|
end
|
|
88
68
|
end
|
|
89
69
|
end
|