gamefic 3.4.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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