gamefic 3.4.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +1 -1
  4. data/lib/gamefic/active/epic.rb +1 -0
  5. data/lib/gamefic/active.rb +4 -0
  6. data/lib/gamefic/chapter.rb +25 -42
  7. data/lib/gamefic/command.rb +40 -1
  8. data/lib/gamefic/dispatcher.rb +5 -31
  9. data/lib/gamefic/entity.rb +26 -0
  10. data/lib/gamefic/narrative.rb +9 -5
  11. data/lib/gamefic/plot.rb +5 -0
  12. data/lib/gamefic/proxy.rb +46 -0
  13. data/lib/gamefic/query/base.rb +28 -12
  14. data/lib/gamefic/query/general.rb +3 -12
  15. data/lib/gamefic/query/result.rb +4 -1
  16. data/lib/gamefic/query/scoped.rb +1 -18
  17. data/lib/gamefic/query/text.rb +13 -12
  18. data/lib/gamefic/response.rb +65 -34
  19. data/lib/gamefic/scanner/base.rb +44 -0
  20. data/lib/gamefic/scanner/fuzzy.rb +17 -0
  21. data/lib/gamefic/scanner/fuzzy_nesting.rb +14 -0
  22. data/lib/gamefic/scanner/nesting.rb +39 -0
  23. data/lib/gamefic/scanner/result.rb +50 -0
  24. data/lib/gamefic/scanner/strict.rb +31 -0
  25. data/lib/gamefic/scanner.rb +33 -111
  26. data/lib/gamefic/scope/descendants.rb +16 -0
  27. data/lib/gamefic/scope/family.rb +31 -8
  28. data/lib/gamefic/scope.rb +1 -0
  29. data/lib/gamefic/scriptable/actions.rb +4 -23
  30. data/lib/gamefic/scriptable/entities.rb +4 -1
  31. data/lib/gamefic/scriptable/plot_proxies.rb +16 -0
  32. data/lib/gamefic/scriptable/proxies.rb +31 -0
  33. data/lib/gamefic/scriptable/queries.rb +15 -7
  34. data/lib/gamefic/scriptable/scenes.rb +7 -1
  35. data/lib/gamefic/scriptable.rb +67 -15
  36. data/lib/gamefic/stage.rb +2 -2
  37. data/lib/gamefic/subplot.rb +27 -1
  38. data/lib/gamefic/version.rb +1 -1
  39. data/lib/gamefic.rb +1 -1
  40. metadata +12 -4
  41. data/lib/gamefic/composer.rb +0 -70
  42. data/lib/gamefic/scriptable/proxy.rb +0 -69
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Methods for referencing entities from proxies.
6
+ #
7
+ module Proxies
8
+ # Convert a proxy into its referenced entity.
9
+ #
10
+ # This method can receive any kind of object. If it's a proxy, its entity
11
+ # will be returned. If it's an array, each of its elements will be
12
+ # unproxied. If it's a hash, each of its values will be unproxied. Any
13
+ # other object will be returned unchanged.
14
+ #
15
+ # @param object [Object]
16
+ # @return [Object]
17
+ def unproxy object
18
+ case object
19
+ when Proxy
20
+ object.fetch self
21
+ when Array
22
+ object.map { |obj| unproxy obj }
23
+ when Hash
24
+ object.transform_values { |val| unproxy val }
25
+ else
26
+ object
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,14 +5,14 @@ module Gamefic
5
5
  # Scriptable methods related to creating action queries.
6
6
  #
7
7
  module Queries
8
- include Proxy
8
+ include Proxies
9
9
 
10
10
  # Define a query that searches the entire plot's entities.
11
11
  #
12
12
  # @param args [Array<Object>] Query arguments
13
13
  # @return [Query::General]
14
14
  def anywhere *args, ambiguous: false
15
- Query::General.new -> { entities }, *unproxy(args), ambiguous: ambiguous
15
+ Query::General.new -> { entities }, *args, ambiguous: ambiguous
16
16
  end
17
17
 
18
18
  # Define a query that searches an actor's family of entities. The
