gamefic 3.3.0 → 3.5.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +1 -1
  4. data/lib/gamefic/action.rb +4 -4
  5. data/lib/gamefic/active/epic.rb +1 -0
  6. data/lib/gamefic/active.rb +4 -0
  7. data/lib/gamefic/callback.rb +16 -0
  8. data/lib/gamefic/chapter.rb +71 -0
  9. data/lib/gamefic/command.rb +40 -1
  10. data/lib/gamefic/dispatcher.rb +5 -31
  11. data/lib/gamefic/entity.rb +26 -0
  12. data/lib/gamefic/narrative.rb +30 -8
  13. data/lib/gamefic/plot.rb +28 -1
  14. data/lib/gamefic/proxy.rb +46 -0
  15. data/lib/gamefic/query/base.rb +28 -12
  16. data/lib/gamefic/query/general.rb +3 -12
  17. data/lib/gamefic/query/result.rb +4 -1
  18. data/lib/gamefic/query/scoped.rb +1 -18
  19. data/lib/gamefic/query/text.rb +13 -12
  20. data/lib/gamefic/response.rb +66 -38
  21. data/lib/gamefic/rulebook/calls.rb +0 -4
  22. data/lib/gamefic/rulebook/events.rb +10 -24
  23. data/lib/gamefic/rulebook/hooks.rb +10 -10
  24. data/lib/gamefic/rulebook.rb +8 -22
  25. data/lib/gamefic/scanner/base.rb +44 -0
  26. data/lib/gamefic/scanner/fuzzy.rb +17 -0
  27. data/lib/gamefic/scanner/fuzzy_nesting.rb +14 -0
  28. data/lib/gamefic/scanner/nesting.rb +39 -0
  29. data/lib/gamefic/scanner/result.rb +50 -0
  30. data/lib/gamefic/scanner/strict.rb +31 -0
  31. data/lib/gamefic/scanner.rb +33 -111
  32. data/lib/gamefic/scope/descendants.rb +16 -0
  33. data/lib/gamefic/scope/family.rb +31 -8
  34. data/lib/gamefic/scope.rb +1 -0
  35. data/lib/gamefic/scriptable/actions.rb +8 -27
  36. data/lib/gamefic/scriptable/entities.rb +4 -1
  37. data/lib/gamefic/scriptable/events.rb +13 -7
  38. data/lib/gamefic/scriptable/plot_proxies.rb +16 -0
  39. data/lib/gamefic/scriptable/proxies.rb +31 -0
  40. data/lib/gamefic/scriptable/queries.rb +15 -7
  41. data/lib/gamefic/scriptable/scenes.rb +10 -4
  42. data/lib/gamefic/scriptable.rb +73 -42
  43. data/lib/gamefic/stage.rb +2 -2
  44. data/lib/gamefic/subplot.rb +31 -7
  45. data/lib/gamefic/version.rb +1 -1
  46. data/lib/gamefic.rb +3 -1
  47. metadata +14 -4
  48. data/lib/gamefic/composer.rb +0 -70
  49. data/lib/gamefic/scriptable/proxy.rb +0 -69
@@ -23,14 +23,14 @@ module Gamefic
23
23
  # end
24
24
  #
25
25
  module Scriptable
26
- autoload :Actions, 'gamefic/scriptable/actions'
27
- autoload :Entities, 'gamefic/scriptable/entities'
28
- autoload :Events, 'gamefic/scriptable/events'
29
- autoload :Queries, 'gamefic/scriptable/queries'
30
- autoload :Proxy, 'gamefic/scriptable/proxy'
31
- autoload :Scenes, 'gamefic/scriptable/scenes'
32
-
33
- include Proxy
26
+ autoload :Actions, 'gamefic/scriptable/actions'
27
+ autoload :Entities, 'gamefic/scriptable/entities'
28
+ autoload :Events, 'gamefic/scriptable/events'
29
+ autoload :Queries, 'gamefic/scriptable/queries'
30
+ autoload :Proxies, 'gamefic/scriptable/proxies'
31
+ autoload :Scenes, 'gamefic/scriptable/scenes'
32
+ autoload :PlotProxies, 'gamefic/scriptable/plot_proxies'
33
+
34
34
  include Queries
