gamefic 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/gamefic.rb +1 -2
- data/lib/gamefic/character.rb +31 -45
- data/lib/gamefic/director/delegate.rb +46 -24
- data/lib/gamefic/director/order.rb +7 -0
- data/lib/gamefic/director/parser.rb +11 -11
- data/lib/gamefic/engine/base.rb +2 -3
- data/lib/gamefic/entity.rb +8 -26
- data/lib/gamefic/plot.rb +38 -242
- data/lib/gamefic/plot/callbacks.rb +101 -0
- data/lib/gamefic/plot/command_mount.rb +70 -40
- data/lib/gamefic/plot/entities.rb +82 -0
- data/lib/gamefic/plot/host.rb +46 -0
- data/lib/gamefic/plot/playbook.rb +192 -0
- data/lib/gamefic/plot/players.rb +15 -0
- data/lib/gamefic/plot/scene_mount.rb +69 -31
- data/lib/gamefic/plot/snapshot.rb +20 -5
- data/lib/gamefic/scene/active.rb +8 -1
- data/lib/gamefic/scene/base.rb +4 -26
- data/lib/gamefic/scene/custom.rb +53 -3
- data/lib/gamefic/scene/multiple_choice.rb +1 -0
- data/lib/gamefic/scene/yes_or_no.rb +1 -1
- data/lib/gamefic/scene_data/multiple_scene.rb +0 -4
- data/lib/gamefic/shell.rb +0 -1
- data/lib/gamefic/source/file.rb +1 -1
- data/lib/gamefic/stage.rb +10 -2
- data/lib/gamefic/subplot.rb +70 -53
- data/lib/gamefic/syntax.rb +9 -2
- data/lib/gamefic/tester.rb +1 -1
- data/lib/gamefic/text.rb +8 -0
- data/lib/gamefic/{ansi.rb → text/ansi.rb} +12 -15
- data/lib/gamefic/text/html.rb +68 -0
- data/lib/gamefic/text/html/conversions.rb +250 -0
- data/lib/gamefic/text/html/entities.rb +9 -0
- data/lib/gamefic/tty.rb +2 -0
- data/lib/gamefic/user/tty.rb +2 -4
- data/lib/gamefic/version.rb +1 -1
- metadata +12 -8
- data/lib/gamefic/direction.rb +0 -46
- data/lib/gamefic/html.rb +0 -68
- data/lib/gamefic/html_to_ansi.rb +0 -185
- data/lib/gamefic/plot/entity_mount.rb +0 -45
- data/lib/gamefic/rule.rb +0 -18
@@ -3,31 +3,6 @@ require 'gamefic/action'
|
|
3
3
|
module Gamefic
|
4
4
|
|
5
5
|
module Plot::CommandMount
|
6
|
-
# Create a Meta Action that responds to a command.
|
7
|
-
# Meta Actions are very similar to standard Actions, except the Plot
|
8
|
-
# understands them to be commands that operate above and/or outside of the
|
9
|
-
# actual game world. Examples of Meta Actions are commands that report the
|
10
|
-
# player's current score, save and restore saved games, or list the game's
|
11
|
-
# credits.
|
12
|
-
#
|
13
|
-
# @example A simple Meta Action
|
14
|
-
# meta :credits do |actor|
|
15
|
-
# actor.tell "This game was written by John Smith."
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# @param command [Symbol] An imperative verb for the command
|
19
|
-
# @param *queries [Array<Query::Base>] Queries to filter the command's tokens
|
20
|
-
# @yieldparam [Character]
|
21
|
-
def meta(command, *queries, &proc)
|
22
|
-
act = self.action(command, *queries, &proc)
|
23
|
-
act.meta = true
|
24
|
-
act
|
25
|
-
end
|
26
|
-
def action(command, *queries, &proc)
|
27
|
-
act = Action.new(command, *queries, &proc)
|
28
|
-
add_action act
|
29
|
-
act
|
30
|
-
end
|
31
6
|
# Create an Action that responds to a command.
|
32
7
|
# An Action uses the command argument to identify the imperative verb that
|
33
8
|
# triggers the action.
|
@@ -51,8 +26,59 @@ module Gamefic
|
|
51
26
|
# @param *queries [Array<Query::Base>] Queries to filter the command's tokens
|
52
27
|
# @yieldparam [Character]
|
53
28
|
def respond(command, *queries, &proc)
|
54
|
-
|
29
|
+
playbook.respond(command, *queries, &proc)
|
55
30
|
end
|
31
|
+
|
32
|
+
# Create a Meta Action that responds to a command.
|
33
|
+
# Meta Actions are very similar to standard Actions, except the Plot
|
34
|
+
# understands them to be commands that operate above and/or outside of the
|
35
|
+
# actual game world. Examples of Meta Actions are commands that report the
|
36
|
+
# player's current score, save and restore saved games, or list the game's
|
37
|
+
# credits.
|
38
|
+
#
|
39
|
+
# @example A simple Meta Action
|
40
|
+
# meta :credits do |actor|
|
41
|
+
# actor.tell "This game was written by John Smith."
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param command [Symbol] An imperative verb for the command
|
45
|
+
# @param *queries [Array<Query::Base>] Queries to filter the command's tokens
|
46
|
+
# @yieldparam [Character]
|
47
|
+
def meta(command, *queries, &proc)
|
48
|
+
playbook.meta command, *queries, &proc
|
49
|
+
end
|
50
|
+
|
51
|
+
# @deprecated
|
52
|
+
def action(command, *queries, &proc)
|
53
|
+
respond command, *queries, &proc
|
54
|
+
end
|
55
|
+
|
56
|
+
# Declare a dismabiguation response for actions.
|
57
|
+
# The disambigurator is executed when an action expects an argument to
|
58
|
+
# reference a specific entity but its query matched more than one. For
|
59
|
+
# example, "red" might refer to either a red key or a red book.
|
60
|
+
#
|
61
|
+
# If a disambiguator is not defined, the playbook will use its default
|
62
|
+
# implementation.
|
63
|
+
#
|
64
|
+
# @example Tell the player the list of ambiguous entities.
|
65
|
+
# disambiguate do |actor, entities|
|
66
|
+
# actor.tell "I don't know which you mean: #{entities.join_or}."
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# @yieldparam [Gamefic::Character]
|
70
|
+
# @yieldparam [Array<Gamefic::Entity>]
|
71
|
+
def disambiguate &block
|
72
|
+
playbook.disambiguate &block
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validate an order before a character can execute its command.
|
76
|
+
#
|
77
|
+
# @yieldparam [Gamefic::Director::Order]
|
78
|
+
def validate &block
|
79
|
+
playbook.validate &block
|
80
|
+
end
|
81
|
+
|
56
82
|
# Create an alternate Syntax for an Action.
|
57
83
|
# The command and its translation can be parameterized.
|
58
84
|
#
|
@@ -68,23 +94,27 @@ module Gamefic
|
|
68
94
|
# @param translation [String] The format of the translated command
|
69
95
|
# @return [Syntax] the Syntax object
|
70
96
|
def interpret command, translation
|
71
|
-
|
97
|
+
playbook.interpret command, translation
|
72
98
|
end
|
73
|
-
|
74
|
-
|
99
|
+
|
100
|
+
# @deprecated
|
101
|
+
def xlate command, translation
|
102
|
+
interpret command, translation
|
75
103
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
104
|
+
|
105
|
+
# Get an Array of available verbs.
|
106
|
+
# If the to_s parameter is true, convert Symbols to Strings.
|
107
|
+
#
|
108
|
+
# @return [Array<Symbol|String>]
|
109
|
+
def verbs to_s: false
|
110
|
+
to_s ? playbook.verbs.map { |v| v.to_s } : playbook.verbs
|
80
111
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
words.uniq
|
112
|
+
|
113
|
+
# Get an Array of all Actions defined in the Plot.
|
114
|
+
#
|
115
|
+
# @return [Array<Action>]
|
116
|
+
def actions
|
117
|
+
playbook.actions
|
88
118
|
end
|
89
119
|
end
|
90
120
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
class Plot
|
4
|
+
module Entities
|
5
|
+
# Make a new Entity with the provided properties.
|
6
|
+
#
|
7
|
+
# @example Create an Entity
|
8
|
+
# chair = make Entity, name: 'red chair'
|
9
|
+
# chair.name #=> 'red chair'
|
10
|
+
#
|
11
|
+
# @param cls [Class] The Class of the Entity to be created.
|
12
|
+
# @param args [Hash] The entity's properties.
|
13
|
+
# @return The Entity instance.
|
14
|
+
def make cls, args = {}, &block
|
15
|
+
ent = cls.new args, &block
|
16
|
+
if ent.kind_of?(Entity) == false
|
17
|
+
raise "Invalid entity class"
|
18
|
+
end
|
19
|
+
p_entities.push ent
|
20
|
+
p_dynamic.push ent if running?
|
21
|
+
ent.playbook = playbook if ent.kind_of?(Character)
|
22
|
+
ent
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy entity
|
26
|
+
if p_dynamic.include?(entity)
|
27
|
+
p_entities.delete entity
|
28
|
+
p_dynamic.delete entity
|
29
|
+
p_players.delete entity
|
30
|
+
end
|
31
|
+
entity.parent = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
# Pick an entity based on its description.
|
35
|
+
# The description provided must match exactly one entity; otherwise
|
36
|
+
# an error is raised.
|
37
|
+
#
|
38
|
+
# @example Select the Entity that matches the description
|
39
|
+
# red_chair = make Entity, :name => 'red chair'
|
40
|
+
# blue_chair make Entity, :name => 'blue chair'
|
41
|
+
# pick "red chair" #=> red_chair
|
42
|
+
# pick "blue chair" #=> blue_chair
|
43
|
+
# pick "chair" #=> IndexError: description is ambiguous
|
44
|
+
#
|
45
|
+
# @param @description [String] The description of the entity
|
46
|
+
# @return [Entity] The entity that matches the description
|
47
|
+
def pick(description)
|
48
|
+
query = Gamefic::Query::Base.new
|
49
|
+
result = query.match(description, entities)
|
50
|
+
if result.objects.length == 0
|
51
|
+
raise IndexError.new("Unable to find entity from '#{description}'")
|
52
|
+
elsif result.objects.length > 1
|
53
|
+
raise IndexError.new("Ambiguous entities found from '#{description}'")
|
54
|
+
end
|
55
|
+
result.objects[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
def entities
|
59
|
+
p_entities.clone
|
60
|
+
end
|
61
|
+
|
62
|
+
def players
|
63
|
+
p_players.clone
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def p_entities
|
69
|
+
@p_entities ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def p_players
|
73
|
+
@p_players ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
def p_dynamic
|
77
|
+
@p_dynamic ||= []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'gamefic/subplot'
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
|
5
|
+
module Plot::Host
|
6
|
+
# Get an array of all the current subplots.
|
7
|
+
#
|
8
|
+
# @return [Array<Subplot>]
|
9
|
+
def subplots
|
10
|
+
p_subplots.clone
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start a new subplot based on the provided class.
|
14
|
+
#
|
15
|
+
# @param [Class] The class of the subplot to be created (Subplot by default)
|
16
|
+
# @return [Subplot]
|
17
|
+
def branch subplot_class = Gamefic::Subplot, introduce: nil
|
18
|
+
subplot = subplot_class.new(self, introduce: introduce)
|
19
|
+
p_subplots.push subplot
|
20
|
+
subplot
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the player's current subplot or nil if none exists.
|
24
|
+
#
|
25
|
+
# @return [Subplot]
|
26
|
+
def subplot_for player
|
27
|
+
subplots.each { |s|
|
28
|
+
return s if s.players.include?(player)
|
29
|
+
}
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determine whether the player is involved in a subplot.
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def subbed? player
|
37
|
+
!subplot_for(player).nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def p_subplots
|
42
|
+
@p_subplots ||= []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
class Plot
|
4
|
+
class Playbook
|
5
|
+
def initialize commands: {}, syntaxes: [], validators: [], disambiguator: nil
|
6
|
+
@commands = commands
|
7
|
+
@syntaxes = syntaxes
|
8
|
+
@validators = validators
|
9
|
+
@disambiguator = disambiguator
|
10
|
+
end
|
11
|
+
|
12
|
+
def syntaxes
|
13
|
+
@syntaxes
|
14
|
+
end
|
15
|
+
|
16
|
+
def actions
|
17
|
+
@commands.values.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def verbs
|
21
|
+
@commands.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def validators
|
25
|
+
@validators
|
26
|
+
end
|
27
|
+
|
28
|
+
def disambiguator
|
29
|
+
@disambiguator ||= Action.new(nil, Query::Base.new) do |actor, entities|
|
30
|
+
definites = []
|
31
|
+
entities.each { |entity|
|
32
|
+
definites.push entity.definitely
|
33
|
+
}
|
34
|
+
actor.tell "I don't know which you mean: #{definites.join_or}."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def disambiguate &block
|
39
|
+
@disambiguator = Action.new(nil, Query::Base.new, &block)
|
40
|
+
@disambiguator.meta = true
|
41
|
+
@disambiguator
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate &block
|
45
|
+
@validators.push block
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get an Array of all Actions associated with the specified verb.
|
49
|
+
#
|
50
|
+
# @param verb [Symbol] The Symbol for the verb (e.g., :go or :look)
|
51
|
+
# @return [Array<Action>] The verb's associated Actions
|
52
|
+
def actions_for verb
|
53
|
+
@commands[verb] || []
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create an Action that responds to a command.
|
57
|
+
# An Action uses the command argument to identify the imperative verb that
|
58
|
+
# triggers the action.
|
59
|
+
# It can also accept queries to tokenize the remainder of the input and
|
60
|
+
# filter for particular entities or properties.
|
61
|
+
# The block argument contains the code to be executed when the input
|
62
|
+
# matches all of the Action's criteria (i.e., verb and queries).
|
63
|
+
#
|
64
|
+
# @example A simple Action.
|
65
|
+
# respond :salute do |actor|
|
66
|
+
# actor.tell "Hello, sir!"
|
67
|
+
# end
|
68
|
+
# # The command "salute" will respond "Hello, sir!"
|
69
|
+
#
|
70
|
+
# @example An Action that accepts a Character
|
71
|
+
# respond :salute, Use.visible(Character) do |actor, character|
|
72
|
+
# actor.tell "#{The character} returns your salute."
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# @param command [Symbol] An imperative verb for the command
|
76
|
+
# @param *queries [Array<Query::Base>] Queries to filter the command's tokens
|
77
|
+
# @yieldparam [Character]
|
78
|
+
def respond(command, *queries, &proc)
|
79
|
+
act = Action.new(command, *queries, &proc)
|
80
|
+
add_action act
|
81
|
+
act
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create a Meta Action that responds to a command.
|
85
|
+
# Meta Actions are very similar to standard Actions, except the Plot
|
86
|
+
# understands them to be commands that operate above and/or outside of the
|
87
|
+
# actual game world. Examples of Meta Actions are commands that report the
|
88
|
+
# player's current score, save and restore saved games, or list the game's
|
89
|
+
# credits.
|
90
|
+
#
|
91
|
+
# @example A simple Meta Action
|
92
|
+
# meta :credits do |actor|
|
93
|
+
# actor.tell "This game was written by John Smith."
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# @param command [Symbol] An imperative verb for the command
|
97
|
+
# @param *queries [Array<Query::Base>] Queries to filter the command's tokens
|
98
|
+
# @yieldparam [Character]
|
99
|
+
def meta(command, *queries, &proc)
|
100
|
+
act = respond(command, *queries, &proc)
|
101
|
+
act.meta = true
|
102
|
+
act
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create an alternate Syntax for an Action.
|
106
|
+
# The command and its translation can be parameterized.
|
107
|
+
#
|
108
|
+
# @example Create a synonym for the Inventory Action.
|
109
|
+
# interpret "catalogue", "inventory"
|
110
|
+
# # The command "catalogue" will be translated to "inventory"
|
111
|
+
#
|
112
|
+
# @example Create a parameterized synonym for the Look Action.
|
113
|
+
# interpret "scrutinize :entity", "look :entity"
|
114
|
+
# # The command "scrutinize chair" will be translated to "look chair"
|
115
|
+
#
|
116
|
+
# @param command [String] The format of the original command
|
117
|
+
# @param translation [String] The format of the translated command
|
118
|
+
# @return [Syntax] the Syntax object
|
119
|
+
def interpret(*args)
|
120
|
+
syn = Syntax.new(*args)
|
121
|
+
add_syntax syn
|
122
|
+
syn
|
123
|
+
end
|
124
|
+
|
125
|
+
# Duplicate the playbook.
|
126
|
+
# This method will duplicate the commands hash and the syntax array so
|
127
|
+
# the new playbook can be modified without affecting the original.
|
128
|
+
#
|
129
|
+
# @return [Playbook]
|
130
|
+
def dup
|
131
|
+
Playbook.new commands: @commands.dup, syntaxes: @syntaxes.dup
|
132
|
+
end
|
133
|
+
|
134
|
+
def freeze
|
135
|
+
@commands.freeze
|
136
|
+
@syntaxes.freeze
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def add_action(action)
|
142
|
+
@commands[action.verb] ||= []
|
143
|
+
@commands[action.verb].unshift action
|
144
|
+
@commands[action.verb].sort! { |a, b|
|
145
|
+
if a.specificity == b.specificity
|
146
|
+
# Newer action takes precedence
|
147
|
+
b.order_key <=> a.order_key
|
148
|
+
else
|
149
|
+
# Higher specificity takes precedence
|
150
|
+
b.specificity <=> a.specificity
|
151
|
+
end
|
152
|
+
}
|
153
|
+
generate_default_syntax action
|
154
|
+
end
|
155
|
+
|
156
|
+
def generate_default_syntax action
|
157
|
+
user_friendly = action.verb.to_s.gsub(/_/, ' ')
|
158
|
+
args = []
|
159
|
+
used_names = []
|
160
|
+
action.queries.each { |c|
|
161
|
+
num = 1
|
162
|
+
new_name = ":var"
|
163
|
+
while used_names.include? new_name
|
164
|
+
num = num + 1
|
165
|
+
new_name = ":var#{num}"
|
166
|
+
end
|
167
|
+
used_names.push new_name
|
168
|
+
user_friendly += " #{new_name}"
|
169
|
+
args.push new_name
|
170
|
+
}
|
171
|
+
add_syntax Syntax.new(user_friendly.strip, "#{action.verb} #{args.join(' ')}")
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_syntax syntax
|
175
|
+
if @commands[syntax.verb] == nil
|
176
|
+
raise "No actions exist for \"#{syntax.verb}\""
|
177
|
+
end
|
178
|
+
@syntaxes.unshift syntax
|
179
|
+
@syntaxes.uniq
|
180
|
+
@syntaxes.sort! { |a, b|
|
181
|
+
if a.token_count == b.token_count
|
182
|
+
# For syntaxes of the same length, length of action takes precedence
|
183
|
+
b.first_word <=> a.first_word
|
184
|
+
else
|
185
|
+
b.token_count <=> a.token_count
|
186
|
+
end
|
187
|
+
}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|