gamefic 1.4.1 → 1.5.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -2
  3. data/lib/gamefic/character.rb +31 -45
  4. data/lib/gamefic/director/delegate.rb +46 -24
  5. data/lib/gamefic/director/order.rb +7 -0
  6. data/lib/gamefic/director/parser.rb +11 -11
  7. data/lib/gamefic/engine/base.rb +2 -3
  8. data/lib/gamefic/entity.rb +8 -26
  9. data/lib/gamefic/plot.rb +38 -242
  10. data/lib/gamefic/plot/callbacks.rb +101 -0
  11. data/lib/gamefic/plot/command_mount.rb +70 -40
  12. data/lib/gamefic/plot/entities.rb +82 -0
  13. data/lib/gamefic/plot/host.rb +46 -0
  14. data/lib/gamefic/plot/playbook.rb +192 -0
  15. data/lib/gamefic/plot/players.rb +15 -0
  16. data/lib/gamefic/plot/scene_mount.rb +69 -31
  17. data/lib/gamefic/plot/snapshot.rb +20 -5
  18. data/lib/gamefic/scene/active.rb +8 -1
  19. data/lib/gamefic/scene/base.rb +4 -26
  20. data/lib/gamefic/scene/custom.rb +53 -3
  21. data/lib/gamefic/scene/multiple_choice.rb +1 -0
  22. data/lib/gamefic/scene/yes_or_no.rb +1 -1
  23. data/lib/gamefic/scene_data/multiple_scene.rb +0 -4
  24. data/lib/gamefic/shell.rb +0 -1
  25. data/lib/gamefic/source/file.rb +1 -1
  26. data/lib/gamefic/stage.rb +10 -2
  27. data/lib/gamefic/subplot.rb +70 -53
  28. data/lib/gamefic/syntax.rb +9 -2
  29. data/lib/gamefic/tester.rb +1 -1
  30. data/lib/gamefic/text.rb +8 -0
  31. data/lib/gamefic/{ansi.rb → text/ansi.rb} +12 -15
  32. data/lib/gamefic/text/html.rb +68 -0
  33. data/lib/gamefic/text/html/conversions.rb +250 -0
  34. data/lib/gamefic/text/html/entities.rb +9 -0
  35. data/lib/gamefic/tty.rb +2 -0
  36. data/lib/gamefic/user/tty.rb +2 -4
  37. data/lib/gamefic/version.rb +1 -1
  38. metadata +12 -8
  39. data/lib/gamefic/direction.rb +0 -46
  40. data/lib/gamefic/html.rb +0 -68
  41. data/lib/gamefic/html_to_ansi.rb +0 -185
  42. data/lib/gamefic/plot/entity_mount.rb +0 -45
  43. 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
- self.action(command, *queries, &proc)
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
- xlate command, translation
97
+ playbook.interpret command, translation
72
98
  end
73
- def syntax(*args)
74
- xlate(*args)
99
+
100
+ # @deprecated
101
+ def xlate command, translation
102
+ interpret command, translation
75
103
  end
76
- def xlate(*args)
77
- syn = Syntax.new(*args)
78
- add_syntax syn
79
- syn
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
- def commandwords
82
- words = Array.new
83
- syntaxes.each { |s|
84
- word = s.first_word
85
- words.push(word) if !word.nil?
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