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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/CHANGELOG.md +19 -0
  4. data/Rakefile +1 -0
  5. data/gamefic.gemspec +1 -1
  6. data/lib/gamefic/action.rb +68 -54
  7. data/lib/gamefic/active/cue.rb +84 -6
  8. data/lib/gamefic/active/messaging.rb +8 -0
  9. data/lib/gamefic/active/narratives.rb +101 -0
  10. data/lib/gamefic/active.rb +80 -92
  11. data/lib/gamefic/binding.rb +44 -0
  12. data/lib/gamefic/chapter.rb +30 -46
  13. data/lib/gamefic/command.rb +22 -40
  14. data/lib/gamefic/core_ext/array.rb +7 -7
  15. data/lib/gamefic/core_ext/string.rb +2 -2
  16. data/lib/gamefic/describable.rb +13 -0
  17. data/lib/gamefic/dispatcher.rb +35 -55
  18. data/lib/gamefic/entity.rb +6 -5
  19. data/lib/gamefic/expression.rb +1 -11
  20. data/lib/gamefic/logging.rb +3 -10
  21. data/lib/gamefic/match.rb +23 -0
  22. data/lib/gamefic/messenger.rb +1 -1
  23. data/lib/gamefic/narrative.rb +38 -74
  24. data/lib/gamefic/narrator.rb +77 -0
  25. data/lib/gamefic/node.rb +40 -8
  26. data/lib/gamefic/order.rb +53 -0
  27. data/lib/gamefic/plot.rb +41 -59
  28. data/lib/gamefic/props/default.rb +5 -17
  29. data/lib/gamefic/props/multiple_choice.rb +5 -2
  30. data/lib/gamefic/props/multiple_partial.rb +16 -0
  31. data/lib/gamefic/props/output.rb +7 -5
  32. data/lib/gamefic/props/yes_or_no.rb +2 -2
  33. data/lib/gamefic/props.rb +1 -0
  34. data/lib/gamefic/proxy/attr.rb +11 -0
  35. data/lib/gamefic/proxy/base.rb +3 -15
  36. data/lib/gamefic/proxy/config.rb +2 -2
  37. data/lib/gamefic/proxy/pick.rb +3 -3
  38. data/lib/gamefic/proxy/pick_ex.rb +11 -0
  39. data/lib/gamefic/proxy.rb +3 -71
  40. data/lib/gamefic/query/ascendants.rb +16 -0
  41. data/lib/gamefic/query/base.rb +47 -73
  42. data/lib/gamefic/query/children.rb +15 -0
  43. data/lib/gamefic/query/descendants.rb +17 -0
  44. data/lib/gamefic/query/extended.rb +20 -0
  45. data/lib/gamefic/query/family.rb +27 -0
  46. data/lib/gamefic/query/global.rb +22 -0
  47. data/lib/gamefic/query/integer.rb +32 -0
  48. data/lib/gamefic/query/myself.rb +13 -0
  49. data/lib/gamefic/query/parent.rb +13 -0
  50. data/lib/gamefic/query/result.rb +1 -1
  51. data/lib/gamefic/query/siblings.rb +12 -0
  52. data/lib/gamefic/query/subqueries.rb +17 -0
  53. data/lib/gamefic/query/text.rb +8 -9
  54. data/lib/gamefic/query.rb +11 -3
  55. data/lib/gamefic/request.rb +60 -0
  56. data/lib/gamefic/response.rb +46 -72
  57. data/lib/gamefic/scanner/nesting.rb +6 -6
  58. data/lib/gamefic/scanner/result.rb +3 -0
  59. data/lib/gamefic/scanner/strict.rb +14 -4
  60. data/lib/gamefic/scanner.rb +11 -6
  61. data/lib/gamefic/scene/active_choice.rb +75 -0
  62. data/lib/gamefic/scene/activity.rb +7 -3
  63. data/lib/gamefic/scene/base.rb +123 -0
  64. data/lib/gamefic/scene/conclusion.rb +4 -1
  65. data/lib/gamefic/scene/multiple_choice.rb +14 -11
  66. data/lib/gamefic/scene/pause.rb +5 -1
  67. data/lib/gamefic/scene/yes_or_no.rb +9 -0
  68. data/lib/gamefic/scene.rb +2 -1
  69. data/lib/gamefic/scriptable/hooks.rb +161 -0
  70. data/lib/gamefic/scriptable/queries.rb +38 -29
  71. data/lib/gamefic/scriptable/responses.rb +70 -0
  72. data/lib/gamefic/scriptable/scenes.rb +88 -115
  73. data/lib/gamefic/scriptable/seeds.rb +69 -0
  74. data/lib/gamefic/scriptable/syntaxes.rb +29 -0
  75. data/lib/gamefic/scriptable.rb +14 -199
  76. data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
  77. data/lib/gamefic/scripting/hooks.rb +45 -0
  78. data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
  79. data/lib/gamefic/scripting/responses.rb +21 -0
  80. data/lib/gamefic/scripting/scenes.rb +57 -0
  81. data/lib/gamefic/scripting/seeds.rb +10 -0
  82. data/lib/gamefic/scripting/syntaxes.rb +13 -0
  83. data/lib/gamefic/scripting.rb +43 -0
  84. data/lib/gamefic/subplot.rb +11 -22
  85. data/lib/gamefic/syntax.rb +39 -24
  86. data/lib/gamefic/version.rb +1 -1
  87. data/lib/gamefic.rb +6 -7
  88. metadata +38 -41
  89. data/lib/gamefic/active/epic.rb +0 -74
  90. data/lib/gamefic/active/take.rb +0 -67
  91. data/lib/gamefic/block.rb +0 -28
  92. data/lib/gamefic/callback.rb +0 -16
  93. data/lib/gamefic/proxy/plot_pick.rb +0 -11
  94. data/lib/gamefic/query/abstract.rb +0 -12
  95. data/lib/gamefic/query/general.rb +0 -41
  96. data/lib/gamefic/query/scoped.rb +0 -27
  97. data/lib/gamefic/rulebook/calls.rb +0 -86
  98. data/lib/gamefic/rulebook/events.rb +0 -65
  99. data/lib/gamefic/rulebook/hooks.rb +0 -57
  100. data/lib/gamefic/rulebook/scenes.rb +0 -68
  101. data/lib/gamefic/rulebook.rb +0 -125
  102. data/lib/gamefic/scene/default.rb +0 -88
  103. data/lib/gamefic/scope/base.rb +0 -44
  104. data/lib/gamefic/scope/children.rb +0 -16
  105. data/lib/gamefic/scope/descendants.rb +0 -16
  106. data/lib/gamefic/scope/family.rb +0 -43
  107. data/lib/gamefic/scope/myself.rb +0 -13
  108. data/lib/gamefic/scope/parent.rb +0 -13
  109. data/lib/gamefic/scope/siblings.rb +0 -14
  110. data/lib/gamefic/scope.rb +0 -9
  111. data/lib/gamefic/scriptable/actions.rb +0 -137
  112. data/lib/gamefic/scriptable/events.rb +0 -71
  113. data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
  114. data/lib/gamefic/snapshot.rb +0 -44
  115. data/lib/gamefic/stage.rb +0 -51
  116. data/lib/gamefic/syntax/template.rb +0 -67
  117. data/lib/gamefic/vault.rb +0 -52
