gamefic 2.0.2 → 2.2.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/.rubocop.yml +4 -1
- data/CHANGELOG.md +15 -1
- data/lib/gamefic/action.rb +37 -41
- data/lib/gamefic/active.rb +49 -76
- data/lib/gamefic/core_ext/array.rb +4 -1
- data/lib/gamefic/core_ext/string.rb +2 -8
- data/lib/gamefic/dispatcher.rb +76 -0
- data/lib/gamefic/element.rb +11 -12
- data/lib/gamefic/entity.rb +3 -3
- data/lib/gamefic/plot.rb +2 -9
- data/lib/gamefic/query/base.rb +25 -24
- data/lib/gamefic/query/descendants.rb +2 -2
- data/lib/gamefic/query/family.rb +2 -0
- data/lib/gamefic/query/text.rb +10 -11
- data/lib/gamefic/query/tree.rb +17 -0
- data/lib/gamefic/query.rb +1 -0
- data/lib/gamefic/scene/activity.rb +1 -3
- data/lib/gamefic/scene/base.rb +4 -2
- data/lib/gamefic/scene/conclusion.rb +1 -1
- data/lib/gamefic/scene/multiple_choice.rb +2 -12
- data/lib/gamefic/scene/pause.rb +1 -1
- data/lib/gamefic/scene/yes_or_no.rb +1 -1
- data/lib/gamefic/scene.rb +0 -1
- data/lib/gamefic/serialize.rb +15 -26
- data/lib/gamefic/subplot.rb +3 -12
- data/lib/gamefic/syntax.rb +25 -18
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic/world/commands.rb +7 -26
- data/lib/gamefic/world/entities.rb +0 -10
- data/lib/gamefic/world/playbook.rb +42 -88
- data/lib/gamefic/world/players.rb +18 -2
- data/lib/gamefic/world/scenes.rb +11 -11
- data/lib/gamefic/world.rb +2 -0
- data/lib/gamefic.rb +2 -2
- metadata +18 -18
- data/lib/gamefic/plot/index.rb +0 -92
- data/lib/gamefic/scene/custom.rb +0 -7
data/lib/gamefic/subplot.rb
CHANGED
@@ -11,8 +11,9 @@ module Gamefic
|
|
11
11
|
attr_reader :plot
|
12
12
|
|
13
13
|
# @param plot [Gamefic::Plot]
|
14
|
-
# @param introduce [Gamefic::Actor]
|
15
|
-
# @param next_cue [Class<Gamefic::Scene::Base
|
14
|
+
# @param introduce [Gamefic::Actor, nil]
|
15
|
+
# @param next_cue [Class<Gamefic::Scene::Base>, nil]
|
16
|
+
# @param more [Hash]
|
16
17
|
def initialize plot, introduce: nil, next_cue: nil, **more
|
17
18
|
@plot = plot
|
18
19
|
@next_cue = next_cue
|
@@ -98,15 +99,5 @@ module Gamefic
|
|
98
99
|
#
|
99
100
|
def configure more
|
100
101
|
end
|
101
|
-
|
102
|
-
# def to_serial(index)
|
103
|
-
# puts "Serializing #{self}"
|
104
|
-
# super
|
105
|
-
# end
|
106
|
-
|
107
|
-
# def from_serial index = []
|
108
|
-
# # @todo Customize subplot unserialization
|
109
|
-
# super
|
110
|
-
# end
|
111
102
|
end
|
112
103
|
end
|
data/lib/gamefic/syntax.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'gamefic/command'
|
2
|
-
|
3
1
|
module Gamefic
|
4
2
|
class Syntax
|
5
3
|
attr_reader :token_count, :first_word, :verb, :template, :command
|
@@ -57,17 +55,18 @@ module Gamefic
|
|
57
55
|
m = text.match(@regexp)
|
58
56
|
return nil if m.nil?
|
59
57
|
arguments = []
|
60
|
-
# HACK: Skip the first word if the verb is not nil.
|
61
|
-
# This is ugly.
|
62
58
|
b = @verb.nil? ? 0 : 1
|
59
|
+
xverb = @verb
|
63
60
|
@replace.to_s.split_words[b..-1].each { |r|
|
64
61
|
if r.match(/^\{\$[0-9]+\}$/)
|
65
62
|
arguments.push m[r[2..-2].to_i]
|
63
|
+
elsif arguments.empty? && xverb.nil?
|
64
|
+
xverb = r.to_sym
|
66
65
|
else
|
67
66
|
arguments.push r
|
68
67
|
end
|
69
68
|
}
|
70
|
-
Command.new
|
69
|
+
Command.new xverb, arguments
|
71
70
|
end
|
72
71
|
|
73
72
|
# Determine if the specified text matches the syntax's expected pattern.
|
@@ -86,28 +85,36 @@ module Gamefic
|
|
86
85
|
end
|
87
86
|
|
88
87
|
def ==(other)
|
88
|
+
return false unless other.is_a?(Syntax)
|
89
89
|
signature == other.signature
|
90
90
|
end
|
91
91
|
|
92
|
-
|
92
|
+
def eql?(other)
|
93
|
+
self == other
|
94
|
+
end
|
95
|
+
|
96
|
+
def hash
|
97
|
+
signature.hash
|
98
|
+
end
|
99
|
+
|
100
|
+
# Tokenize an Array of Commands from the specified text. The resulting
|
101
|
+
# array is in descending order of specificity, i.e., most to least matched
|
102
|
+
# tokens.
|
93
103
|
#
|
94
104
|
# @param text [String] The text to tokenize.
|
95
105
|
# @param syntaxes [Array<Gamefic::Syntax>] The Syntaxes to use.
|
96
106
|
# @return [Array<Gamefic::Command>] The tokenized commands.
|
97
107
|
def self.tokenize text, syntaxes
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
b.arguments.length <=> a.arguments.length
|
108
|
+
syntaxes
|
109
|
+
.map { |syn| syn.tokenize(text) }
|
110
|
+
.reject(&:nil?)
|
111
|
+
.sort do |a, b|
|
112
|
+
if a.arguments.length == b.arguments.length
|
113
|
+
b.verb.to_s <=> a.verb.to_s
|
114
|
+
else
|
115
|
+
b.arguments.length <=> a.arguments.length
|
116
|
+
end
|
108
117
|
end
|
109
|
-
}
|
110
|
-
matches
|
111
118
|
end
|
112
119
|
end
|
113
120
|
end
|
data/lib/gamefic/version.rb
CHANGED
@@ -95,25 +95,6 @@ module Gamefic
|
|
95
95
|
playbook.meta command, *queries, &proc
|
96
96
|
end
|
97
97
|
|
98
|
-
# Declare a dismabiguation response for actions.
|
99
|
-
# The disambigurator is executed when an action expects an argument to
|
100
|
-
# reference a specific entity but its query matched more than one. For
|
101
|
-
# example, "red" might refer to either a red key or a red book.
|
102
|
-
#
|
103
|
-
# If a disambiguator is not defined, the playbook will use its default
|
104
|
-
# implementation.
|
105
|
-
#
|
106
|
-
# @example Tell the player the list of ambiguous entities.
|
107
|
-
# disambiguate do |actor, entities|
|
108
|
-
# actor.tell "I don't know which you mean: #{entities.join_or}."
|
109
|
-
# end
|
110
|
-
#
|
111
|
-
# @yieldparam [Gamefic::Actor]
|
112
|
-
# @yieldparam [Array<Gamefic::Entity>]
|
113
|
-
def disambiguate &block
|
114
|
-
playbook.disambiguate &block
|
115
|
-
end
|
116
|
-
|
117
98
|
# Validate an order before a character can execute its command.
|
118
99
|
#
|
119
100
|
# @yieldparam [Gamefic::Director::Order]
|
@@ -144,7 +125,7 @@ module Gamefic
|
|
144
125
|
#
|
145
126
|
# @return [Array<String>]
|
146
127
|
def verbs
|
147
|
-
playbook.verbs.map
|
128
|
+
playbook.verbs.map(&:to_s).reject { |v| v.start_with?('_') }
|
148
129
|
end
|
149
130
|
|
150
131
|
# Get an Array of all Actions defined in the Plot.
|
@@ -164,20 +145,20 @@ module Gamefic
|
|
164
145
|
|
165
146
|
private
|
166
147
|
|
148
|
+
# @param queries [Array]
|
149
|
+
# @return [Array<Query::Base>]
|
167
150
|
def map_response_args queries
|
168
|
-
|
169
|
-
queries.each do |q|
|
151
|
+
queries.map do |q|
|
170
152
|
if q.is_a?(Regexp)
|
171
|
-
|
153
|
+
Gamefic::Query::Text.new(q)
|
172
154
|
elsif q.is_a?(Gamefic::Query::Base)
|
173
|
-
|
155
|
+
q
|
174
156
|
elsif q.is_a?(Gamefic::Element) || (q.is_a?(Class) && q <= Gamefic::Element)
|
175
|
-
|
157
|
+
get_default_query.new(q)
|
176
158
|
else
|
177
159
|
raise ArgumentError.new("Invalid argument for response: #{q}")
|
178
160
|
end
|
179
161
|
end
|
180
|
-
result
|
181
162
|
end
|
182
163
|
end
|
183
164
|
end
|
@@ -93,16 +93,6 @@ module Gamefic
|
|
93
93
|
def players
|
94
94
|
@players ||= []
|
95
95
|
end
|
96
|
-
|
97
|
-
# private
|
98
|
-
|
99
|
-
# def mark_static_entities
|
100
|
-
# @static_entity_length ||= entities.length
|
101
|
-
# end
|
102
|
-
|
103
|
-
# def static_entity_length
|
104
|
-
# @static_entity_length || 0
|
105
|
-
# end
|
106
96
|
end
|
107
97
|
end
|
108
98
|
end
|
@@ -1,20 +1,28 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
module World
|
3
5
|
# A collection of rules for performing commands.
|
4
6
|
#
|
5
7
|
class Playbook
|
6
|
-
def initialize commands: {}, syntaxes: [], validators: [], disambiguator: nil
|
7
|
-
@commands = commands
|
8
|
-
@syntaxes = syntaxes
|
9
|
-
@validators = validators
|
10
|
-
@disambiguator = disambiguator
|
11
|
-
end
|
12
|
-
|
13
8
|
# An array of available syntaxes.
|
14
9
|
#
|
15
10
|
# @return [Array<Gamefic::Syntax>]
|
16
|
-
|
17
|
-
|
11
|
+
attr_reader :syntaxes
|
12
|
+
|
13
|
+
# An array of defined validators.
|
14
|
+
#
|
15
|
+
# @return [Array<Proc>]
|
16
|
+
attr_reader :validators
|
17
|
+
|
18
|
+
# @param commands [Hash]
|
19
|
+
# @param syntaxes [Array<Syntax>, Set<Syntax>]
|
20
|
+
# @param validators [Array]
|
21
|
+
def initialize commands: {}, syntaxes: [], validators: []
|
22
|
+
@commands = commands
|
23
|
+
@syntax_set = syntaxes.to_set
|
24
|
+
sort_syntaxes
|
25
|
+
@validators = validators
|
18
26
|
end
|
19
27
|
|
20
28
|
# An array of available actions.
|
@@ -31,32 +39,6 @@ module Gamefic
|
|
31
39
|
@commands.keys
|
32
40
|
end
|
33
41
|
|
34
|
-
# An array of defined validators.
|
35
|
-
#
|
36
|
-
# @return [Array<Proc>]
|
37
|
-
def validators
|
38
|
-
@validators
|
39
|
-
end
|
40
|
-
|
41
|
-
# Get the action for handling ambiguous entity references.
|
42
|
-
#
|
43
|
-
def disambiguator
|
44
|
-
@disambiguator ||= Action.subclass(nil, Query::Base.new) do |actor, entities|
|
45
|
-
definites = []
|
46
|
-
entities.each { |entity|
|
47
|
-
definites.push entity.definitely
|
48
|
-
}
|
49
|
-
actor.tell "I don't know which you mean: #{definites.join_or}."
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Set the action for handling ambiguous entity references.
|
54
|
-
#
|
55
|
-
def disambiguate &block
|
56
|
-
@disambiguator = Action.subclass(nil, Query::Base.new, meta: true, &block)
|
57
|
-
@disambiguator
|
58
|
-
end
|
59
|
-
|
60
42
|
# Add a block that determines whether an action can be executed.
|
61
43
|
#
|
62
44
|
def validate &block
|
@@ -66,7 +48,7 @@ module Gamefic
|
|
66
48
|
# Get an Array of all Actions associated with the specified verb.
|
67
49
|
#
|
68
50
|
# @param verb [Symbol] The Symbol for the verb (e.g., :go or :look)
|
69
|
-
# @return [Array<Action
|
51
|
+
# @return [Array<Class<Action>>] The verb's associated Actions
|
70
52
|
def actions_for verb
|
71
53
|
@commands[verb] || []
|
72
54
|
end
|
@@ -142,40 +124,16 @@ module Gamefic
|
|
142
124
|
syn
|
143
125
|
end
|
144
126
|
|
145
|
-
# Get
|
146
|
-
#
|
147
|
-
# The command can either be a single string (e.g., "examine book") or a
|
148
|
-
# list of tokens (e.g., :examine, @book).
|
127
|
+
# Get a Dispatcher to select actions that can potentially be executed
|
128
|
+
# from the specified command string.
|
149
129
|
#
|
150
|
-
# @
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
result.concat dispatch_from_params(actor, command[0], command[1..-1])
|
155
|
-
end
|
156
|
-
if result.empty?
|
157
|
-
result.concat dispatch_from_string(actor, command.join(' '))
|
158
|
-
end
|
159
|
-
result
|
160
|
-
end
|
161
|
-
|
162
|
-
# Get an array of actions, derived from the specified command, that the
|
163
|
-
# actor can potentially execute.
|
164
|
-
# The command should be a plain-text string, e.g., "examine the book."
|
165
|
-
#
|
166
|
-
# @return [Array<Gamefic::Action>]
|
167
|
-
def dispatch_from_string actor, text
|
168
|
-
result = []
|
130
|
+
# @param actor [Actor]
|
131
|
+
# @param text [String]
|
132
|
+
# @return [Dispatcher]
|
133
|
+
def dispatch(actor, text)
|
169
134
|
commands = Syntax.tokenize(text, actor.syntaxes)
|
170
|
-
commands.
|
171
|
-
|
172
|
-
available.each { |a|
|
173
|
-
next if a.hidden?
|
174
|
-
o = a.attempt(actor, c.arguments)
|
175
|
-
result.unshift o unless o.nil?
|
176
|
-
}
|
177
|
-
}
|
178
|
-
sort_and_reduce_actions result
|
135
|
+
actions = commands.flat_map { |cmd| actions_for(cmd.verb).reject(&:hidden?) }
|
136
|
+
Dispatcher.new(actor, commands, sort_and_reduce_actions(actions))
|
179
137
|
end
|
180
138
|
|
181
139
|
# Get an array of actions, derived from the specified verb and params,
|
@@ -183,12 +141,8 @@ module Gamefic
|
|
183
141
|
#
|
184
142
|
# @return [Array<Gamefic::Action>]
|
185
143
|
def dispatch_from_params actor, verb, params
|
186
|
-
result = []
|
187
144
|
available = actions_for(verb)
|
188
|
-
|
189
|
-
result.unshift a.new(actor, params) if a.valid?(actor, params)
|
190
|
-
}
|
191
|
-
sort_and_reduce_actions result
|
145
|
+
Dispatcher.new(actor, [Command.new(verb, params)], sort_and_reduce_actions(available))
|
192
146
|
end
|
193
147
|
|
194
148
|
# Duplicate the playbook.
|
@@ -217,38 +171,38 @@ module Gamefic
|
|
217
171
|
user_friendly = action.verb.to_s.gsub(/_/, ' ')
|
218
172
|
args = []
|
219
173
|
used_names = []
|
220
|
-
action.queries.each
|
174
|
+
action.queries.each do |_c|
|
221
175
|
num = 1
|
222
176
|
new_name = ":var"
|
223
177
|
while used_names.include? new_name
|
224
|
-
num
|
178
|
+
num += 1
|
225
179
|
new_name = ":var#{num}"
|
226
180
|
end
|
227
181
|
used_names.push new_name
|
228
182
|
user_friendly += " #{new_name}"
|
229
183
|
args.push new_name
|
230
|
-
|
184
|
+
end
|
231
185
|
add_syntax Syntax.new(user_friendly.strip, "#{action.verb} #{args.join(' ')}") unless action.verb.to_s.start_with?('_')
|
232
186
|
end
|
233
187
|
|
234
188
|
def add_syntax syntax
|
235
|
-
if @commands[syntax.verb]
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
189
|
+
raise "No actions exist for \"#{syntax.verb}\"" if @commands[syntax.verb].nil?
|
190
|
+
sort_syntaxes if @syntax_set.add?(syntax)
|
191
|
+
end
|
192
|
+
|
193
|
+
def sort_and_reduce_actions arr
|
194
|
+
arr.sort_by.with_index { |a, i| [a.rank, i] }.reverse.uniq
|
195
|
+
end
|
196
|
+
|
197
|
+
def sort_syntaxes
|
198
|
+
@syntaxes = @syntax_set.sort do |a, b|
|
241
199
|
if a.token_count == b.token_count
|
242
|
-
# For syntaxes of the same length,
|
200
|
+
# For syntaxes of the same length, sort first word
|
243
201
|
b.first_word <=> a.first_word
|
244
202
|
else
|
245
203
|
b.token_count <=> a.token_count
|
246
204
|
end
|
247
|
-
|
248
|
-
end
|
249
|
-
|
250
|
-
def sort_and_reduce_actions arr
|
251
|
-
arr.sort_by.with_index{|a, i| [a.rank, -i]}.reverse.uniq(&:class)
|
205
|
+
end
|
252
206
|
end
|
253
207
|
end
|
254
208
|
end
|
@@ -2,6 +2,7 @@ module Gamefic
|
|
2
2
|
module World
|
3
3
|
module Players
|
4
4
|
include Gamefic::World::Entities
|
5
|
+
include Gamefic::World::Commands
|
5
6
|
|
6
7
|
# An array of entities that are currently connected to users.
|
7
8
|
#
|
@@ -10,12 +11,27 @@ module Gamefic
|
|
10
11
|
@players ||= []
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
+
def player_class cls = nil
|
15
|
+
STDERR.puts "Modifying player_class this way is deprecated. Use set_player_class instead" unless cls.nil?
|
16
|
+
@player_class = cls unless cls.nil?
|
17
|
+
@player_class ||= Gamefic::Actor
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param cls [Class]
|
21
|
+
def set_player_class cls
|
22
|
+
unless cls < Gamefic::Active && cls <= Gamefic::Entity
|
23
|
+
raise ArgumentError, "Player class must be an active entity"
|
24
|
+
end
|
25
|
+
@player_class = cls
|
26
|
+
end
|
27
|
+
|
28
|
+
# Make a character that a player will control on introduction.
|
14
29
|
#
|
15
30
|
# @return [Gamefic::Actor]
|
16
|
-
def
|
31
|
+
def make_player_character
|
17
32
|
cast player_class, name: 'yourself', synonyms: 'self myself you me', proper_named: true
|
18
33
|
end
|
34
|
+
alias get_player_character make_player_character
|
19
35
|
end
|
20
36
|
end
|
21
37
|
end
|
data/lib/gamefic/world/scenes.rb
CHANGED
@@ -95,10 +95,10 @@ module Gamefic
|
|
95
95
|
#
|
96
96
|
# @param prompt [String]
|
97
97
|
# @yieldparam [Gamefic::Actor]
|
98
|
-
# @yieldparam [Gamefic::Scene::
|
99
|
-
# @return [Class<Gamefic::Scene::
|
98
|
+
# @yieldparam [Gamefic::Scene::Base]
|
99
|
+
# @return [Class<Gamefic::Scene::Base>]
|
100
100
|
def question prompt = 'What is your answer?', &block
|
101
|
-
s = Scene::
|
101
|
+
s = Scene::Base.subclass do |actor, scene|
|
102
102
|
scene.prompt = prompt
|
103
103
|
scene.on_finish &block
|
104
104
|
end
|
@@ -150,23 +150,23 @@ module Gamefic
|
|
150
150
|
# Custom scenes should always specify the next scene to be cued or
|
151
151
|
# prepared. If not, the scene will get repeated on the next turn.
|
152
152
|
#
|
153
|
-
# This method creates a Scene::
|
153
|
+
# This method creates a Scene::Base by default. You can customize other
|
154
154
|
# scene types by specifying the class to create.
|
155
155
|
#
|
156
156
|
# @example Ask the user for a name
|
157
|
-
# @scene = custom do |scene|
|
158
|
-
#
|
159
|
-
# scene.on_finish do
|
160
|
-
# actor.name =
|
157
|
+
# @scene = custom do |actor, scene|
|
158
|
+
# scene.prompt = "What's your name?"
|
159
|
+
# scene.on_finish do
|
160
|
+
# actor.name = scene.input
|
161
161
|
# actor.tell "Hello, #{actor.name}!"
|
162
|
-
# actor.cue
|
162
|
+
# actor.cue default_scene
|
163
163
|
# end
|
164
164
|
# end
|
165
165
|
#
|
166
166
|
# @param cls [Class] The class of scene to be instantiated.
|
167
167
|
# @yieldparam [Gamefic::Actor]
|
168
|
-
# @return [Class<Gamefic::Scene::
|
169
|
-
def custom cls = Scene::
|
168
|
+
# @return [Class<Gamefic::Scene::Base>]
|
169
|
+
def custom cls = Scene::Base, &block
|
170
170
|
s = cls.subclass &block
|
171
171
|
scene_classes.push s
|
172
172
|
s
|