gamefic 1.6.0 → 1.7.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.
@@ -18,7 +18,19 @@ module Gamefic
18
18
  end
19
19
  p_entities.push ent
20
20
  p_dynamic.push ent if running?
21
- ent.playbook = playbook if ent.kind_of?(Character)
21
+ ent
22
+ end
23
+
24
+ # Cast an active entity.
25
+ # This method is similar to make, but it also provides the plot's
26
+ # playbook to the entity so it can perform actions. The entity should
27
+ # either be a kind of Gamefic::Actor or include the Gamefic::Active
28
+ # module.
29
+ #
30
+ # @return [Gamefic::Actor, Gamefic::Active]
31
+ def cast cls, args = {}, &block
32
+ ent = make cls, args, &block
33
+ ent.playbooks.push playbook
22
34
  ent
23
35
  end
24
36
 
@@ -14,30 +14,32 @@ module Gamefic
14
14
  #
15
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, next_cue: nil, busy_cue: nil
18
- subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, busy_cue: busy_cue)
17
+ def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil
18
+ subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue)
19
19
  p_subplots.push subplot
20
20
  subplot
21
21
  end
22
22
 
23
- # Get the player's current subplot or nil if none exists.
23
+ # Get the player's current subplots.
24
24
  #
25
- # @return [Subplot]
26
- def subplot_for player
25
+ # @return [Array<Subplot>]
26
+ def subplots_featuring player
27
+ result = []
27
28
  subplots.each { |s|
28
- return s if s.players.include?(player)
29
+ result.push s if s.players.include?(player)
29
30
  }
30
- nil
31
+ result
31
32
  end
32
33
 
33
34
  # Determine whether the player is involved in a subplot.
34
35
  #
35
36
  # @return [Boolean]
36
37
  def in_subplot? player
37
- !subplot_for(player).nil?
38
+ !subplots_featuring(player).empty?
38
39
  end
39
40
 
40
41
  private
42
+
41
43
  def p_subplots
42
44
  @p_subplots ||= []
43
45
  end
@@ -131,14 +131,7 @@ module Gamefic
131
131
  if result.empty?
132
132
  result.concat dispatch_from_string(actor, command.join(' '))
133
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}
134
+ result
142
135
  end
143
136
 
144
137
  def dispatch_from_string actor, text
@@ -147,11 +140,12 @@ module Gamefic
147
140
  commands.each { |c|
148
141
  available = actions_for(c.verb)
149
142
  available.each { |a|
143
+ next if a.hidden?
150
144
  o = a.attempt(actor, c.arguments)
151
145
  result.unshift o unless o.nil?
152
146
  }
153
147
  }
154
- result
148
+ sort_and_reduce_actions result
155
149
  end
156
150
 
157
151
  def dispatch_from_params actor, verb, params
@@ -160,7 +154,7 @@ module Gamefic
160
154
  available.each { |a|
161
155
  result.unshift a.new(actor, params) if a.valid?(actor, params)
162
156
  }
163
- result
157
+ sort_and_reduce_actions result
164
158
  end
165
159
 
166
160
  # Duplicate the playbook.
@@ -223,10 +217,20 @@ module Gamefic
223
217
  }
224
218
  end
225
219
 
220
+ def sort_and_reduce_actions arr
221
+ arr.sort { |a,b|
222
+ if a.rank == b.rank
223
+ b.order_key <=> a.order_key
224
+ else
225
+ b.rank <=> a.rank
226
+ end
227
+ }.uniq{|a| a.class}
228
+ end
229
+
226
230
  def raise_order_key
227
- @order_key ||= 0
228
- tmp = @order_key
229
- @order_key += 1
231
+ @@order_key ||= 0
232
+ tmp = @@order_key
233
+ @@order_key += 1
230
234
  tmp
231
235
  end
232
236
 
@@ -2,7 +2,7 @@ module Gamefic
2
2
 
3
3
  module Plot::Scenes
4
4
  def default_scene
5
- @default_scene ||= Scene::Active
5
+ @default_scene ||= Scene::Activity
6
6
  end
7
7
 
8
8
  def default_conclusion
@@ -19,7 +19,7 @@ module Gamefic
19
19
  # end
20
20
  #
