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