35
35
  # @!parse
36
36
  # include Scriptable::Actions
@@ -101,10 +101,10 @@ module Gamefic
101
101
  # make_seed Gamefic::Entity, name: 'thing'
102
102
  #
103
103
  # @param klass [Class<Gamefic::Entity>]
104
+ # @return [void]
104
105
  def make_seed klass, **opts
105
- @count ||= 0
106
106
  seed { make(klass, **opts) }
107
- Proxy::Agent.new(@count.tap { @count += 1 })
107
+ nil
108
108
  end
109
109
 
110
110
  # Seed an entity with an attribute method.
@@ -117,16 +117,69 @@ module Gamefic
117
117
  # plot = Plot.new
118
118
  # plot.thing #=> #<Gamefic::Entity a thing>
119
119
  #
120
+ # @param name [Symbol] The attribute name
120
121
  # @param klass [Class<Gamefic::Entity>]
122
+ # @return [Proxy]
121
123
  def attr_seed name, klass, **opts
122
- @count ||= 0
123
- seed do
124
- instance_variable_set("@#{name}", make(klass, **opts))
125
- self.class.define_method(name) { instance_variable_get("@#{name}") }
124
+ ivname = "@#{name}"
125
+ define_method(name) do
126
+ return instance_variable_get(ivname) if instance_variable_defined?(ivname)
127
+
128
+ instance_variable_set(ivname, make(klass, **opts))
129
+ end
130
+ seed { send name }
131
+ Proxy.new(:attr, name)
132
+ end
133
+
134
+ # @param symbol [Symbol]
135
+ # @return [Proxy]
136
+ def proxy symbol
137
+ Logging.logger.warn "#proxy is deprecated. Use lazy_attr, lazy_ivar, or lazy_pick instead"
138
+ if symbol.to_s.start_with?('@')
139
+ lazy_ivar(symbol)
140
+ else
141
+ lazy_attr(symbol)
126
142
  end
127
- Proxy::Agent.new(@count.tap { @count += 1 })
128
143
  end
129
144
 
145
+ # Lazy reference an entity by its instance variable.
146
+ #
147
+ # @example
148
+ # lazy_ivar(:@variable)
149
+ #
150
+ # @param key [Symbol]
151
+ # @return [Proxy]
152
+ def lazy_ivar key
153
+ Proxy.new(:ivar, key)
154
+ end
155
+ alias _ivar lazy_ivar
156
+
157
+ # Lazy reference an entity by its attribute or method.
158
+ #
159
+ # @example
160
+ # lazy_attr(:method)
161
+ #
162
+ # @param key [Symbol]
163
+ # @return [Proxy]
164
+ def lazy_attr key
165
+ Proxy.new(:attr, key)
166
+ end
167
+ alias _attr lazy_attr
168
+
169
+ # Lazy reference an entity by its description.
170
+ #
171
+ # @example
172
+ # lazy_pick('the red box')
173
+ #
174
+ # @raise [RuntimeError] if a unique match could not be found.
175
+ #
176
+ # @param description [String]
177
+ # @return [Proxy]
178
+ def lazy_pick description
179
+ Proxy.new(:pick, description)
180
+ end
181
+ alias _pick lazy_pick
182
+
130
183
  if RUBY_ENGINE == 'opal'
131
184
  # :nocov:
132
185
  def method_missing method, *args, &block
@@ -154,35 +207,13 @@ module Gamefic
154
207
  # This can be useful when you need access to the Scriptable's constants and
155
208
  # instance methods, but you don't want to duplicate its rules.
156
209
  #
157
- # @example
158
- # # Plot and Subplot will both include the `info` method, but
159
- # # only Plot will implement the `think` action.
210
+ # @deprecated Removing script blocks is no longer necessary. This method
211
+ # will simply return self until it's removed.
160
212
  #