21
21
  # @yieldparam [Gamefic::Character]
22
- def introduction (&proc)
22
+ def introduction(&proc)
23
23
  @introduction = proc
24
24
  end
25
25
 
@@ -28,7 +28,7 @@ module Gamefic
28
28
  #
29
29
  # @param [Gamefic::Character]
30
30
  def introduce(player)
31
- player.playbook = playbook
31
+ player.playbooks.push playbook unless player.playbooks.include?(playbook)
32
32
  player.cue default_scene
33
33
  p_players.push player
34
34
  @introduction.call(player) unless @introduction.nil?
@@ -40,10 +40,12 @@ module Gamefic
40
40
  # @yieldparam [Gamefic::Character]
41
41
  # @yieldparam [Gamefic::Scene::Data::MultipleChoice]
42
42
  def multiple_choice *choices, &block
43
- Scene::MultipleChoice.subclass do |actor, scene|
43
+ s = Scene::MultipleChoice.subclass do |actor, scene|
44
44
  scene.options.concat choices
45
45
  scene.on_finish &block
46
46
  end
47
+ scene_classes.push s
48
+ s
47
49
  end
48
50
 
49
51
  # Create a yes-or-no scene.
@@ -52,17 +54,30 @@ module Gamefic
52
54
  # @yieldparam [Gamefic::Character]
53
55
  # @yieldparam [Gamefic::Scene::YesOrNo]
54
56
  def yes_or_no prompt = nil, &block
55
- Scene::YesOrNo.subclass do |actor, scene|
57
+ s = Scene::YesOrNo.subclass do |actor, scene|
56
58
  scene.prompt = prompt
57
59
  scene.on_finish &block
58
60
  end
61
+ scene_classes.push s
62
+ s
59
63
  end
60
-
64
+
65
+ # Create a scene with custom processing on user input.
66
+ #
67
+ # @example Echo the user's response
68
+ # @scene = question 'What do you say?' do |actor, scene|
69
+ # actor.tell "You said #{scene.input}"
70
+ # end
71
+ #
72
+ # @yieldparam [Gamefic::Character]
73
+ # @yieldparam [Gamefic::Scene::YesOrNo]
61
74
  def question prompt = 'What is your answer?', &block
62
- Scene::Custom.subclass do |actor, scene|
75
+ s = Scene::Custom.subclass do |actor, scene|
63
76
  scene.prompt = prompt
64
77
  scene.on_finish &block
65
78
  end
79
+ scene_classes.push s
80
+ s
66
81
  end
67
82
 
68
83
  # Create a scene that pauses the game.
@@ -73,10 +88,12 @@ module Gamefic
73
88
  # @yieldparam [Gamefic::Character]
74
89
  # @yieldparam [Gamefic::Scene::Pause]
75
90
  def pause prompt = nil, &block
76
- Scene::Pause.subclass do |actor, scene|
91
+ s = Scene::Pause.subclass do |actor, scene|
77
92
  scene.prompt = prompt unless prompt.nil?
78
93
  block.call(actor, scene) unless block.nil?
79
94
  end
95
+ scene_classes.push s
96
+ s
80
97
  end
81
98
 
82
99
  # Create a conclusion.
@@ -86,7 +103,9 @@ module Gamefic
86
103
  # @yieldparam [Gamefic::Character]
87
104
  # @yieldparam [Gamefic::Scene::Conclusion]
88
105
  def conclusion &block
89
- Scene::Conclusion.subclass &block
106
+ s = Scene::Conclusion.subclass &block
107
+ scene_classes.push s
108
+ s
90
109
  end
91
110
 
92
111
  # Create a custom scene.
@@ -111,7 +130,9 @@ module Gamefic
111
130
  # @yieldparam [Gamefic::Character]
112
131
  # @yieldparam [Scene::Custom] The instantiated scene.
113
132
  def custom cls = Scene::Custom, &block
114
- cls.subclass &block
133
+ s = cls.subclass &block
134
+ scene_classes.push s
135
+ s
115
136
  end
116
137
 
117
138
  # Choose a new scene based on a list of options.
@@ -133,16 +154,33 @@ module Gamefic
133
154
  # actor.cue select_one_or_two # The actor will be prompted to select "one" or "two" and get sent to the corresponding scene