@@ -59,13 +59,13 @@ module Gamefic
59
59
  # @return [String, nil]
60
60
 
61
61
  # @param key [Symbol]
62
- def [] key
62
+ def [](key)
63
63
  raw_data[key]
64
64
  end
65
65
 
66
66
  # @param key [Symbol]
67
67
  # @param value [Object]
68
- def []= key, value
68
+ def []=(key, value)
69
69
  raw_data[key] = value
70
70
  end
71
71
 
@@ -74,15 +74,15 @@ module Gamefic
74
74
  raw_data.dup
75
75
  end
76
76
 
77
- def to_json _ = nil
77
+ def to_json(_ = nil)
78
78
  raw_data.to_json
79
79
  end
80
80
 
81
- def merge! data
81
+ def merge!(data)
82
82
  data.each { |key, val| self[key] = val }
83
83
  end
84
84
 
85
- def replace data
85
+ def replace(data)
86
86
  raw_data.replace data
87
87
  end
88
88
 
@@ -102,6 +102,8 @@ module Gamefic
102
102
  def respond_to_missing?(method, _with_private = false)
103
103
  READER_METHODS.include?(method) || WRITER_METHODS.include?(method)
104
104
  end
105
+
106
+ EMPTY = new.freeze
105
107
  end
106
108
  end