@@ -22,7 +22,7 @@ module Gamefic
22
22
  # @param args [Array<Object>] Query arguments
23
23
  # @return [Query::Scoped]
24
24
  def available *args, ambiguous: false
25
- Query::Scoped.new Scope::Family, *unproxy(args), ambiguous: ambiguous
25
+ Query::Scoped.new Scope::Family, *args, ambiguous: ambiguous
26
26
  end
27
27
  alias family available
28
28
 
@@ -31,7 +31,7 @@ module Gamefic
31
31
  # @param args [Array<Object>] Query arguments
32
32
  # @return [Query::Scoped]
33
33
  def parent *args, ambiguous: false
34
- Query::Scoped.new Scope::Parent, *unproxy(args), ambiguous: ambiguous
34
+ Query::Scoped.new Scope::Parent, *args, ambiguous: ambiguous
35
35
  end
36
36
 
37
37
  # Define a query that searches an actor's children.
@@ -39,7 +39,15 @@ module Gamefic
39
39
  # @param args [Array<Object>] Query arguments
40
40
  # @return [Query::Scoped]
41
41
  def children *args, ambiguous: false
42
- Query::Scoped.new Scope::Children, *unproxy(args), ambiguous: ambiguous
42
+ Query::Scoped.new Scope::Children, *args, ambiguous: ambiguous
43
+ end
44
+
45
+ # Define a query that searches an actor's descendants.
46
+ #
47
+ # @param args [Array<Object>] Query arguments
48
+ # @return [Query::Scoped]
49
+ def descendants *args, ambiguous: false
50
+ Query::Scoped.new Scope::Descendants, *args, ambiguous: ambiguous
43
51
  end
44
52
 
45
53
  # Define a query that searches an actor's siblings.
@@ -47,7 +55,7 @@ module Gamefic
47
55
  # @param args [Array<Object>] Query arguments
48
56
  # @return [Query::Scoped]
49
57
  def siblings *args, ambiguous: false
50
- Query::Scoped.new Scope::Siblings, *unproxy(args), ambiguous: ambiguous
58
+ Query::Scoped.new Scope::Siblings, *args, ambiguous: ambiguous
51
59
  end
52
60
 
53
61
  # Define a query that returns the actor itself.
@@ -55,7 +63,7 @@ module Gamefic
55
63
  # @param args [Array<Object>] Query arguments
56
64
  # @return [Query::Scoped]
57
65
  def myself *args, ambiguous: false
58
- Query::Scoped.new Scope::Myself, *unproxy(args), ambiguous: ambiguous
66
+ Query::Scoped.new Scope::Myself, *args, ambiguous: ambiguous
59
67
  end
60
68
 
61
69
  # Define a query that performs a plaintext search. It can take a String
@@ -36,7 +36,6 @@ module Gamefic
36
36
  rulebook.scenes.add klass.new(name, self, on_start: on_start, on_finish: on_finish, &blk)
37
37
  name
38
38
  end
39
- alias scene block
40
39
 
41
40
  def preface name, klass = Scene::Activity, &start
42
41
  rulebook.scenes.add klass.new(name, self, on_start: start)
@@ -160,9 +159,16 @@ module Gamefic
160
159
  on_start: start
161
160
  end
162
161
 
162
+ # @return [Array<Symbol>]
163
163
  def scenes
164
164
  rulebook.scenes.names
165
165
  end
166
+
167
+ # @param name [Symbol]
168
+ # @return [Scene::Default, nil]
169
+ def scene(name)
170
+ rulebook.scenes[name]
171
+ end
166
172
  end
167
173
  end
168
174
  end
@@ -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.
@@ -119,15 +119,67 @@ module Gamefic
119
119
  #
120
120
  # @param name [Symbol] The attribute name
121
121
  # @param klass [Class<Gamefic::Entity>]
122
+ # @return [Proxy]
122
123
  def attr_seed name, klass, **opts
123
- @count ||= 0
124
- seed do
125
- instance_variable_set("@#{name}", make(klass, **opts))
126
- 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)
127
142
  end