134
155
  # end
135
156
  #
136
- # @param map [Hash] A Hash of options and associated scene keys.
157
+ # @example Customize options
158
+ # scene_one = pause # do...
159
+ # scene_two = pause # do...
160
+ #
161
+ # # Some event in the game sets actor[:can_go_to_scene_two] to true
162
+ #
163
+ # select_one_or_two = multiple_scene do |actor, scene|
164
+ # scene.map "Go to scene one", scene_one
165
+ # scene.map "Go to scene two", scene_two if actor[:can_go_to_scene_two]
166
+ # end
167
+ #
168
+ # @param map [Hash] A Hash of options and associated scenes.
137
169
  # @yieldparam [Gamefic::Character]
138
170
  # @yieldparam [Gamefic::Scene::MultipleScene]
139
171
  def multiple_scene map = {}, &block
140
- Scene::MultipleScene.subclass do |actor, scene|
172
+ s = Scene::MultipleScene.subclass do |actor, scene|
141
173
  map.each_pair { |k, v|
142
174
  scene.map k, v
143
175
  }
144
176
  block.call actor, scene unless block.nil?
145
177
  end
178
+ scene_classes.push s
179
+ s
180
+ end
181
+
182
+ def scene_classes
183
+ @scene_classes ||= []
146
184
  end
147
185
  end
148
186
 
@@ -2,226 +2,31 @@ require 'json'
2
2
 
3
3
  module Gamefic
4
4
  module Plot::Snapshot
5
-
6
- # Take a snapshot of the plot's current state.
7
- # The snapshot is a hash with two keys: entities and subplots.
8
- #
9
5
  # @return [Hash]
10
6
  def save
11
- store = get_entity_hash
12
- if @initial_state.nil?
13
- @initial_state = store
14
- store = []
15
- @initial_state.length.times do
16
- store.push {}
17
- end
18
- else
19
- store = reduce(store)
20
- end
21
- return {
22
- entities: store,
23
- subplots: save_subplots,
24
- metadata: metadata
25
- }
26
- end
27
-
28
- # Restore the plot to the state of the provided snapshot.
29
- #
30
- # @param snapshot [Hash]
31
- def restore snapshot
32
- restore_initial_state
33
- internal_restore snapshot[:entities]
34
- restore_subplots snapshot[:subplots]
35
- end
36
-
37
- private
38
-
39
- # Restore the plot to the state of its first snapshot.
40
- #
41
- def restore_initial_state
42
- p_entities[@initial_state.length..-1].each { |e|
43
- e.parent = nil
44
- }
45
- p_entities.slice! @initial_state.length..-1
46
- internal_restore @initial_state
47
- end
48
-
49
- def get_entity_hash
50
- store = []
51
- entities.each { |e|
52
- hash = {}
53
- e.instance_variables.each { |k|
54
- next if k == :@children
55
- v = e.instance_variable_get(k)
56
- hash[k] = v
57
- }
58
- hash[:class] = e.class.to_s
59
- store.push serialize_object(hash)
60
- }
61
- store
62
- end
63
-
64
- def internal_restore snapshot
65
- index = 0
66
- snapshot.each { |hash|
67
- if entities[index].nil?
68
- cls = Kernel.const_get(hash[:class])
69
- p_entities[index] = make cls
70
- end
71
- internal_restore_hash hash, index
72
- index += 1
73
- }
74
- nil
75
- end
76
-
77
- def internal_restore_hash hash, index
78
- hash.each { |k, v|
79
- if k == :scene
80
- entities[index].cue v.to_sym
81
- elsif (k != :class)
82
- entities[index].instance_variable_set(k, unserialize(v))
83
- end
84
- }
85
- nil
86
- end
87
-
88
- def reduce entities
89
- reduced = []
90
- index = 0
91
- entities.each { |e|
92
- r = {}
93
- e.each_pair { |k, v|
94
- if index >= @initial_state.length or @initial_state[index][k] != v
95
- r[k] = v
96
- end
97
- }
98
- reduced.push r
99
- index += 1
100
- }
101
- reduced
102
- end
103
-
104
- def can_serialize? obj
105
- return true if (obj == true or obj == false or obj.nil?)
106
- allowed = [String, Fixnum, Float, Numeric, Entity, Direction, Hash, Array, Symbol]
107
- allowed.each { |a|
108
- return true if obj.kind_of?(a)
109
- }
110
- false
7
+ initial_state
8
+ internal_save
111
9
  end