161
- # module Shared
162
- # extend Gamefic::Scriptable
163
- #
164
- # def info
165
- # "This method was added by the Shared module."
166
- # end
167
- #
168
- # respond :think do |actor|
169
- # actor.tell 'You ponder your predicament.'
170
- # end
171
- # end
172
- #
173
- # class Plot < Gamefic::Plot
174
- # include Shared
175
- # end
176
- #
177
- # class Subplot < Gamefic::Subplot
178
- # include Shared.no_scripts
179
- # end
180
- #
181
- # @return [Module]
213
+ # @return [Module<self>]
182
214
  def no_scripts
183
- Module.new.tap do |mod|
184
- append_features(mod)
185
- end
215
+ Logging.logger.warn 'Calling `no_scripts` on Scriptable modules is no longer necessary.'
216
+ self
186
217
  end
187
218
  end
188
219
  end
data/lib/gamefic/stage.rb CHANGED
@@ -14,7 +14,7 @@ module Gamefic
14
14
 
15
15
  OVERWRITEABLE_CLASSES = [String, Numeric, Symbol].freeze
16
16
 
17
- SWAPPABLE_VALUES = [true, false, nil].freeze
17
+ SWAPPABLE_VALUES = [true, false].freeze
18
18
 
19
19
  class << self
20
20
  private
@@ -37,7 +37,7 @@ module Gamefic
37
37
  end
38
38
 
39
39
  def overwriteable? cval, nval
40
- return true if swappable?(cval, nval)
40
+ return true if cval.nil? || swappable?(cval, nval)
41
41
 
42
42
  allowed = OVERWRITEABLE_CLASSES.find { |klass| cval.is_a?(klass) }
43
43
  allowed && cval.is_a?(allowed)
@@ -7,6 +7,8 @@ module Gamefic
7
7
  # started and concluded at any time during the parent plot's runtime.
8
8
  #
9
9
  class Subplot < Narrative
10
+ extend Scriptable::PlotProxies
11
+
10
12
  # @return [Hash]
11
13
  attr_reader :config
12
14
 
@@ -22,7 +24,24 @@ module Gamefic
22
24
  configure
23
25
  @config.freeze
24
26
  super()
25
- [introduce].flatten.each { |pl| self.introduce pl }
27
+ @concluded = false
28
+ [introduce].flatten.each { |plyr| self.introduce plyr }
29
+ end
30
+
31
+ def self.persist!
32
+ @persistent = true
33
+ end
34
+
35
+ def self.persistent?
36
+ @persistent ||= false
37
+ end
38
+
39
+ def persistent?
40
+ self.class.persistent?
41
+ end
42
+
43
+ def included_blocks
44
+ super - plot.included_blocks
26
45
  end
27
46
 
28
47
  def ready
@@ -37,6 +56,7 @@ module Gamefic
37
56
  uncast plyr
38
57
  end
39
58
  entities.each { |ent| destroy ent }
59
+ @concluded = true
40
60
  end
41
61
 
42
62
  # Make an entity that persists in the subplot's parent plot.
@@ -65,14 +85,18 @@ module Gamefic
65
85
  #
66
86
  def configure; end
67
87
 
68
- def inspect
69
- "#<#{self.class}>"
88
+ def concluding?
89
+ return super unless persistent?
90
+
91
+ @concluded
70
92
  end
71
93
 
72
- def hydrate
73
- @rulebook = Rulebook.new(self)
74
- @rulebook.script
75
- @rulebook.freeze
94
+ def introduce player
95
+ @concluded ? player : super
96
+ end
97
+
98
+ def inspect
99
+ "#<#{self.class}>"
76
100
  end
77
101
  end
78
102
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- VERSION = '3.3.0'
4
+ VERSION = '3.5.0'
5
5
  end
data/lib/gamefic.rb CHANGED
@@ -8,6 +8,7 @@ require 'gamefic/core_ext/string'
8
8
  require 'gamefic/syntax'