128
- Proxy::Agent.new(@count.tap { @count += 1 })
129
143
  end
130
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
+
131
183
  if RUBY_ENGINE == 'opal'
132
184
  # :nocov:
133
185
  def method_missing method, *args, &block
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,20 @@ 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?
26
41
  end
27
42
 
28
43
  def included_blocks
@@ -41,6 +56,7 @@ module Gamefic
41
56
  uncast plyr
42
57
  end
43
58
  entities.each { |ent| destroy ent }
59
+ @concluded = true
44
60
  end
45
61
 
46
62
  # Make an entity that persists in the subplot's parent plot.
@@ -69,6 +85,16 @@ module Gamefic
69
85
  #
70
86
  def configure; end
71
87
 
88
+ def concluding?
89
+ return super unless persistent?
90
+
91
+ @concluded
92
+ end
93
+
94
+ def introduce player
95
+ @concluded ? player : super
96
+ end
97
+
72
98
  def inspect
73
99
  "#<#{self.class}>"
74
100
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- VERSION = '3.4.0'
4
+ VERSION = '3.5.0'
5
5
  end
data/lib/gamefic.rb CHANGED
@@ -17,6 +17,7 @@ require 'gamefic/command'
17
17
  require 'gamefic/action'
18
18
  require 'gamefic/props'
19
19
  require 'gamefic/scene'
20
+ require 'gamefic/proxy'
20
21
  require 'gamefic/scriptable'
21
22
  require 'gamefic/block'
22
23
  require 'gamefic/stage'
@@ -30,7 +31,6 @@ require 'gamefic/node'
30
31
  require 'gamefic/describable'
31
32
  require 'gamefic/messenger'
32
33
  require 'gamefic/entity'
33
- require 'gamefic/composer'
34
34
  require 'gamefic/dispatcher'
35
35
  require 'gamefic/active'
36
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.4.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-10 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
@@ -138,7 +138,6 @@ files:
138
138
  - lib/gamefic/callback.rb
139
139
  - lib/gamefic/chapter.rb
140
140
  - lib/gamefic/command.rb
141
- - lib/gamefic/composer.rb
142
141
  - lib/gamefic/core_ext/array.rb
143
142
  - lib/gamefic/core_ext/string.rb
144
143
  - lib/gamefic/describable.rb
@@ -156,6 +155,7 @@ files:
156
155
  - lib/gamefic/props/output.rb
157
156
  - lib/gamefic/props/pause.rb
158
157
  - lib/gamefic/props/yes_or_no.rb
158
+ - lib/gamefic/proxy.rb
159
159
  - lib/gamefic/query.rb
160
160
  - lib/gamefic/query/base.rb
161
161
  - lib/gamefic/query/general.rb
@@ -169,6 +169,12 @@ files:
169
169
  - lib/gamefic/rulebook/hooks.rb
170
170
  - lib/gamefic/rulebook/scenes.rb
171
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
172
178
  - lib/gamefic/scene.rb
173
179
  - lib/gamefic/scene/activity.rb
174
180
  - lib/gamefic/scene/conclusion.rb
@@ -179,6 +185,7 @@ files:
179
185
  - lib/gamefic/scope.rb
180
186
  - lib/gamefic/scope/base.rb
181
187
  - lib/gamefic/scope/children.rb
188
+ - lib/gamefic/scope/descendants.rb
182
189
  - lib/gamefic/scope/family.rb
183
190
  - lib/gamefic/scope/myself.rb
184
191
  - lib/gamefic/scope/parent.rb
@@ -187,7 +194,8 @@ files:
187
194
  - lib/gamefic/scriptable/actions.rb
188
195
  - lib/gamefic/scriptable/entities.rb
189
196
  - lib/gamefic/scriptable/events.rb
190
- - lib/gamefic/scriptable/proxy.rb
197
+ - lib/gamefic/scriptable/plot_proxies.rb
198
+ - lib/gamefic/scriptable/proxies.rb
191
199
  - lib/gamefic/scriptable/queries.rb
192
200
  - lib/gamefic/scriptable/scenes.rb
193
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