112
10
 
113
- def serialize_object obj
114
- return nil if obj.nil?
115
- return false if obj == false
116
- if obj.kind_of?(Hash)
117
- return serialize_hash obj
118
- elsif obj.kind_of?(Array)
119
- return serialize_array obj
120
- else
121
- if obj.kind_of?(Entity)
122
- return "#<EIN_#{p_entities.index(obj)}>"
123
- elsif obj.kind_of?(Direction)
124
- return "#<DIR_#{obj.name}>"
125
- end
126
- end
127
- return obj
128
- end
129
-
130
- def serialize_hash obj
131
- hash = {}
132
- obj.each_pair { |k, v|
133
- if can_serialize?(k) and can_serialize?(v)
134
- hash[serialize_object(k)] = serialize_object(v)
135
- end
136
- }
137
- return hash
138
- end
139
-
140
- def serialize_array obj
141
- arr = []
142
- obj.each_index { |i|
143
- if can_serialize?(obj[i])
144
- arr[i] = serialize_object(obj[i])
145
- else
146
- raise "Bad array in snapshot"
147
- end
148
- }
149
- return arr
11
+ def restore snapshot
12
+ # HACK Force conclusion of current subplots
13
+ p_subplots.each { |s| s.conclude }
14
+ p_subplots.clear
15
+ Gamefic::Plot::Darkroom.new(self).restore(snapshot)
16
+ entities.each { |e| e.flush }
150
17
  end
151
18
 
152
- def unserialize obj
153
- if obj.kind_of?(Hash)
154
- unserialize_hash obj
155
- elsif obj.kind_of?(Array)
156
- unserialize_array obj
157
- elsif obj.to_s.match(/^#<EIN_[0-9]+>$/)
158
- i = obj[6..-2].to_i
159
- p_entities[i]
160
- elsif obj.to_s.match(/^#<DIR_[a-z]+>$/)
161
- Direction.find(obj[6..-2])
162
- else
163
- obj
19
+ def initial_state
20
+ if @initial_state.nil?
21
+ @initial_state = internal_save
164
22
  end
23
+ @initial_state
165
24
  end
166
25
 
167
- def unserialize_hash obj
168
- hash = {}
169
- obj.each_pair { |k, v|
170
- hash[unserialize(k)] = unserialize(v)
171
- }
172
- hash
173
- end
174
-
175
- def unserialize_array obj
176
- arr = []
177
- obj.each_index { |i|
178
- arr[i] = unserialize(obj[i])
179
- }
180
- arr
181
- end
26
+ private
182
27
 
183
- def save_subplots
184
- # TODO: Subplot snapshots are temporarily disabled.
185
- return []
186
- arr = []
187
- subplots.each { |s|
188
- hash = {}
189
- hash[:class] = s.class.to_s
190
- s.instance_variables.each { |k|
191
- v = s.instance_variable_get(k)
192
- if can_serialize?(v)
193
- hash[k] = serialize_object(v)
194
- end
195
- }
196
- arr.push hash
197
- }
198
- arr
199
- end
200
-
201
- def restore_subplots arr
202
- # TODO: Subplot snapshots are temporarily disabled.
203
- return
204
- players.each { |p|
205
- p.send(:p_subplots).clear
206
- }
207
- p_subplots.clear
208
- arr.each { |hash|
209
- cls = Kernel.const_get(hash[:class])
210
- subplot = cls.new self
211
- hash.each { |k, v|
212
- if k != :class
213
- subplot.instance_variable_set(k, unserialize(v))
214
- end
215
- }
216
- subplot.players.each { |p|
217
- p.send(:p_subplots).push subplot
218
- }
219
- subplot.entities.each { |e|
220
- e.extend Subplot::Element
221
- e.instance_variable_set(:@subplot, subplot)
222
- }
223
- p_subplots.push subplot
224
- }
28
+ def internal_save
29
+ Gamefic::Plot::Darkroom.new(self).save
225
30
  end
226
31
  end
227
32
  end