gamefic 2.4.0 → 3.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/.github/workflows/rspec.yml +41 -40
- data/.rspec-opal +2 -0
- data/.solargraph.yml +20 -3
- data/CHANGELOG.md +9 -0
- data/Rakefile +11 -1
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gamefic.gemspec +5 -2
- data/lib/gamefic/action.rb +52 -183
- data/lib/gamefic/active/cue.rb +25 -0
- data/lib/gamefic/active/epic.rb +68 -0
- data/lib/gamefic/active/messaging.rb +43 -0
- data/lib/gamefic/active/take.rb +69 -0
- data/lib/gamefic/active.rb +95 -192
- data/lib/gamefic/actor.rb +2 -0
- data/lib/gamefic/block.rb +28 -0
- data/lib/gamefic/command.rb +16 -6
- data/lib/gamefic/core_ext/array.rb +4 -4
- data/lib/gamefic/core_ext/string.rb +10 -5
- data/lib/gamefic/describable.rb +39 -65
- data/lib/gamefic/dispatcher.rb +63 -32
- data/lib/gamefic/entity.rb +44 -19
- data/lib/gamefic/logging.rb +32 -0
- data/lib/gamefic/messenger.rb +66 -0
- data/lib/gamefic/narrative.rb +104 -0
- data/lib/gamefic/node.rb +44 -53
- data/lib/gamefic/plot.rb +60 -93
- data/lib/gamefic/props/default.rb +41 -0
- data/lib/gamefic/props/multiple_choice.rb +65 -0
- data/lib/gamefic/props/pause.rb +11 -0
- data/lib/gamefic/props/yes_or_no.rb +21 -0
- data/lib/gamefic/props.rb +10 -0
- data/lib/gamefic/query/base.rb +45 -126
- data/lib/gamefic/query/general.rb +46 -0
- data/lib/gamefic/query/result.rb +20 -0
- data/lib/gamefic/query/scoped.rb +41 -0
- data/lib/gamefic/query/text.rb +30 -31
- data/lib/gamefic/query.rb +7 -15
- data/lib/gamefic/response.rb +118 -0
- data/lib/gamefic/rulebook/calls.rb +90 -0
- data/lib/gamefic/rulebook/events.rb +79 -0
- data/lib/gamefic/rulebook/hooks.rb +57 -0
- data/lib/gamefic/rulebook/scenes.rb +68 -0
- data/lib/gamefic/rulebook.rb +139 -0
- data/lib/gamefic/scanner.rb +103 -0
- data/lib/gamefic/scene/activity.rb +9 -17
- data/lib/gamefic/scene/conclusion.rb +6 -5
- data/lib/gamefic/scene/default.rb +88 -0
- data/lib/gamefic/scene/multiple_choice.rb +14 -69
- data/lib/gamefic/scene/pause.rb +9 -13
- data/lib/gamefic/scene/yes_or_no.rb +6 -46
- data/lib/gamefic/scene.rb +11 -7
- data/lib/gamefic/scope/base.rb +44 -0
- data/lib/gamefic/scope/children.rb +16 -0
- data/lib/gamefic/scope/family.rb +20 -0
- data/lib/gamefic/scope/myself.rb +13 -0
- data/lib/gamefic/scope/parent.rb +13 -0
- data/lib/gamefic/scope/siblings.rb +14 -0
- data/lib/gamefic/scope.rb +8 -0
- data/lib/gamefic/scriptable/actions.rb +156 -0
- data/lib/gamefic/scriptable/entities.rb +76 -0
- data/lib/gamefic/scriptable/events.rb +65 -0
- data/lib/gamefic/scriptable/proxy.rb +55 -0
- data/lib/gamefic/scriptable/queries.rb +73 -0
- data/lib/gamefic/scriptable/scenes.rb +162 -0
- data/lib/gamefic/scriptable.rb +167 -73
- data/lib/gamefic/snapshot.rb +36 -0
- data/lib/gamefic/stage.rb +51 -0
- data/lib/gamefic/subplot.rb +51 -79
- data/lib/gamefic/syntax/template.rb +67 -0
- data/lib/gamefic/syntax.rb +102 -83
- data/lib/gamefic/vault.rb +50 -0
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +26 -15
- data/spec-opal/spec_helper.rb +24 -0
- metadata +91 -29
- data/lib/gamefic/element.rb +0 -46
- data/lib/gamefic/keywords.rb +0 -52
- data/lib/gamefic/messaging.rb +0 -43
- data/lib/gamefic/plot/darkroom.rb +0 -120
- data/lib/gamefic/plot/host.rb +0 -42
- data/lib/gamefic/plot/snapshot.rb +0 -27
- data/lib/gamefic/query/children.rb +0 -9
- data/lib/gamefic/query/descendants.rb +0 -15
- data/lib/gamefic/query/external.rb +0 -39
- data/lib/gamefic/query/family.rb +0 -18
- data/lib/gamefic/query/itself.rb +0 -13
- data/lib/gamefic/query/matches.rb +0 -75
- data/lib/gamefic/query/parent.rb +0 -9
- data/lib/gamefic/query/siblings.rb +0 -13
- data/lib/gamefic/query/tree.rb +0 -17
- data/lib/gamefic/scene/base.rb +0 -142
- data/lib/gamefic/scene/multiple_scene.rb +0 -29
- data/lib/gamefic/serialize.rb +0 -196
- data/lib/gamefic/world/callbacks.rb +0 -135
- data/lib/gamefic/world/commands.rb +0 -181
- data/lib/gamefic/world/entities.rb +0 -98
- data/lib/gamefic/world/playbook.rb +0 -233
- data/lib/gamefic/world/players.rb +0 -37
- data/lib/gamefic/world/scenes.rb +0 -228
- data/lib/gamefic/world.rb +0 -18
data/lib/gamefic/node.rb
CHANGED
@@ -1,58 +1,45 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
4
|
|
5
5
|
module Gamefic
|
6
|
+
# Exception raised when setting a node's parent to an invalid object.
|
7
|
+
#
|
8
|
+
class NodeError < RuntimeError; end
|
9
|
+
|
10
|
+
# Parent/child relationships for objects.
|
11
|
+
#
|
6
12
|
module Node
|
13
|
+
# The object's parent.
|
14
|
+
#
|
15
|
+
# @return [Node, nil]
|
16
|
+
attr_reader :parent
|
17
|
+
|
7
18
|
# An array of the object's children.
|
8
19
|
#
|
9
|
-
# @return [Array]
|
20
|
+
# @return [Array<Node>]
|
10
21
|
def children
|
11
|
-
|
12
|
-
@children.clone
|
22
|
+
child_set.to_a.freeze
|
13
23
|
end
|
14
24
|
|
15
25
|
# Get a flat array of all descendants.
|
16
26
|
#
|
17
|
-
# @return [Array]
|
27
|
+
# @return [Array<Node>]
|
18
28
|
def flatten
|
19
|
-
|
20
|
-
children.each { |child|
|
21
|
-
array = array + recurse_flatten(child)
|
22
|
-
}
|
23
|
-
array
|
24
|
-
end
|
25
|
-
|
26
|
-
# The object's parent.
|
27
|
-
#
|
28
|
-
# @return [Object]
|
29
|
-
def parent
|
30
|
-
@parent
|
29
|
+
children.flat_map { |child| [child] + child.flatten }
|
31
30
|
end
|
32
31
|
|
33
32
|
# Set the object's parent.
|
34
33
|
#
|
34
|
+
# @param node [Node, nil]
|
35
35
|
def parent=(node)
|
36
|
-
return if node ==
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
if node != nil and flatten.include?(node)
|
45
|
-
raise CircularNodeReferenceError.new("Node cannot be a child of a descendant")
|
46
|
-
end
|
47
|
-
if @parent != node
|
48
|
-
if @parent != nil
|
49
|
-
@parent.send(:rem_child, self)
|
50
|
-
end
|
51
|
-
@parent = node
|
52
|
-
if @parent != nil
|
53
|
-
@parent.send(:add_child, self)
|
54
|
-
end
|
55
|
-
end
|
36
|
+
return if node == parent
|
37
|
+
|
38
|
+
validate_parent node
|
39
|
+
|
40
|
+
parent&.rem_child self
|
41
|
+
@parent = node
|
42
|
+
parent&.add_child self
|
56
43
|
end
|
57
44
|
|
58
45
|
# Determine if external objects can interact with this object's children.
|
@@ -64,31 +51,35 @@ module Gamefic
|
|
64
51
|
true
|
65
52
|
end
|
66
53
|
|
54
|
+
# True if this node is the other's parent.
|
55
|
+
#
|
56
|
+
# @param other [Node]
|
57
|
+
def has?(other)
|
58
|
+
other.parent == self
|
59
|
+
end
|
60
|
+
|
67
61
|
protected
|
68
62
|
|
69
63
|
def add_child(node)
|
70
|
-
|
71
|
-
@children.push(node)
|
64
|
+
child_set.add node
|
72
65
|
end
|
73
66
|
|
74
67
|
def rem_child(node)
|
75
|
-
|
76
|
-
@children.delete(node)
|
68
|
+
child_set.delete node
|
77
69
|
end
|
78
70
|
|
79
|
-
|
80
|
-
|
71
|
+
private
|
72
|
+
|
73
|
+
def child_set
|
74
|
+
@child_set ||= Set.new
|
81
75
|
end
|
82
76
|
|
83
|
-
|
77
|
+
def validate_parent(node)
|
78
|
+
raise NodeError, 'Parent must be a Node' unless node.is_a?(Node) || node.nil?
|
79
|
+
|
80
|
+
raise NodeError, "Node cannot be its own parent" if node == self
|
84
81
|
|
85
|
-
|
86
|
-
array = Array.new
|
87
|
-
array.push(node)
|
88
|
-
node.children.each { |child|
|
89
|
-
array = array + recurse_flatten(child)
|
90
|
-
}
|
91
|
-
return array
|
82
|
+
raise NodeError, 'Node cannot be a child of a descendant' if flatten.include?(node)
|
92
83
|
end
|
93
84
|
end
|
94
85
|
end
|
data/lib/gamefic/plot.rb
CHANGED
@@ -1,114 +1,81 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Gamefic
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# special container called a stage. All of the elements that compose the
|
7
|
-
# narrative (characters, locations, scenes, etc.) reside in the stage's
|
8
|
-
# scope. Game engines use the plot to receive game data and process user
|
9
|
-
# input.
|
4
|
+
# The plot is the central narrative. It provides a script interface with
|
5
|
+
# methods for creating entities, actions, scenes, and hooks.
|
10
6
|
#
|
11
|
-
class Plot
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
class Plot < Narrative
|
8
|
+
def ready
|
9
|
+
super
|
10
|
+
subplots.each(&:ready)
|
11
|
+
players.each(&:start_take)
|
12
|
+
subplots.delete_if(&:concluding?)
|
13
|
+
players.select(&:concluding?).each { |plyr| rulebook.run_player_conclude_blocks plyr }
|
14
|
+
end
|
18
15
|
|
19
|
-
|
16
|
+
def update
|
17
|
+
players.each(&:finish_take)
|
18
|
+
super
|
19
|
+
subplots.each(&:update)
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
# Remove an actor from the game.
|
23
|
+
#
|
24
|
+
# Calling `uncast` on the plot will also remove the actor from its
|
25
|
+
# subplots.
|
26
|
+
#
|
27
|
+
# @param actor [Actor]
|
28
|
+
# @return [Actor]
|
29
|
+
def uncast actor
|
30
|
+
subplots.each { |sp| sp.uncast actor }
|
31
|
+
super
|
32
|
+
end
|
27
33
|
|
28
|
-
|
34
|
+
# Get an array of all the current subplots.
|
35
|
+
#
|
36
|
+
# @return [Array<Subplot>]
|
37
|
+
def subplots
|
38
|
+
@subplots ||= []
|
39
|
+
end
|
29
40
|
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
# Start a new subplot based on the provided class.
|
42
|
+
#
|
43
|
+
# @param subplot_class [Class<Gamefic::Subplot>] The Subplot class
|
44
|
+
# @param introduce [Gamefic::Actor, Array<Gamefic::Actor>] Players to introduce
|
45
|
+
# @param config [Hash] Subplot configuration
|
46
|
+
# @return [Gamefic::Subplot]
|
47
|
+
def branch subplot_class = Gamefic::Subplot, introduce: [], **config
|
48
|
+
subplot_class.new(self, introduce: introduce, **config)
|
49
|
+
.tap { |sub| subplots.push sub }
|
35
50
|
end
|
36
51
|
|
37
|
-
def
|
38
|
-
self
|
52
|
+
def save
|
53
|
+
Snapshot.save self
|
39
54
|
end
|
40
55
|
|
41
|
-
|
42
|
-
|
43
|
-
# @return [Array<Syntax>]
|
44
|
-
def syntaxes
|
45
|
-
playbook.syntaxes
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class}>"
|
46
58
|
end
|
47
59
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
playbook.freeze
|
54
|
-
call_ready
|
55
|
-
call_player_ready
|
56
|
-
subplots.each { |s| s.ready }
|
57
|
-
players.each do |p|
|
58
|
-
p.state.replace(
|
59
|
-
p.scene.state
|
60
|
-
.merge({
|
61
|
-
messages: p.messages,
|
62
|
-
last_prompt: p.last_prompt,
|
63
|
-
last_input: p.last_input,
|
64
|
-
queue: p.queue
|
65
|
-
})
|
66
|
-
.merge(p.state)
|
67
|
-
)
|
68
|
-
p.output.replace(p.state)
|
69
|
-
p.state.clear
|
70
|
-
end
|
60
|
+
def detach
|
61
|
+
cache = [@rulebook]
|
62
|
+
@rulebook = nil
|
63
|
+
cache.concat subplots.map(&:detach)
|
64
|
+
cache
|
71
65
|
end
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
#
|
77
|
-
def update
|
78
|
-
entities.each { |e| e.flush }
|
79
|
-
call_before_player_update
|
80
|
-
players.each do |p|
|
81
|
-
next unless p.scene
|
82
|
-
p.last_input = p.queue.last
|
83
|
-
p.last_prompt = p.scene.prompt
|
84
|
-
p.scene.update
|
85
|
-
if p.scene.is_a?(Scene::Conclusion)
|
86
|
-
player_conclude_procs.each do |proc|
|
87
|
-
proc.call p
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
call_player_update
|
92
|
-
call_update
|
93
|
-
subplots.delete_if(&:concluded?)
|
94
|
-
subplots.each(&:update)
|
67
|
+
def attach(cache)
|
68
|
+
super(cache.shift)
|
69
|
+
subplots.each { |subplot| subplot.attach cache.shift }
|
95
70
|
end
|
96
71
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
# @param message [String]
|
101
|
-
def tell entities, message
|
102
|
-
entities.each { |entity|
|
103
|
-
entity.tell message
|
104
|
-
}
|
72
|
+
def hydrate
|
73
|
+
super
|
74
|
+
subplots.each(&:hydrate)
|
105
75
|
end
|
106
|
-
end
|
107
|
-
end
|
108
76
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
Gamefic::Plot.script &block
|
77
|
+
def self.restore data
|
78
|
+
Snapshot.restore data
|
79
|
+
end
|
113
80
|
end
|
114
81
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Props
|
5
|
+
# A collection of data related to a scene. Scenes define which Props class
|
6
|
+
# they use. Props can be accessed in a scene's on_start and on_finish
|
7
|
+
# callbacks.
|
8
|
+
#
|
9
|
+
# Props::Default includes the most common attributes that a scene requires.
|
10
|
+
# Scenes are able but not required to subclass it. Some scenes, like
|
11
|
+
# MultipleChoice, use specialized Props subclasses, but in many cases,
|
12
|
+
# Props::Default is sufficient.
|
13
|
+
#
|
14
|
+
class Default
|
15
|
+
# @return [String]
|
16
|
+
attr_writer :prompt
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
attr_accessor :input
|
20
|
+
|
21
|
+
# A freeform dictionary of objects related to the scene. Plots can pass
|
22
|
+
# opts to be included in the context when they cue scenes.
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
attr_reader :context
|
26
|
+
alias data context
|
27
|
+
|
28
|
+
# @param scene [Scene, nil]
|
29
|
+
# @param context [Hash]
|
30
|
+
def initialize name, type, **context
|
31
|
+
@scene_name = name
|
32
|
+
@scene_type = type
|
33
|
+
@context = context
|
34
|
+
end
|
35
|
+
|
36
|
+
def prompt
|
37
|
+
@prompt ||= '>'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Props
|
5
|
+
# Props for MultipleChoice scenes.
|
6
|
+
#
|
7
|
+
class MultipleChoice < Default
|
8
|
+
# A message to send the player for an invalid choice. A formatting
|
9
|
+
# token named %<input>s can be used to inject the user input.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_writer :invalid_message
|
13
|
+
|
14
|
+
# The array of available options.
|
15
|
+
#
|
16
|
+
# @return [Array<String>]
|
17
|
+
def options
|
18
|
+
@options ||= []
|
19
|
+
end
|
20
|
+
|
21
|
+
def invalid_message
|
22
|
+
@invalid_message ||= '"%<input>s" is not a valid choice.'
|
23
|
+
end
|
24
|
+
|
25
|
+
# The zero-based index of the selected option.
|
26
|
+
#
|
27
|
+
# @return [Integer, nil]
|
28
|
+
def index
|
29
|
+
return nil unless input
|
30
|
+
|
31
|
+
@index ||= index_by_number || index_by_text
|
32
|
+
end
|
33
|
+
|
34
|
+
# The one-based index of the selected option.
|
35
|
+
#
|
36
|
+
# @return [Integer, nil]
|
37
|
+
def number
|
38
|
+
return nil unless index
|
39
|
+
|
40
|
+
index + 1
|
41
|
+
end
|
42
|
+
|
43
|
+
# The full text of the selected option.
|
44
|
+
#
|
45
|
+
# @return [String, nil]
|
46
|
+
def selection
|
47
|
+
return nil unless index
|
48
|
+
|
49
|
+
options[index]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def index_by_number
|
55
|
+
return input.to_i - 1 if input.match(/^\d+$/) && options[input.to_i - 1]
|
56
|
+
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def index_by_text
|
61
|
+
options.find_index { |text| input.downcase == text.downcase }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Props
|
5
|
+
# A MultipleChoice variant that only allows Yes or No.
|
6
|
+
#
|
7
|
+
class YesOrNo < MultipleChoice
|
8
|
+
def yes?
|
9
|
+
selection == 'Yes'
|
10
|
+
end
|
11
|
+
|
12
|
+
def no?
|
13
|
+
selection == 'No'
|
14
|
+
end
|
15
|
+
|
16
|
+
def options
|
17
|
+
@options ||= %w[Yes No].freeze
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/gamefic/query/base.rb
CHANGED
@@ -1,160 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
module Query
|
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.
|
8
|
+
#
|
3
9
|
class Base
|
4
|
-
|
5
|
-
|
10
|
+
# @return [Array<Object>]
|
6
11
|
attr_reader :arguments
|
7
12
|
|
8
|
-
def initialize *args
|
9
|
-
@arguments = args
|
10
|
-
end
|
11
|
-
|
12
|
-
# Determine whether the query allows ambiguous entity references.
|
13
|
-
# If false, actions that use this query will only be valid if the token
|
14
|
-
# passed into it resolves to a single entity. If true, actions will
|
15
|
-
# accept an array of matching entities instead.
|
16
|
-
# Queries are not ambiguous by default (ambiguous? == false).
|
17
|
-
#
|
18
13
|
# @return [Boolean]
|
19
|
-
|
20
|
-
false
|
21
|
-
end
|
14
|
+
attr_reader :ambiguous
|
22
15
|
|
23
|
-
#
|
24
|
-
# all entities that exist in the query's context.
|
16
|
+
# @raise [ArgumentError] if any of the arguments are nil
|
25
17
|
#
|
26
|
-
# @
|
27
|
-
|
28
|
-
|
29
|
-
|
18
|
+
# @param arguments [Array<Object>]
|
19
|
+
# @param ambiguous [Boolean]
|
20
|
+
def initialize *arguments, ambiguous: false
|
21
|
+
raise ArgumentError, "nil argument in query" if arguments.any?(&:nil?)
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
# @param subject [Entity]
|
35
|
-
# @param token [String]
|
36
|
-
# @param continued [Boolean]
|
37
|
-
# @return [Gamefic::Query::Matches]
|
38
|
-
def resolve(subject, token, continued: false)
|
39
|
-
available = context_from(subject)
|
40
|
-
return Matches.new([], '', token) if available.empty?
|
41
|
-
if continued
|
42
|
-
return Matches.execute(available, token, continued: continued)
|
43
|
-
elsif nested?(token)
|
44
|
-
drill = denest(available, token).select { |e| accept?(e) }
|
45
|
-
return Matches.new(drill, token, '') unless drill.length != 1
|
46
|
-
return Matches.new([], '', token)
|
47
|
-
end
|
48
|
-
result = available.select { |e| e.specified?(token) }
|
49
|
-
result = available.select { |e| e.specified?(token, fuzzy: true) } if result.empty?
|
50
|
-
result.keep_if { |e| accept? e }
|
51
|
-
Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
|
23
|
+
@arguments = arguments
|
24
|
+
@ambiguous = ambiguous
|
52
25
|
end
|
53
26
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
27
|
+
# @param subject [Gamefic::Entity]
|
28
|
+
# @param token [String]
|
29
|
+
# @return [Result]
|
30
|
+
def query(subject, token)
|
31
|
+
raise "#query not implemented for #{self.class}"
|
58
32
|
end
|
59
33
|
|
60
|
-
# A ranking of how precise the query's arguments are.
|
61
|
-
#
|
62
|
-
# Query precision is a factor in calculating Action#rank.
|
63
|
-
#
|
64
34
|
# @return [Integer]
|
65
35
|
def precision
|
66
|
-
|
67
|
-
@precision = 1
|
68
|
-
arguments.each { |a|
|
69
|
-
if a.is_a?(Class)
|
70
|
-
@precision += 100
|
71
|
-
elsif a.is_a?(Gamefic::Entity)
|
72
|
-
@precision += 1000
|
73
|
-
end
|
74
|
-
}
|
75
|
-
@precision
|
76
|
-
end
|
77
|
-
@precision
|
36
|
+
@precision ||= calculate_precision
|
78
37
|
end
|
79
|
-
alias rank precision
|
80
38
|
|
81
|
-
def
|
82
|
-
|
39
|
+
def ambiguous?
|
40
|
+
@ambiguous
|
83
41
|
end
|
84
42
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
(
|
94
|
-
elsif a.is_a?(Regexp)
|
95
|
-
!entity.to_s.match(a).nil?
|
96
|
-
elsif a.is_a?(Module) || a.is_a?(Class)
|
97
|
-
entity.is_a?(a)
|
43
|
+
private
|
44
|
+
|
45
|
+
def calculate_precision
|
46
|
+
@arguments.sum(@ambiguous ? -1000 : 0) do |arg|
|
47
|
+
case arg
|
48
|
+
when Entity, Scriptable::Proxy::Agent
|
49
|
+
1000
|
50
|
+
when Class, Module
|
51
|
+
class_depth(arg) * 100
|
98
52
|
else
|
99
|
-
|
53
|
+
1
|
100
54
|
end
|
101
|
-
break if result == false
|
102
55
|
end
|
103
|
-
result
|
104
56
|
end
|
105
57
|
|
106
|
-
|
58
|
+
def class_depth klass
|
59
|
+
return 1 unless klass.is_a?(Class)
|
107
60
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# @param [Entity]
|
113
|
-
# @return [Array<Object>]
|
114
|
-
def subquery_accessible entity
|
115
|
-
return [] if entity.nil?
|
116
|
-
result = []
|
117
|
-
if entity.accessible?
|
118
|
-
entity.children.each do |c|
|
119
|
-
result.push c
|
120
|
-
result.concat subquery_accessible(c)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
result
|
61
|
+
depth = 1
|
62
|
+
sup = klass
|
63
|
+
depth += 1 while (sup = sup.superclass)
|
64
|
+
depth
|
124
65
|
end
|
125
66
|
|
126
|
-
|
67
|
+
def ambiguous_result scan
|
68
|
+
return Result.new(nil, scan.token) if scan.matched.empty?
|
127
69
|
|
128
|
-
|
129
|
-
arguments.map do |a|
|
130
|
-
if a.is_a?(Class) || a.is_a?(Object)
|
131
|
-
a.to_s.split('::').last.downcase
|
132
|
-
else
|
133
|
-
a.to_s.downcase
|
134
|
-
end
|
135
|
-
end
|
70
|
+
Result.new(scan.matched, scan.remainder)
|
136
71
|
end
|
137
72
|
|
138
|
-
def
|
139
|
-
|
140
|
-
end
|
73
|
+
def unambiguous_result scan
|
74
|
+
return Result.new(nil, scan.token) unless scan.matched.one?
|
141
75
|
|
142
|
-
|
143
|
-
parts = token.split(NEST_REGEXP)
|
144
|
-
current = parts.pop
|
145
|
-
last_result = objects.select { |e| e.specified?(current) }
|
146
|
-
last_result = objects.select { |e| e.specified?(current, fuzzy: true) } if last_result.empty?
|
147
|
-
until parts.empty?
|
148
|
-
current = "#{parts.last} #{current}"
|
149
|
-
result = last_result.select { |e| e.specified?(current) }
|
150
|
-
result = last_result.select { |e| e.specified?(current, fuzzy: true) } if result.empty?
|
151
|
-
break if result.empty?
|
152
|
-
parts.pop
|
153
|
-
last_result = result
|
154
|
-
end
|
155
|
-
return [] if last_result.empty? or last_result.length > 1
|
156
|
-
return last_result if parts.empty?
|
157
|
-
denest(last_result[0].children, parts.join(' '))
|
76
|
+
Result.new(scan.matched.first, scan.remainder)
|
158
77
|
end
|
159
78
|
end
|
160
79
|
end
|