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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -3
  3. data/lib/gamefic/action.rb +140 -79
  4. data/lib/gamefic/character.rb +120 -53
  5. data/lib/gamefic/character/state.rb +12 -0
  6. data/lib/gamefic/core_ext/array.rb +53 -11
  7. data/lib/gamefic/core_ext/string.rb +1 -0
  8. data/lib/gamefic/describable.rb +37 -11
  9. data/lib/gamefic/engine/base.rb +17 -4
  10. data/lib/gamefic/engine/tty.rb +4 -0
  11. data/lib/gamefic/entity.rb +4 -15
  12. data/lib/gamefic/matchable.rb +50 -0
  13. data/lib/gamefic/messaging.rb +45 -0
  14. data/lib/gamefic/node.rb +16 -0
  15. data/lib/gamefic/plot.rb +27 -33
  16. data/lib/gamefic/plot/{article_mount.rb → articles.rb} +22 -22
  17. data/lib/gamefic/plot/callbacks.rb +30 -4
  18. data/lib/gamefic/plot/{command_mount.rb → commands.rb} +121 -121
  19. data/lib/gamefic/plot/entities.rb +3 -3
  20. data/lib/gamefic/plot/host.rb +3 -3
  21. data/lib/gamefic/plot/playbook.rb +74 -30
  22. data/lib/gamefic/plot/scenes.rb +149 -0
  23. data/lib/gamefic/plot/snapshot.rb +14 -39
  24. data/lib/gamefic/plot/theater.rb +73 -0
  25. data/lib/gamefic/query.rb +6 -19
  26. data/lib/gamefic/query/base.rb +127 -246
  27. data/lib/gamefic/query/children.rb +6 -7
  28. data/lib/gamefic/query/descendants.rb +15 -0
  29. data/lib/gamefic/query/family.rb +19 -7
  30. data/lib/gamefic/query/itself.rb +13 -0
  31. data/lib/gamefic/query/matches.rb +67 -11
  32. data/lib/gamefic/query/parent.rb +6 -7
  33. data/lib/gamefic/query/siblings.rb +10 -7
  34. data/lib/gamefic/query/text.rb +39 -35
  35. data/lib/gamefic/scene.rb +1 -1
  36. data/lib/gamefic/scene/active.rb +12 -6
  37. data/lib/gamefic/scene/base.rb +56 -5
  38. data/lib/gamefic/scene/conclusion.rb +3 -0
  39. data/lib/gamefic/scene/custom.rb +0 -83
  40. data/lib/gamefic/scene/multiple_choice.rb +54 -32
  41. data/lib/gamefic/scene/multiple_scene.rb +11 -6
  42. data/lib/gamefic/scene/pause.rb +3 -4
  43. data/lib/gamefic/scene/yes_or_no.rb +23 -9
  44. data/lib/gamefic/script/base.rb +4 -0
  45. data/lib/gamefic/subplot.rb +22 -19
  46. data/lib/gamefic/syntax.rb +7 -15
  47. data/lib/gamefic/user/base.rb +7 -13
  48. data/lib/gamefic/user/buffer.rb +7 -0
  49. data/lib/gamefic/user/tty.rb +13 -12
  50. data/lib/gamefic/version.rb +1 -1
  51. metadata +11 -37
  52. data/lib/gamefic/director.rb +0 -23
  53. data/lib/gamefic/director/delegate.rb +0 -126
  54. data/lib/gamefic/director/order.rb +0 -17
  55. data/lib/gamefic/director/parser.rb +0 -137
  56. data/lib/gamefic/keywords.rb +0 -67
  57. data/lib/gamefic/plot/query_mount.rb +0 -9
  58. data/lib/gamefic/plot/scene_mount.rb +0 -182
  59. data/lib/gamefic/query/ambiguous_children.rb +0 -5
  60. data/lib/gamefic/query/expression.rb +0 -47
  61. data/lib/gamefic/query/many_children.rb +0 -7
  62. data/lib/gamefic/query/plural_children.rb +0 -14
  63. data/lib/gamefic/query/self.rb +0 -10
  64. data/lib/gamefic/scene_data.rb +0 -10
  65. data/lib/gamefic/scene_data/base.rb +0 -12
  66. data/lib/gamefic/scene_data/multiple_choice.rb +0 -19
  67. data/lib/gamefic/scene_data/multiple_scene.rb +0 -21
  68. data/lib/gamefic/scene_data/yes_or_no.rb +0 -18
  69. data/lib/gamefic/serialized.rb +0 -24
  70. 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 @description [String] The description of the entity
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)
@@ -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
- @disambiguator = Action.new(nil, Query::Base.new, &block)
40
- @disambiguator.meta = true
41
- @disambiguator
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 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)
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 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
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 command [String] The format of the original command
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(*args)
120
- syn = Syntax.new(*args)
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].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
- }
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.serialized_attributes.each {|m|
54
- con = m.to_s
55
- if con.end_with?("?")
56
- con = con[0..-2]
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[:session] = {}
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 != :session and k != :class)
102
- entities[index].send("#{k}=", unserialize(v))
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 serialize_obj obj
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[serialize_obj(k)] = serialize_obj(v)
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] = serialize_obj(obj[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] = serialize_obj(v)
193
+ hash[k] = serialize_object(v)
219
194
  end
220
195
  }
221
196
  arr.push hash