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
@@ -10,7 +10,7 @@ module Gamefic
|
|
10
10
|
#
|
11
11
|
# @param cls [Class] The Class of the Entity to be created.
|
12
12
|
# @param args [Hash] The entity's properties.
|
13
|
-
# @return [Entity]
|
13
|
+
# @return [Gamefic::Entity]
|
14
14
|
def make cls, args = {}, &block
|
15
15
|
ent = cls.new args, &block
|
16
16
|
if ent.kind_of?(Entity) == false
|
@@ -42,8 +42,8 @@ module Gamefic
|
|
42
42
|
# pick "blue chair" #=> blue_chair
|
43
43
|
# pick "chair" #=> IndexError: description is ambiguous
|
44
44
|
#
|
45
|
-
# @param
|
46
|
-
# @return [Entity] The entity that matches the description
|
45
|
+
# @param description [String] The description of the entity
|
46
|
+
# @return [Gamefic::Entity] The entity that matches the description
|
47
47
|
def pick(description)
|
48
48
|
query = Gamefic::Query::Base.new
|
49
49
|
result = query.match(description, entities)
|
data/lib/gamefic/plot/host.rb
CHANGED
@@ -12,10 +12,10 @@ module Gamefic
|
|
12
12
|
|
13
13
|
# Start a new subplot based on the provided class.
|
14
14
|
#
|
15
|
-
# @param [Class] The class of the subplot to be created (Subplot by default)
|
15
|
+
# @param subplot_class [Class] The class of the subplot to be created (Subplot by default)
|
16
16
|
# @return [Subplot]
|
17
|
-
def branch subplot_class = Gamefic::Subplot, introduce: nil
|
18
|
-
subplot = subplot_class.new(self, introduce: introduce)
|
17
|
+
def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil, busy_cue: nil
|
18
|
+
subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, busy_cue: busy_cue)
|
19
19
|
p_subplots.push subplot
|
20
20
|
subplot
|
21
21
|
end
|
@@ -35,11 +35,11 @@ module Gamefic
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def disambiguate &block
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
38
|
+
#def disambiguate &block
|
39
|
+
# @disambiguator = Action.new(nil, Query::Base.new, &block)
|
40
|
+
# @disambiguator.meta = true
|
41
|
+
# @disambiguator
|
42
|
+
#end
|
43
43
|
|
44
44
|
def validate &block
|
45
45
|
@validators.push block
|
@@ -72,11 +72,12 @@ module Gamefic
|
|
72
72
|
# actor.tell "#{The character} returns your salute."
|
73
73
|
# end
|
74
74
|
#
|
75
|
-
# @param
|
76
|
-
# @param
|
77
|
-
# @yieldparam [Character]
|
78
|
-
|
79
|
-
|
75
|
+
# @param verb [Symbol] An imperative verb for the command
|
76
|
+
# @param queries [Array<Query::Base>] Filters for the command's tokens
|
77
|
+
# @yieldparam [Gamefic::Character]
|
78
|
+
# @return [Gamefic::Action]
|
79
|
+
def respond(verb, *queries, &proc)
|
80
|
+
act = Action.subclass verb, *queries, order_key: raise_order_key, &proc
|
80
81
|
add_action act
|
81
82
|
act
|
82
83
|
end
|
@@ -93,12 +94,12 @@ module Gamefic
|
|
93
94
|
# actor.tell "This game was written by John Smith."
|
94
95
|
# end
|
95
96
|
#
|
96
|
-
# @param
|
97
|
-
# @param
|
98
|
-
# @yieldparam [Character]
|
99
|
-
def meta(
|
100
|
-
act =
|
101
|
-
act
|
97
|
+
# @param verb [Symbol] An imperative verb for the command
|
98
|
+
# @param queries [Array<Query::Base>] Filters for the command's tokens
|
99
|
+
# @yieldparam [Gamefic::Character]
|
100
|
+
def meta(verb, *queries, &proc)
|
101
|
+
act = Action.subclass verb, *queries, meta: true, &proc
|
102
|
+
add_action act
|
102
103
|
act
|
103
104
|
end
|
104
105
|
|
@@ -113,15 +114,55 @@ module Gamefic
|
|
113
114
|
# interpret "scrutinize :entity", "look :entity"
|
114
115
|
# # The command "scrutinize chair" will be translated to "look chair"
|
115
116
|
#
|
116
|
-
# @param
|
117
|
+
# @param input [String] The format of the original command
|
117
118
|
# @param translation [String] The format of the translated command
|
118
119
|
# @return [Syntax] the Syntax object
|
119
|
-
def interpret(
|
120
|
-
syn = Syntax.new(
|
120
|
+
def interpret(input, translation)
|
121
|
+
syn = Syntax.new(input, translation)
|
121
122
|
add_syntax syn
|
122
123
|
syn
|
123
124
|
end
|
124
125
|
|
126
|
+
def dispatch(actor, *command)
|
127
|
+
result = []
|
128
|
+
if command.length > 1
|
129
|
+
result.concat dispatch_from_params(actor, command[0], command[1..-1])
|
130
|
+
end
|
131
|
+
if result.empty?
|
132
|
+
result.concat dispatch_from_string(actor, command.join(' '))
|
133
|
+
end
|
134
|
+
result.sort! { |a,b|
|
135
|
+
if a.rank == b.rank
|
136
|
+
b.order_key <=> a.order_key
|
137
|
+
else
|
138
|
+
b.rank <=> a.rank
|
139
|
+
end
|
140
|
+
}
|
141
|
+
result.uniq{|a| a.class}
|
142
|
+
end
|
143
|
+
|
144
|
+
def dispatch_from_string actor, text
|
145
|
+
result = []
|
146
|
+
commands = Syntax.tokenize(text, syntaxes)
|
147
|
+
commands.each { |c|
|
148
|
+
available = actions_for(c.verb)
|
149
|
+
available.each { |a|
|
150
|
+
o = a.attempt(actor, c.arguments)
|
151
|
+
result.unshift o unless o.nil?
|
152
|
+
}
|
153
|
+
}
|
154
|
+
result
|
155
|
+
end
|
156
|
+
|
157
|
+
def dispatch_from_params actor, verb, params
|
158
|
+
result = []
|
159
|
+
available = actions_for(verb)
|
160
|
+
available.each { |a|
|
161
|
+
result.unshift a.new(actor, params) if a.valid?(actor, params)
|
162
|
+
}
|
163
|
+
result
|
164
|
+
end
|
165
|
+
|
125
166
|
# Duplicate the playbook.
|
126
167
|
# This method will duplicate the commands hash and the syntax array so
|
127
168
|
# the new playbook can be modified without affecting the original.
|
@@ -140,16 +181,11 @@ module Gamefic
|
|
140
181
|
|
141
182
|
def add_action(action)
|
142
183
|
@commands[action.verb] ||= []
|
143
|
-
@commands[action.verb].
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
else
|
149
|
-
# Higher specificity takes precedence
|
150
|
-
b.specificity <=> a.specificity
|
151
|
-
end
|
152
|
-
}
|
184
|
+
@commands[action.verb].push action
|
185
|
+
#@commands[action.verb].uniq!
|
186
|
+
#@commands[action.verb].sort! { |a, b|
|
187
|
+
# b.rank <=> a.rank
|
188
|
+
#}
|
153
189
|
generate_default_syntax action
|
154
190
|
end
|
155
191
|
|
@@ -176,7 +212,7 @@ module Gamefic
|
|
176
212
|
raise "No actions exist for \"#{syntax.verb}\""
|
177
213
|
end
|
178
214
|
@syntaxes.unshift syntax
|
179
|
-
@syntaxes.uniq
|
215
|
+
@syntaxes.uniq!
|
180
216
|
@syntaxes.sort! { |a, b|
|
181
217
|
if a.token_count == b.token_count
|
182
218
|
# For syntaxes of the same length, length of action takes precedence
|
@@ -186,6 +222,14 @@ module Gamefic
|
|
186
222
|
end
|
187
223
|
}
|
188
224
|
end
|
225
|
+
|
226
|
+
def raise_order_key
|
227
|
+
@order_key ||= 0
|
228
|
+
tmp = @order_key
|
229
|
+
@order_key += 1
|
230
|
+
tmp
|
231
|
+
end
|
232
|
+
|
189
233
|
end
|
190
234
|
end
|
191
235
|
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
module Plot::Scenes
|
4
|
+
def default_scene
|
5
|
+
@default_scene ||= Scene::Active
|
6
|
+
end
|
7
|
+
|
8
|
+
def default_conclusion
|
9
|
+
@default_conclusion ||= Scene::Conclusion
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add a block to be executed when a player is added to the game.
|
13
|
+
# Each Plot can only have one introduction. Subsequent calls will
|
14
|
+
# overwrite the existing one.
|
15
|
+
#
|
16
|
+
# @example Welcome the player to the game
|
17
|
+
# introduction do |actor|
|
18
|
+
# actor.tell "Welcome to the game!"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @yieldparam [Gamefic::Character]
|
22
|
+
def introduction (&proc)
|
23
|
+
@introduction = proc
|
24
|
+
end
|
25
|
+
|
26
|
+
# Introduce a player to the game.
|
27
|
+
# This method is typically called by the Engine that manages game execution.
|
28
|
+
#
|
29
|
+
# @param [Gamefic::Character]
|
30
|
+
def introduce(player)
|
31
|
+
player.playbook = playbook
|
32
|
+
player.cue default_scene
|
33
|
+
p_players.push player
|
34
|
+
@introduction.call(player) unless @introduction.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a multiple-choice scene.
|
38
|
+
# The user will be required to make a valid choice to continue.
|
39
|
+
#
|
40
|
+
# @yieldparam [Gamefic::Character]
|
41
|
+
# @yieldparam [Gamefic::Scene::Data::MultipleChoice]
|
42
|
+
def multiple_choice *choices, &block
|
43
|
+
Scene::MultipleChoice.subclass do |actor, scene|
|
44
|
+
scene.options.concat choices
|
45
|
+
scene.on_finish &block
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create a yes-or-no scene.
|
50
|
+
# The user will be required to answer Yes or No to continue.
|
51
|
+
#
|
52
|
+
# @yieldparam [Gamefic::Character]
|
53
|
+
# @yieldparam [Gamefic::Scene::YesOrNo]
|
54
|
+
def yes_or_no prompt = nil, &block
|
55
|
+
Scene::YesOrNo.subclass do |actor, scene|
|
56
|
+
scene.prompt = prompt
|
57
|
+
scene.on_finish &block
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def question prompt = 'What is your answer?', &block
|
62
|
+
Scene::Custom.subclass do |actor, scene|
|
63
|
+
scene.prompt = prompt
|
64
|
+
scene.on_finish &block
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Create a scene that pauses the game.
|
69
|
+
# This scene will execute the specified block and wait for input from the
|
70
|
+
# the user (e.g., pressing Enter) to continue.
|
71
|
+
#
|
72
|
+
# @param prompt [String] The text to display when prompting the user to continue.
|
73
|
+
# @yieldparam [Gamefic::Character]
|
74
|
+
# @yieldparam [Gamefic::Scene::Pause]
|
75
|
+
def pause prompt = nil, &block
|
76
|
+
Scene::Pause.subclass do |actor, scene|
|
77
|
+
scene.prompt = prompt unless prompt.nil?
|
78
|
+
block.call(actor, scene) unless block.nil?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create a conclusion.
|
83
|
+
# The game (or the character's participation in it) will end after this
|
84
|
+
# scene is complete.
|
85
|
+
#
|
86
|
+
# @yieldparam [Gamefic::Character]
|
87
|
+
# @yieldparam [Gamefic::Scene::Conclusion]
|
88
|
+
def conclusion &block
|
89
|
+
Scene::Conclusion.subclass &block
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create a custom scene.
|
93
|
+
#
|
94
|
+
# Custom scenes should always specify the next scene to be cued or
|
95
|
+
# prepared. If not, the scene will get repeated on the next turn.
|
96
|
+
#
|
97
|
+
# This method creates a Scene::Custom by default. You can customize other
|
98
|
+
# scene types by specifying the class to create.
|
99
|
+
#
|
100
|
+
# @example Ask the user for a name
|
101
|
+
# @scene = custom do |scene|
|
102
|
+
# data.prompt = "What's your name?"
|
103
|
+
# scene.on_finish do |actor, data|
|
104
|
+
# actor.name = data.input
|
105
|
+
# actor.tell "Hello, #{actor.name}!"
|
106
|
+
# actor.cue :active
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# @param cls [Class] The class of scene to be instantiated.
|
111
|
+
# @yieldparam [Gamefic::Character]
|
112
|
+
# @yieldparam [Scene::Custom] The instantiated scene.
|
113
|
+
def custom cls = Scene::Custom, &block
|
114
|
+
cls.subclass &block
|
115
|
+
end
|
116
|
+
|
117
|
+
# Choose a new scene based on a list of options.
|
118
|
+
# This is a specialized type of multiple-choice scene that determines
|
119
|
+
# which scene to cue based on a Hash of choices and scene keys.
|
120
|
+
#
|
121
|
+
# @example Select a scene
|
122
|
+
# scene_one = pause do |actor|
|
123
|
+
# actor.tell "You went to scene one"
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# scene_two = pause do |actor|
|
127
|
+
# actor.tell "You went to scene two"
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# select_one_or_two = multiple_scene "One" => scene_one, "Two" => scene_two
|
131
|
+
#
|
132
|
+
# introduction do |actor|
|
133
|
+
# actor.cue select_one_or_two # The actor will be prompted to select "one" or "two" and get sent to the corresponding scene
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# @param map [Hash] A Hash of options and associated scene keys.
|
137
|
+
# @yieldparam [Gamefic::Character]
|
138
|
+
# @yieldparam [Gamefic::Scene::MultipleScene]
|
139
|
+
def multiple_scene map = {}, &block
|
140
|
+
Scene::MultipleScene.subclass do |actor, scene|
|
141
|
+
map.each_pair { |k, v|
|
142
|
+
scene.map k, v
|
143
|
+
}
|
144
|
+
block.call actor, scene unless block.nil?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -20,13 +20,14 @@ module Gamefic
|
|
20
20
|
end
|
21
21
|
return {
|
22
22
|
entities: store,
|
23
|
-
subplots: save_subplots
|
23
|
+
subplots: save_subplots,
|
24
|
+
metadata: metadata
|
24
25
|
}
|
25
26
|
end
|
26
27
|
|
27
28
|
# Restore the plot to the state of the provided snapshot.
|
28
29
|
#
|
29
|
-
# @param [Hash]
|
30
|
+
# @param snapshot [Hash]
|
30
31
|
def restore snapshot
|
31
32
|
restore_initial_state
|
32
33
|
internal_restore snapshot[:entities]
|
@@ -47,36 +48,15 @@ module Gamefic
|
|
47
48
|
|
48
49
|
def get_entity_hash
|
49
50
|
store = []
|
50
|
-
index = 0
|
51
51
|
entities.each { |e|
|
52
52
|
hash = {}
|
53
|
-
e.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
if e.respond_to?(m) == true
|
59
|
-
begin
|
60
|
-
val = e.send(m)
|
61
|
-
if val == false
|
62
|
-
hash[con] = false
|
63
|
-
elsif val
|
64
|
-
hash[con] = serialize_obj(val)
|
65
|
-
else
|
66
|
-
hash[con] = nil
|
67
|
-
end
|
68
|
-
rescue Exception => error
|
69
|
-
hash[con] = nil
|
70
|
-
end
|
71
|
-
end
|
53
|
+
e.instance_variables.each { |k|
|
54
|
+
next if k == :@children
|
55
|
+
v = e.instance_variable_get(k)
|
56
|
+
hash[k] = v
|
72
57
|
}
|
73
58
|
hash[:class] = e.class.to_s
|
74
|
-
hash
|
75
|
-
e.session.each_pair { |k, v|
|
76
|
-
hash[:session][k] = serialize_obj(v)
|
77
|
-
}
|
78
|
-
store.push hash
|
79
|
-
index += 1
|
59
|
+
store.push serialize_object(hash)
|
80
60
|
}
|
81
61
|
store
|
82
62
|
end
|
@@ -98,13 +78,8 @@ module Gamefic
|
|
98
78
|
hash.each { |k, v|
|
99
79
|
if k == :scene
|
100
80
|
entities[index].cue v.to_sym
|
101
|
-
elsif (k != :
|
102
|
-
entities[index].
|
103
|
-
end
|
104
|
-
unless hash[:session].nil?
|
105
|
-
hash[:session].each_pair { |k, v|
|
106
|
-
entities[index].session[k.to_sym] = unserialize(v)
|
107
|
-
}
|
81
|
+
elsif (k != :class)
|
82
|
+
entities[index].instance_variable_set(k, unserialize(v))
|
108
83
|
end
|
109
84
|
}
|
110
85
|
nil
|
@@ -135,7 +110,7 @@ module Gamefic
|
|
135
110
|
false
|
136
111
|
end
|
137
112
|
|
138
|
-
def
|
113
|
+
def serialize_object obj
|
139
114
|
return nil if obj.nil?
|
140
115
|
return false if obj == false
|
141
116
|
if obj.kind_of?(Hash)
|
@@ -156,7 +131,7 @@ module Gamefic
|
|
156
131
|
hash = {}
|
157
132
|
obj.each_pair { |k, v|
|
158
133
|
if can_serialize?(k) and can_serialize?(v)
|
159
|
-
hash[
|
134
|
+
hash[serialize_object(k)] = serialize_object(v)
|
160
135
|
end
|
161
136
|
}
|
162
137
|
return hash
|
@@ -166,7 +141,7 @@ module Gamefic
|
|
166
141
|
arr = []
|
167
142
|
obj.each_index { |i|
|
168
143
|
if can_serialize?(obj[i])
|
169
|
-
arr[i] =
|
144
|
+
arr[i] = serialize_object(obj[i])
|
170
145
|
else
|
171
146
|
raise "Bad array in snapshot"
|
172
147
|
end
|
@@ -215,7 +190,7 @@ module Gamefic
|
|
215
190
|
s.instance_variables.each { |k|
|
216
191
|
v = s.instance_variable_get(k)
|
217
192
|
if can_serialize?(v)
|
218
|
-
hash[k] =
|
193
|
+
hash[k] = serialize_object(v)
|
219
194
|
end
|
220
195
|
}
|
221
196
|
arr.push hash
|