9
9
  require 'gamefic/response'
10
10
  require 'gamefic/rulebook'
11
+ require 'gamefic/callback'
11
12
  require 'gamefic/query'
12
13
  require 'gamefic/scanner'
13
14
  require 'gamefic/scope'
@@ -16,19 +17,20 @@ require 'gamefic/command'
16
17
  require 'gamefic/action'
17
18
  require 'gamefic/props'
18
19
  require 'gamefic/scene'
20
+ require 'gamefic/proxy'
19
21
  require 'gamefic/scriptable'
20
22
  require 'gamefic/block'
21
23
  require 'gamefic/stage'
22
24
  require 'gamefic/vault'
23
25
  require 'gamefic/narrative'
24
26
  require 'gamefic/plot'
27
+ require 'gamefic/chapter'
25
28
  require 'gamefic/subplot'
26
29
  require 'gamefic/snapshot'
27
30
  require 'gamefic/node'
28
31
  require 'gamefic/describable'
29
32
  require 'gamefic/messenger'
30
33
  require 'gamefic/entity'
31
- require 'gamefic/composer'
32
34
  require 'gamefic/dispatcher'
33
35
  require 'gamefic/active'
34
36
  require 'gamefic/active/cue'
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: 3.3.0
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-01 00:00:00.000000000 Z
11
+ date: 2024-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -135,8 +135,9 @@ files:
135
135
  - lib/gamefic/active/take.rb
136
136
  - lib/gamefic/actor.rb
137
137
  - lib/gamefic/block.rb
138
+ - lib/gamefic/callback.rb
139
+ - lib/gamefic/chapter.rb
138
140
  - lib/gamefic/command.rb
139
- - lib/gamefic/composer.rb
140
141
  - lib/gamefic/core_ext/array.rb
141
142
  - lib/gamefic/core_ext/string.rb
142
143
  - lib/gamefic/describable.rb
@@ -154,6 +155,7 @@ files:
154
155
  - lib/gamefic/props/output.rb
155
156
  - lib/gamefic/props/pause.rb
156
157
  - lib/gamefic/props/yes_or_no.rb
158
+ - lib/gamefic/proxy.rb
157
159
  - lib/gamefic/query.rb
158
160
  - lib/gamefic/query/base.rb
159
161
  - lib/gamefic/query/general.rb
@@ -167,6 +169,12 @@ files:
167
169
  - lib/gamefic/rulebook/hooks.rb
168
170
  - lib/gamefic/rulebook/scenes.rb
169
171
  - lib/gamefic/scanner.rb
172
+ - lib/gamefic/scanner/base.rb
173
+ - lib/gamefic/scanner/fuzzy.rb
174
+ - lib/gamefic/scanner/fuzzy_nesting.rb
175
+ - lib/gamefic/scanner/nesting.rb
176
+ - lib/gamefic/scanner/result.rb
177
+ - lib/gamefic/scanner/strict.rb
170
178
  - lib/gamefic/scene.rb
171
179
  - lib/gamefic/scene/activity.rb
172
180
  - lib/gamefic/scene/conclusion.rb
@@ -177,6 +185,7 @@ files:
177
185
  - lib/gamefic/scope.rb
178
186
  - lib/gamefic/scope/base.rb
179
187
  - lib/gamefic/scope/children.rb
188
+ - lib/gamefic/scope/descendants.rb
180
189
  - lib/gamefic/scope/family.rb
181
190
  - lib/gamefic/scope/myself.rb
182
191
  - lib/gamefic/scope/parent.rb
@@ -185,7 +194,8 @@ files:
185
194
  - lib/gamefic/scriptable/actions.rb
186
195
  - lib/gamefic/scriptable/entities.rb
187
196
  - lib/gamefic/scriptable/events.rb
188
- - lib/gamefic/scriptable/proxy.rb
197
+ - lib/gamefic/scriptable/plot_proxies.rb
198
+ - lib/gamefic/scriptable/proxies.rb
189
199
  - lib/gamefic/scriptable/queries.rb
