gamefic 1.5.1 → 1.6.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/lib/gamefic.rb +1 -3
- data/lib/gamefic/action.rb +140 -79
- data/lib/gamefic/character.rb +120 -53
- data/lib/gamefic/character/state.rb +12 -0
- data/lib/gamefic/core_ext/array.rb +53 -11
- data/lib/gamefic/core_ext/string.rb +1 -0
- data/lib/gamefic/describable.rb +37 -11
- data/lib/gamefic/engine/base.rb +17 -4
- data/lib/gamefic/engine/tty.rb +4 -0
- data/lib/gamefic/entity.rb +4 -15
- data/lib/gamefic/matchable.rb +50 -0
- data/lib/gamefic/messaging.rb +45 -0
- data/lib/gamefic/node.rb +16 -0
- data/lib/gamefic/plot.rb +27 -33
- data/lib/gamefic/plot/{article_mount.rb → articles.rb} +22 -22
- data/lib/gamefic/plot/callbacks.rb +30 -4
- data/lib/gamefic/plot/{command_mount.rb → commands.rb} +121 -121
- data/lib/gamefic/plot/entities.rb +3 -3
- data/lib/gamefic/plot/host.rb +3 -3
- data/lib/gamefic/plot/playbook.rb +74 -30
- data/lib/gamefic/plot/scenes.rb +149 -0
- data/lib/gamefic/plot/snapshot.rb +14 -39
- data/lib/gamefic/plot/theater.rb +73 -0
- data/lib/gamefic/query.rb +6 -19
- data/lib/gamefic/query/base.rb +127 -246
- data/lib/gamefic/query/children.rb +6 -7
- data/lib/gamefic/query/descendants.rb +15 -0
- data/lib/gamefic/query/family.rb +19 -7
- data/lib/gamefic/query/itself.rb +13 -0
- data/lib/gamefic/query/matches.rb +67 -11
- data/lib/gamefic/query/parent.rb +6 -7
- data/lib/gamefic/query/siblings.rb +10 -7
- data/lib/gamefic/query/text.rb +39 -35
- data/lib/gamefic/scene.rb +1 -1
- data/lib/gamefic/scene/active.rb +12 -6
- data/lib/gamefic/scene/base.rb +56 -5
- data/lib/gamefic/scene/conclusion.rb +3 -0
- data/lib/gamefic/scene/custom.rb +0 -83
- data/lib/gamefic/scene/multiple_choice.rb +54 -32
- data/lib/gamefic/scene/multiple_scene.rb +11 -6
- data/lib/gamefic/scene/pause.rb +3 -4
- data/lib/gamefic/scene/yes_or_no.rb +23 -9
- data/lib/gamefic/script/base.rb +4 -0
- data/lib/gamefic/subplot.rb +22 -19
- data/lib/gamefic/syntax.rb +7 -15
- data/lib/gamefic/user/base.rb +7 -13
- data/lib/gamefic/user/buffer.rb +7 -0
- data/lib/gamefic/user/tty.rb +13 -12
- data/lib/gamefic/version.rb +1 -1
- metadata +11 -37
- data/lib/gamefic/director.rb +0 -23
- data/lib/gamefic/director/delegate.rb +0 -126
- data/lib/gamefic/director/order.rb +0 -17
- data/lib/gamefic/director/parser.rb +0 -137
- data/lib/gamefic/keywords.rb +0 -67
- data/lib/gamefic/plot/query_mount.rb +0 -9
- data/lib/gamefic/plot/scene_mount.rb +0 -182
- data/lib/gamefic/query/ambiguous_children.rb +0 -5
- data/lib/gamefic/query/expression.rb +0 -47
- data/lib/gamefic/query/many_children.rb +0 -7
- data/lib/gamefic/query/plural_children.rb +0 -14
- data/lib/gamefic/query/self.rb +0 -10
- data/lib/gamefic/scene_data.rb +0 -10
- data/lib/gamefic/scene_data/base.rb +0 -12
- data/lib/gamefic/scene_data/multiple_choice.rb +0 -19
- data/lib/gamefic/scene_data/multiple_scene.rb +0 -21
- data/lib/gamefic/scene_data/yes_or_no.rb +0 -18
- data/lib/gamefic/serialized.rb +0 -24
- data/lib/gamefic/stage.rb +0 -106
@@ -0,0 +1,73 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
module Plot::Theater
|
4
|
+
# Execute a block of code in a subset of the object's scope. An object's
|
5
|
+
# stage is an isolated namespace that has its own instance variables and
|
6
|
+
# access to its owner's public methods.
|
7
|
+
#
|
8
|
+
# There are two ways to execute code on the stage. It will accept either a
|
9
|
+
# string of code with an optional file name and line number, or a proc
|
10
|
+
# with optional arguments. See module_eval and module_exec for more
|
11
|
+
# information.
|
12
|
+
#
|
13
|
+
# @example Evaluate a string of code
|
14
|
+
# stage "puts 'Hello'"
|
15
|
+
#
|
16
|
+
# @example Evaluate a string of code with a file name and line number
|
17
|
+
# stage "puts 'Hello'", "file.rb", 1
|
18
|
+
#
|
19
|
+
# @example Execute a block of code
|
20
|
+
# stage {
|
21
|
+
# puts 'Hello'
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
# @example Execute a block of code with arguments
|
25
|
+
# stage 'hello' { |message|
|
26
|
+
# puts message # <- prints 'hello'
|
27
|
+
# }
|
28
|
+
#
|
29
|
+
# @example Use an instance variable
|
30
|
+
# stage "@message = 'hello'"
|
31
|
+
# stage "puts @message" # <- prints 'hello'
|
32
|
+
#
|
33
|
+
# @return [Object] The value returned by the executed code
|
34
|
+
def stage *args, &block
|
35
|
+
if block.nil?
|
36
|
+
theater.module_eval *args
|
37
|
+
else
|
38
|
+
theater.module_exec *args, &block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The module that acts as an isolated namespace for staged code.
|
43
|
+
#
|
44
|
+
# @return [Module]
|
45
|
+
def theater
|
46
|
+
return @theater unless @theater.nil?
|
47
|
+
instance = self
|
48
|
+
|
49
|
+
@theater = Module.new do
|
50
|
+
define_singleton_method :method_missing do |symbol, *args, &block|
|
51
|
+
instance.public_send :public_send, symbol, *args, &block
|
52
|
+
end
|
53
|
+
|
54
|
+
define_singleton_method :stage do |*args|
|
55
|
+
raise NoMethodError.new("The stage method is not available from inside staged scripts")
|
56
|
+
end
|
57
|
+
|
58
|
+
define_singleton_method :to_s do
|
59
|
+
"[Theater]"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# HACK: Include the theater module in Object so that classes and modules
|
64
|
+
# defined in scripts are accessible from procs passed to the stage.
|
65
|
+
Object.class_exec(@theater) do |t|
|
66
|
+
include t
|
67
|
+
end
|
68
|
+
|
69
|
+
@theater
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/gamefic/query.rb
CHANGED
@@ -1,30 +1,17 @@
|
|
1
|
-
require 'gamefic/keywords'
|
1
|
+
#require 'gamefic/keywords'
|
2
2
|
|
3
3
|
module Gamefic
|
4
4
|
|
5
5
|
module Query
|
6
6
|
autoload :Base, 'gamefic/query/base'
|
7
|
-
autoload :Text, 'gamefic/query/text'
|
8
|
-
autoload :Expression, 'gamefic/query/expression'
|
9
|
-
autoload :Self, 'gamefic/query/self'
|
10
|
-
autoload :Parent, 'gamefic/query/parent'
|
11
7
|
autoload :Children, 'gamefic/query/children'
|
12
|
-
autoload :
|
13
|
-
autoload :AmbiguousChildren, 'gamefic/query/ambiguous_children'
|
14
|
-
autoload :PluralChildren, 'gamefic/query/plural_children'
|
15
|
-
autoload :Siblings, 'gamefic/query/siblings'
|
8
|
+
autoload :Descendants, 'gamefic/query/descendants'
|
16
9
|
autoload :Family, 'gamefic/query/family'
|
10
|
+
autoload :Itself, 'gamefic/query/itself'
|
17
11
|
autoload :Matches, 'gamefic/query/matches'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@allow_plurals = true
|
22
|
-
end
|
23
|
-
@allow_plurals
|
24
|
-
end
|
25
|
-
def self.allow_plurals= boolean
|
26
|
-
@allow_plurals = boolean
|
27
|
-
end
|
12
|
+
autoload :Parent, 'gamefic/query/parent'
|
13
|
+
autoload :Siblings, 'gamefic/query/siblings'
|
14
|
+
autoload :Text, 'gamefic/query/text'
|
28
15
|
end
|
29
16
|
|
30
17
|
end
|
data/lib/gamefic/query/base.rb
CHANGED
@@ -1,266 +1,147 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
test_arguments arguments
|
11
|
-
@optional = false
|
12
|
-
if arguments.include?(:optional)
|
13
|
-
@optional = true
|
14
|
-
arguments.delete :optional
|
1
|
+
module Gamefic
|
2
|
+
module Query
|
3
|
+
class Base
|
4
|
+
NEST_REGEXP = / in | on | of | from | inside /
|
5
|
+
|
6
|
+
attr_reader :arguments
|
7
|
+
|
8
|
+
def initialize *args
|
9
|
+
@arguments = args
|
15
10
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# Check whether the query allows ambiguous matches.
|
20
|
-
# If allowed, this query's
|
21
|
-
def allow_ambiguous?
|
22
|
-
false
|
23
|
-
end
|
24
|
-
def allow_many?
|
25
|
-
false
|
26
|
-
end
|
27
|
-
def last_match_for(subject)
|
28
|
-
@match_hash[subject]
|
29
|
-
end
|
30
|
-
def optional?
|
31
|
-
@optional
|
32
|
-
end
|
33
|
-
def context_from(subject)
|
34
|
-
subject
|
35
|
-
end
|
36
|
-
def validate(subject, object)
|
37
|
-
arr = context_from(subject)
|
38
|
-
@arguments.each { |arg|
|
39
|
-
arr = arr.that_are(arg)
|
40
|
-
}
|
41
|
-
if (allow_many? or allow_ambiguous?)
|
42
|
-
if object.kind_of?(Array)
|
43
|
-
return (object & arr) == object
|
44
|
-
end
|
45
|
-
return false
|
46
|
-
elsif !object.kind_of?(Array)
|
47
|
-
return arr.include?(object)
|
11
|
+
|
12
|
+
def ambiguous?
|
13
|
+
false
|
48
14
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
15
|
+
|
16
|
+
# Subclasses should override this method with the logic required to collect
|
17
|
+
# all entities that exist in the query's context.
|
18
|
+
#
|
19
|
+
# @return [Array<Object>]
|
20
|
+
def context_from(subject)
|
21
|
+
[]
|
55
22
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
23
|
+
|
24
|
+
# Get an array of objects that exist in the subject's context and match
|
25
|
+
# the provided token.
|
26
|
+
#
|
27
|
+
def resolve(subject, token, continued: false)
|
28
|
+
available = context_from(subject)
|
29
|
+
return Matches.new([], '', token) if available.empty?
|
30
|
+
if continued
|
31
|
+
return Matches.execute(available, token, continued: continued)
|
32
|
+
elsif nested?(token)
|
33
|
+
drill = denest(available, token)
|
34
|
+
drill.keep_if{ |e| accept?(e) }
|
35
|
+
return Matches.new(drill, token, '') unless drill.length != 1
|
36
|
+
return Matches.new([], '', token)
|
59
37
|
end
|
38
|
+
result = available.select{ |e| e.match?(token) }
|
39
|
+
result = available.select{ |e| e.match?(token, fuzzy: true) } if result.empty?
|
40
|
+
result.keep_if{ |e| accept? e }
|
41
|
+
Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
|
60
42
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if !subject.last_object.nil?
|
67
|
-
obj = subject.last_object
|
68
|
-
if validate(subject, obj)
|
69
|
-
matches = Matches.new([obj], "it", "")
|
70
|
-
end
|
71
|
-
end
|
43
|
+
|
44
|
+
def include?(subject, object)
|
45
|
+
return false unless accept?(object)
|
46
|
+
result = context_from(subject)
|
47
|
+
result.include?(object)
|
72
48
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
item = item.class
|
87
|
-
end
|
88
|
-
if item.kind_of?(Class)
|
89
|
-
s = item
|
90
|
-
while s != nil
|
91
|
-
@specificity += magnitude
|
92
|
-
s = s.superclass
|
49
|
+
|
50
|
+
def precision
|
51
|
+
if @precision.nil?
|
52
|
+
@precision = 1
|
53
|
+
arguments.each { |a|
|
54
|
+
if a.kind_of?(Symbol) or a.kind_of?(Regexp)
|
55
|
+
@precision += 1
|
56
|
+
elsif a.kind_of?(Class)
|
57
|
+
@precision += (count_superclasses(a) * 100)
|
58
|
+
elsif a.kind_of?(Module)
|
59
|
+
@precision += 10
|
60
|
+
elsif a.kind_of?(Object)
|
61
|
+
@precision += 1000
|
93
62
|
end
|
63
|
+
}
|
64
|
+
@precision
|
65
|
+
end
|
66
|
+
@precision
|
67
|
+
end
|
68
|
+
|
69
|
+
def rank
|
70
|
+
precision
|
71
|
+
end
|
72
|
+
|
73
|
+
def signature
|
74
|
+
"#{self.class.to_s.downcase}(#{@arguments.join(',')})"
|
75
|
+
end
|
76
|
+
|
77
|
+
def accept?(entity)
|
78
|
+
result = true
|
79
|
+
arguments.each { |a|
|
80
|
+
if a.kind_of?(Symbol)
|
81
|
+
result = (entity.send(a) != false)
|
82
|
+
elsif a.kind_of?(Regexp)
|
83
|
+
result = (!entity.to_s.match(a).nil?)
|
84
|
+
elsif a.is_a?(Module) or a.is_a?(Class)
|
85
|
+
result = (entity.is_a?(a))
|
94
86
|
else
|
95
|
-
|
87
|
+
result = (entity == a)
|
96
88
|
end
|
89
|
+
break if result == false
|
97
90
|
}
|
98
|
-
|
99
|
-
# HACK Ridiculously high magic number to force queries that return
|
100
|
-
# arrays to take precedence over everything
|
101
|
-
@specificity = @specificity * 10
|
102
|
-
end
|
91
|
+
result
|
103
92
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
if my_classes.length == 0 and my_objects.length == 0
|
119
|
-
raise ArgumentError.new("Query signature requires at least one class, module, or object to accept a method symbol")
|
120
|
-
end
|
121
|
-
if my_classes.length > 0
|
122
|
-
responds = false
|
123
|
-
my_classes.each { |c|
|
124
|
-
if c.instance_methods.include?(a)
|
125
|
-
responds = true
|
126
|
-
break
|
127
|
-
end
|
128
|
-
}
|
129
|
-
if !responds
|
130
|
-
raise ArgumentError.new("Query signature does not have a target that responds to #{a}")
|
131
|
-
end
|
132
|
-
end
|
133
|
-
my_objects.each { |o|
|
134
|
-
if !o.respond_to?(a)
|
135
|
-
raise ArgumentError.new("Query signature contains object '#{o}' that does not respond to '#{a}'")
|
136
|
-
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
# Return an array of the entity's children. If the child is neighborly,
|
97
|
+
# recursively append its children.
|
98
|
+
# The result will NOT include the original entity itself.
|
99
|
+
#
|
100
|
+
# @return [Array<Object>]
|
101
|
+
def subquery_accessible entity
|
102
|
+
result = []
|
103
|
+
if entity.accessible?
|
104
|
+
entity.children.each { |c|
|
105
|
+
result.push c
|
106
|
+
result.concat subquery_accessible(c)
|
137
107
|
}
|
138
|
-
else
|
139
|
-
raise ArgumentError.new("Invalid argument '#{a}' in query signature")
|
140
|
-
end
|
141
|
-
}
|
142
|
-
end
|
143
|
-
def match(description, array)
|
144
|
-
keywords = get_keywords(description)
|
145
|
-
array.each { |e|
|
146
|
-
if e.uid == keywords[0]
|
147
|
-
return Matches.new([e], keywords.shift, keywords.join(' '))
|
148
|
-
end
|
149
|
-
}
|
150
|
-
used = []
|
151
|
-
skipped = []
|
152
|
-
possibilities = array
|
153
|
-
at_least_one_match = false
|
154
|
-
while keywords.length > 0
|
155
|
-
next_word = keywords.shift
|
156
|
-
if @@subquery_prepositions.include?(next_word)
|
157
|
-
if !at_least_one_match
|
158
|
-
return Matches.new([], '', description)
|
159
|
-
end
|
160
|
-
so_far = keywords.join(' ')
|
161
|
-
in_matched = self.match(so_far, array)
|
162
|
-
if in_matched.objects.length > 0 and (in_matched.objects.length == 1 or in_matched.objects[0].kind_of?(Array))
|
163
|
-
# Subset matching should only consider the intersection of the
|
164
|
-
# original array and the matched object's children. This ensures
|
165
|
-
# that it won't erroneously match a child that was excluded from
|
166
|
-
# the original query.
|
167
|
-
parent = in_matched.objects.shift
|
168
|
-
subset = self.match(used.join(' '), (array & (parent.kind_of?(Array) ? parent[0].children : parent.children)))
|
169
|
-
if subset.objects.length == 1
|
170
|
-
if in_matched.objects.length == 0
|
171
|
-
return subset
|
172
|
-
else
|
173
|
-
return Matches.new([subset.objects] + in_matched.objects, subset.matching_text, subset.remainder)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
used.push next_word
|
179
|
-
new_results = []
|
180
|
-
possibilities.each { |p|
|
181
|
-
words = Keywords.new(used.last)
|
182
|
-
if words.length > 0
|
183
|
-
matches = words.found_in(p.keywords, (allow_many? or allow_ambiguous?))
|
184
|
-
if matches > 0
|
185
|
-
new_results.push p
|
186
|
-
end
|
187
|
-
end
|
188
|
-
}
|
189
|
-
if new_results.length > 0
|
190
|
-
at_least_one_match = true
|
191
|
-
intersection = possibilities & new_results
|
192
|
-
if intersection.length == 0
|
193
|
-
skipped.push used.pop
|
194
|
-
else
|
195
|
-
skipped.clear
|
196
|
-
possibilities = intersection
|
197
|
-
end
|
198
|
-
elsif (next_word.downcase == 'and' or next_word == ',')
|
199
|
-
while keywords.first == ',' or keywords.first.downcase == 'and'
|
200
|
-
used.push keywords.shift
|
201
|
-
end
|
202
|
-
if allow_ambiguous?
|
203
|
-
# Ambiguous queries filter based on all keywords instead of
|
204
|
-
# building an array of specified entities
|
205
|
-
next
|
206
|
-
end
|
207
|
-
so_far = keywords.join(' ')
|
208
|
-
recursed = self.match(so_far, array)
|
209
|
-
if possibilities.length == 1 and !allow_ambiguous?
|
210
|
-
possibilities = [possibilities]
|
211
|
-
else
|
212
|
-
# Force lists of things to be uniquely identifying
|
213
|
-
return Matches.new([], '', description)
|
214
|
-
end
|
215
|
-
objects = recursed.objects.clone
|
216
|
-
while objects.length > 0
|
217
|
-
obj = objects.shift
|
218
|
-
if obj.kind_of?(Array)
|
219
|
-
possibilities.push obj
|
220
|
-
else
|
221
|
-
combined = [obj] + objects
|
222
|
-
possibilities.push combined
|
223
|
-
break
|
224
|
-
end
|
225
|
-
end
|
226
|
-
used += recursed.matching_text.split_words
|
227
|
-
skipped = recursed.remainder.split_words
|
228
|
-
keywords = []
|
229
|
-
else
|
230
|
-
# The first unignored word must have at least one match
|
231
|
-
if at_least_one_match and !@@ignored_words.include?(used.last)
|
232
|
-
keywords.unshift used.pop
|
233
|
-
return Matches.new(possibilities, used.join(' '), keywords.join(' '))
|
234
|
-
else
|
235
|
-
if !@@ignored_words.include?(used.last)
|
236
|
-
return Matches.new([], '', description)
|
237
|
-
end
|
238
|
-
end
|
239
108
|
end
|
109
|
+
result
|
240
110
|
end
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def count_superclasses cls
|
115
|
+
s = cls.superclass
|
116
|
+
c = 1
|
117
|
+
until s.nil? or s == Object or s == BasicObject
|
118
|
+
c += 1
|
119
|
+
s = s.superclass
|
120
|
+
end
|
121
|
+
c
|
246
122
|
end
|
247
|
-
end
|
248
123
|
|
249
|
-
|
124
|
+
def nested?(token)
|
125
|
+
!token.match(NEST_REGEXP).nil?
|
126
|
+
end
|
250
127
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
128
|
+
def denest(objects, token)
|
129
|
+
parts = token.split(NEST_REGEXP)
|
130
|
+
current = parts.pop
|
131
|
+
last_result = objects.select{ |e| e.match?(current) }
|
132
|
+
last_result = objects.select{ |e| e.match?(current, fuzzy: true) } if last_result.empty?
|
133
|
+
result = last_result
|
134
|
+
while parts.length > 0
|
135
|
+
current = "#{parts.last} #{current}"
|
136
|
+
result = last_result.select{ |e| e.match?(current) }
|
137
|
+
result = last_result.select{ |e| e.match?(current, fuzzy: true) } if result.empty?
|
138
|
+
break if result.empty?
|
139
|
+
parts.pop
|
140
|
+
last_result = result
|
141
|
+
end
|
142
|
+
return [] if last_result.empty? or last_result.length > 1
|
143
|
+
return last_result if parts.empty?
|
144
|
+
denest(last_result[0].children, parts.join(' '))
|
264
145
|
end
|
265
146
|
end
|
266
147
|
end
|