107
109
  end
@@ -4,7 +4,7 @@ module Gamefic
4
4
  module Props
5
5
  # A MultipleChoice variant that only allows Yes or No.
6
6
  #
7
- class YesOrNo < MultipleChoice
7
+ class YesOrNo < MultiplePartial
8
8
  def yes?
9
9
  selection == 'Yes'
10
10
  end
@@ -14,7 +14,7 @@ module Gamefic
14
14
  end
15
15
 
16
16
  def options
17
- @options ||= %w[Yes No].freeze
17
+ @options ||= %w[Yes No]
18
18
  end
19
19
  end
20
20
  end
data/lib/gamefic/props.rb CHANGED
@@ -4,6 +4,7 @@ module Gamefic
4
4
  module Props
5
5
  require 'gamefic/props/default'
6
6
  require 'gamefic/props/multiple_choice'
7
+ require 'gamefic/props/multiple_partial'
7
8
  require 'gamefic/props/pause'
8
9
  require 'gamefic/props/yes_or_no'
9
10
  require 'gamefic/props/output'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Proxy
5
+ class Attr < Base
6
+ def fetch(narrative)
7
+ args.inject(narrative) { |object, key| object.send(key) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,27 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- class Proxy
4
+ module Proxy
5
5
  class Base
6
6
  attr_reader :args
7
7
 
8
- def initialize *args, raise: false
8
+ def initialize *args
9
9
  @args = args
10
- @raise = raise
11
10
  end
12
11
 
13
- def raise?
14
- @raise
15
- end
16
-
17
- def fetch narrative
18
- result = select(narrative)
19
- return result if result
20
- raise "#{self.class} failed for #{args.inspect}" if raise?
21
- end
22
-
23
- def select narrative
24
- end
12
+ def fetch(narrative); end
25
13
  end
26
14
  end
27
15
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- class Proxy
4
+ module Proxy
5
5
  class Config < Base
6
- def select narrative
6
+ def fetch(narrative)
7
7
  args.inject(narrative.config) { |hash, key| hash[key] }
8
8
  end
9
9
 
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- class Proxy
4
+ module Proxy
5
5
  class Pick < Base
6
- def select narrative
7
- raise? ? narrative.pick!(*args) : narrative.pick(*args)
6
+ def fetch(narrative)
7
+ narrative.pick(*args)
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Proxy
5
+ class PickEx < Base
6
+ def fetch(narrative)
7
+ narrative.pick!(*args)
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/gamefic/proxy.rb CHANGED
@@ -1,79 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- # @todo Turn this into a module after the old proxies are completely deprecated
5
- #
6
- class Proxy
4
+ module Proxy
7
5
  require 'gamefic/proxy/base'
6
+ require 'gamefic/proxy/attr'
8
7
  require 'gamefic/proxy/config'
9
8
  require 'gamefic/proxy/pick'
10
- require 'gamefic/proxy/plot_pick'
11
-
12
- TYPES = %i[attr ivar pick pick! plot_pick plot_pick! config].freeze
13
-
14
- # @return [Symbol]
15
- attr_reader :type
16
-
17
- # @return [Symbol, Array<Symbol>, String, Integer]
18
- attr_reader :key
19
-
20
- # @param type [Symbol]
21
- # @param key [Symbol, String, Array]
22
- def initialize type, key
23
- Gamefic.logger.debug "Using deprecated #{type} proxy"
24
- @type = type
25
- validate_type
26
- @key = type == :config ? [key].compact : key
27
- end
28
-
29
- def fetch narrative
30
- send(type, narrative) ||
31
- raise(ArgumentError, "Unable to fetch entity from proxy agent symbol `#{key}`")
32
- end
33
-
34
- def [](key)
35
- raise ArgumentError, 'Invalid []' unless type == :config
36
-
37
- @key.push key
38
- self
39
- end
40
-
41
- private
42
-
43
- def attr narrative
44
- Stage.run(narrative, [key].flatten) { |keys| keys.inject(self) { |obj, key| obj.send key } }
45
- rescue NoMethodError
46
- nil
47
- end
48
-
49
- def ivar narrative
50
- narrative.instance_variable_get key
51
- end
52
-
53
- def pick narrative
54
- narrative.pick *key
55
- end
56
-
57
- def pick! narrative
58
- narrative.pick! *key
59
- end
60
-
61
- def plot_pick narrative
62
- narrative.plot.pick *key
63
- end
64
-
65
- def plot_pick! narrative
66
- narrative.plot.pick! *key
67
- end
68
-
69
- def config narrative
70
- key.inject(narrative.config) { |hash, key| hash[key] }
71
- end
72
-
73
- def validate_type
74
- return if TYPES.include?(type)
75
-
76
- raise ArgumentError, "Invalid proxy type `#{type}` (must be #{TYPES.join_or})"
77
- end
9
+ require 'gamefic/proxy/pick_ex'
78
10
  end
79
11
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's parent and accessible grandparents.
6
+ #
7
+ class Ascendants < Base
8
+ include Subqueries
9
+
10
+ def span(subject)
11
+ [subject.parent].tap { |result| result.push result.last.parent while result.last&.parent&.accessible&.include?(result.last) }
12
+ .compact
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,61 +3,46 @@
3
3
  module Gamefic
4
4
  module Query
5
5
  # A base class for entity-based queries that can be applied to responses.
6
- # Each query represents an attempt to match an argument in a command to a
7
- # game entity.
6
+ # Each query matches a command token to an object that can be passed into
7
+ # a response callback.
8
+ #
9
+ # Most queries return entities, but there are also queries for plain text
10
+ # and integers.
8
11
  #
9
12
  class Base
10
13
  # @return [Array<Object>]
11
14
  attr_reader :arguments
12
15
 
13
- # @return [Boolean]
14
- attr_reader :ambiguous
15
-
16
- attr_accessor :narrative
17
-
18
16
  # @raise [ArgumentError] if any of the arguments are nil
19
17
  #
20
18
  # @param arguments [Array<Object>]
21
- # @param ambiguous [Boolean]
22
19
  # @param name [String]
23
- def initialize *arguments, ambiguous: false, name: self.class.to_s
20
+ def initialize *arguments, name: self.class.to_s
24
21
  raise ArgumentError, "nil argument in query" if arguments.any?(&:nil?)
25
22
 
26
23
  @arguments = arguments
27
- @ambiguous = ambiguous
28
24
  @name = name
29
25
  end
30
26
 
31
27
  # Get a query result for a given subject and token.
32
28
  #
33
- # @example
34
- # respond :reds do |actor|
35
- # reds = available(ambiguous: true).query(actor, 'red').match
36
- # actor.tell "The red things you can see here are #{reds.join_and}."
37
- # end
38
- #
39
29
  # @param subject [Gamefic::Entity]
40
30
  # @param token [String]
41
31
  # @return [Result]
42
- def query(subject, token)
43
- first_pass = Scanner.scan(span(subject), token)
44
- if ambiguous?
45
- ambiguous_result(first_pass.filter(*normalized_arguments))
46
- elsif first_pass.match.one?
47
- unambiguous_result(first_pass.filter(*normalized_arguments))
48
- else
49
- unambiguous_result(first_pass)
50
- end
32
+ def filter(subject, token)
33
+ scan = Scanner.scan(select(subject), token)
34
+ return Result.new(nil, scan.token) unless scan.matched.one?
35
+
36
+ Result.new(scan.matched.first, scan.remainder, scan.strictness)
51
37
  end
52
- alias filter query
53
38
 
54
39
  # Get an array of entities that match the arguments from the context of
55
40
  # the subject.
56
41
  #
57
42
  # @param subject [Entity]
58
43
  # @return [Array<Entity>]
59
- def select subject
60
- span(subject).that_are(*normalized_arguments)
44
+ def select(subject)
45
+ span(subject).that_are(*arguments)
61
46
  end
62
47
 
63
48
  # Get an array of entities that are candidates for selection from the
@@ -68,47 +53,65 @@ module Gamefic
68
53
  #
69
54
  # @param subject [Entity]
70
55
  # @return [Array<Entity>]
71
- def span _subject
56
+ def span(_subject)
72
57
  []
73
58
  end
74
59
 
75
60
  # True if the object is selectable by the subject.
76
61
  #
77
62
  # @param subject [Entity]
78
- # @param object [Array<Entity>, Entity]
63
+ # @param object [Entity]
79
64
  # @return [Boolean]
80
65
  def accept?(subject, object)
81
- available = select(subject)
82
- if ambiguous?
83
- object & available == object
84
- else
85
- available.include?(object)
86
- end
66
+ select(subject).include?(object)
87
67
  end
88
68
 
69
+ # The query's precision. The higher the number, the more specific the
70
+ # query is.
71
+ #
72
+ # In general terms, a query's precision is highest if its arguments
73
+ # select for a specific instance of an entity instead of a class of
74
+ # entity.
75
+ #
76
+ # When a command gets parsed, the resulting list of available actions
77
+ # gets sorted in descending order of their responses' overall precision,
78
+ # so the action with the highest precision gets attempted first.
79
+ #
89
80
  # @return [Integer]
90
81
  def precision
91
82
  @precision ||= calculate_precision
92
83
  end
93
84
 
94
- def ambiguous?
95
- @ambiguous
96
- end
97
-
98
85
  def name
99
86
  @name || self.class.to_s
100
87
  end
101
88
 
102
89
  def inspect
103
- "##{ambiguous? ? '*' : ''}#{name}(#{normalized_arguments.map(&:inspect).join(', ')})"
90
+ "#{name}(#{arguments.map(&:inspect).join(', ')})"
91
+ end
92
+
93
+ def bind(narrative)
94
+ clone.tap do |query|
95
+ query.instance_exec do
96
+ @arguments = narrative.unproxy(@arguments)
97
+ end
98
+ end
99
+ end
100
+
101
+ def self.plain
102
+ @plain ||= new
103
+ end
104
+
105
+ def self.span(subject)
106
+ plain.span(subject)
104
107
  end
105
108
 
106
109
  private
107
110
 
108
111
  def calculate_precision
109
- normalized_arguments.sum(@ambiguous ? -1000 : 0) do |arg|
112
+ arguments.sum(0) do |arg|
110
113
  case arg
111
- when Entity, Proxy, Proxy::Base
114
+ when Entity, Proxy::Base
112
115
  1000
113
116
  when Class, Module
114
117
  class_depth(arg) * 100
@@ -118,7 +121,7 @@ module Gamefic
118
121
  end
119
122
  end
120
123
 
121
- def class_depth klass
124
+ def class_depth(klass)
122
125
  return 1 unless klass.is_a?(Class)
123
126
 
124
127
  depth = 1
@@ -126,35 +129,6 @@ module Gamefic
126
129
  depth += 1 while (sup = sup.superclass)
127
130
  depth
128
131
  end
129
-
130
- # @param scan [Scanner::Result]
131
- def ambiguous_result scan
132
- return Result.new(nil, scan.token) if scan.matched.empty?
133
-
134
- Result.new(scan.matched, scan.remainder)
135
- end
136
-
137
- # @param scan [Scanner::Result]
138
- def unambiguous_result scan
139
- return Result.new(nil, scan.token) unless scan.matched.one?
140
-
141
- Result.new(scan.matched.first, scan.remainder, scan.strictness)
142
- end
143
-
144
- def normalized_arguments
145
- @normalized_arguments ||= arguments.map do |arg|
146
- case arg
147
- when Proxy, Proxy::Base
148
- arg.fetch(narrative)
149
- when String
150
- proc do |entity|
151
- arg.keywords.all? { |word| entity.keywords.include?(word) }
152
- end
153
- else
154
- arg
155
- end
156
- end
157
- end
158
132
  end
159
133
  end
160
134
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's children.
6
+ #
7
+ class Children < Base
8
+ include Subqueries
9
+
10
+ def span(subject)
11
+ subject.children
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's children and accessible grandchildren.
6
+ #
7
+ class Descendants < Base
8
+ include Subqueries
9
+
10
+ def span(subject)
11
+ subject.children.flat_map do |child|
12
+ [child] + subquery_accessible(child)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's siblings and their descendants. Unlike `Family`, the
6
+ # subject's descendants are excluded from results.
7
+ #
8
+ # Descendants need to be `accessible` to be included in the query.
9
+ #
10
+ class Extended < Base
11
+ include Subqueries
12
+
13
+ def span(subject)
14
+ Siblings.span(subject).flat_map do |child|
15
+ [child] + subquery_accessible(child)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's ascendants, descendants, siblings, and siblings'
6
+ # descendants.
7
+ #
8
+ # Entities other than the subject's parent and immediate children need to
9
+ # be `accessible` to be included in the query.
10
+ #
11
+ class Family < Base
12
+ include Subqueries
13
+
14
+ def span(subject)
15
+ Ascendants.span(subject) + Descendants.span(subject) + match_sibling_branches(subject)
16
+ end
17
+
18
+ private
19
+
20
+ def match_sibling_branches(subject)
21
+ Siblings.span(subject).flat_map do |child|
22
+ [child] + subquery_accessible(child)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query all the entities in the subject's epic.
6
+ #
7
+ # If the subject is not an actor, this query will always return an empty
8
+ # result.
9
+ #
10
+ class Global < Base
11
+ def span(subject)
12
+ return [] unless subject.is_a?(Active)
13
+
14
+ subject.narratives.entities
15
+ end
16
+
17
+ def precision
18
+ @precision ||= super - 2000
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # A special query that handles integers instead of entities.
6
+ #
7
+ class Integer < Base
8
+ # @param name [String, nil]
9
+ def initialize(name: self.class.name)
10
+ super(name: name)
11
+ end
12
+
13
+ def filter(_subject, token)
14
+ return Result.new(token, '') if token.is_a?(::Integer)
15
+
16
+ words = token.keywords
17
+ number = words.shift
18
+ return Result.new(nil, token) unless number =~ /\d+/
19
+
20
+ Result.new(number.to_i, words.join(' '))
21
+ end
22
+
23
+ def precision
24
+ -10_000
25
+ end
26
+
27
+ def accept?(_subject, token)
28
+ token.is_a?(::Integer)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject itself.
6
+ #
7
+ class Myself < Base
8
+ def span(subject)
9
+ [subject]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's parent.
6
+ #
7
+ class Parent < Base
8
+ def span(subject)
9
+ [subject.parent].compact
10
+ end
11
+ end
12
+ end
13
+ end
@@ -13,7 +13,7 @@ module Gamefic
13
13
 
14
14
  attr_reader :strictness
15
15
 
16
- def initialize match, remainder, strictness = 0
16
+ def initialize(match, remainder, strictness = 0)
17
17
  @match = match
18
18
  @remainder = remainder
19
19
  @strictness = strictness
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ # Query the subject's siblings (i.e., entities with the same parent).
6
+ class Siblings < Base
7
+ def span(subject)
8
+ (subject.parent&.children || []) - [subject]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Gamefic
2
+ module Query
3
+ module Subqueries
4
+ module_function
5
+
6
+ # Return an array of the entity's accessible descendants.
7
+ #
8
+ # @param entity [Entity]
9
+ # @return [Array<Entity>]
10
+ def subquery_accessible(entity)
11
+ entity.accessible.flat_map do |child|
12
+ [child] + subquery_accessible(child)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end