gamefic 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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