190
200
  - lib/gamefic/scriptable/scenes.rb
191
201
  - lib/gamefic/snapshot.rb
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gamefic
4
- # A function module for creating commands from expressions.
5
- #
6
- module Composer
7
- # Create a command from the first expression that matches a response.
8
- #
9
- # @param actor [Actor]
10
- # @param expressions [Array<Expression>]
11
- # @return [Command]
12
- def self.compose actor, expressions
13
- %i[strict fuzzy].each do |method|
14
- result = match_expressions_to_response actor, expressions, method
15
- return result if result
16
- end
17
- Command.new(nil, [])
18
- end
19
-
20
- class << self
21
- private
22
-
23
- def match_expressions_to_response actor, expressions, method
24
- expressions.each do |expression|
25
- result = match_response_arguments actor, expression, method
26
- return result if result
27
- end
28
- nil
29
- end
30
-
31
- def match_response_arguments actor, expression, method
32
- actor.epic.responses_for(expression.verb).each do |response|
33
- next unless response.queries.length >= expression.tokens.length
34
-
35
- result = match_query_arguments(actor, expression, response, method)
36
- return result if result
37
- end
38
- nil
39
- end
40
-
41
- def match_query_arguments actor, expression, response, method
42
- remainder = response.verb ? '' : expression.verb.to_s
43
- arguments = []
44
- response.queries.each_with_index do |query, idx|
45
- result = Scanner.send(method, query.select(actor), "#{remainder} #{expression.tokens[idx]}".strip)
46
- break unless valid_result_from_query?(result, query)
47
-
48
- if query.ambiguous?
49
- arguments.push result.matched
50
- else
51
- arguments.push result.matched.first
52
- end
53
- remainder = result.remainder
54
- end
55
-
56
- return nil if arguments.length != response.queries.length || remainder != ''
57
-
58
- Command.new(response.verb, arguments)
59
- end
60
-
61
- # @param result [Scanner::Result]
62
- # @param query [Query::Base]
63
- def valid_result_from_query? result, query
64
- return false if result.matched.empty?
65
-
66
- result.matched.length == 1 || query.ambiguous?
67
- end
68
- end
69
- end
70
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gamefic
4
- module Scriptable
5
- # Functions that provide proxies for referencing a narrative's entities
6
- # from class-level scripts.
7
- #
8
- module Proxy
9
- # The object that fetches a proxied entity.
10
- #
11
- class Agent
12
- attr_reader :symbol
13
-
14
- # @param symbol [Symbol, Integer]
15
- def initialize symbol
16
- @symbol = symbol
17
- end
18
-
19
- def fetch container
20
- result = safe_fetch(container)
21
- raise ArgumentError, "Unable to fetch entity from proxy agent symbol `#{symbol}`" unless result
22
-
23
- result
24
- end
25
-
26
- private
27
-
28
- def safe_fetch container
29
- if symbol.to_s =~ /^\d+$/
30
- Stage.run(container, symbol) { |sym| entities[sym] }
31
- elsif symbol.to_s.start_with?('@')
32
- Stage.run(container, symbol) { |sym| instance_variable_get(sym) }
33
- else
34
- Stage.run(container, symbol) { |sym| send(sym) }
35
- end
36
- rescue NoMethodError
37
- nil
38
- end
39
- end
40
-
41
- # Proxy a method or instance variable.
42
- #
43
- # @example
44
- # proxy(:method_name)
45
- # proxy(:@instance_variable_name)
46
- #
47
- # @param symbol [Symbol]
48
- # @return [Agent]
49
- def proxy symbol
50
- Agent.new(symbol)
51
- end
52
-
53
- # @param object [Object]
54
- # @return [Object]
55
- def unproxy object
56
- case object
57
- when Agent
58
- object.fetch self
59
- when Array
60
- object.map { |obj| unproxy obj }
61
- when Hash
62
- object.transform_values { |val| unproxy val }
63
- else
64
- object
65
- end
66
- end
67
- end
68
- end
69
- end