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