gamefic 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04c82a67567327c04056889b9f2b18e8756c917449cad5c5b0fbee780a5086fe
4
- data.tar.gz: 552ca9a8bf3a8d9816158a5472399f0425eb4e5afb918cd41a4f6d72b3501f6b
3
+ metadata.gz: 6ac3f26cc5dcb62f6b4af8f0a365a316a4324605efecac8fd852f9980f00e336
4
+ data.tar.gz: 11348faf9a1c471c11ca4da93f419167db25a92ec7617fd73fb592fdf0ec7d7f
5
5
  SHA512:
6
- metadata.gz: 269912641fca497c003d815918fc7da4797500bdf62367fe21a1630ddcb9b7ad3e7157f8440d0de4e9ffb6107d268b70756e5dd7d0caeda9097f9ca602f2e0f2
7
- data.tar.gz: 4b4a1179eff5209df4204976107b5da6d40368eda34f787fea421891f1d4047f70a78be58f3e07b424ca453220314e0e241142262ae524536125405070af0295
6
+ metadata.gz: 1bd78b541afec9fd2129d785442ddc811dacbc42c41f9c683329776f90ada2e105578324ef73fa7dedd7fc26ed828917f1a796765f0f1b74ade334b54c94d5ab
7
+ data.tar.gz: d3a982efcb6bdd0c306ebdab70ee722fcd1040d6cf51501b738b8bca65e955689dcfe2d77aa5e8684e1f0e18d27b71579410f51a6189d23014da47156bbbe3cf
@@ -0,0 +1,2 @@
1
+ # 2.0.2 - April 25, 2020
2
+ - Improved snapshot serialization
@@ -5,7 +5,7 @@ require 'gamefic/core_ext/array'
5
5
  require 'gamefic/core_ext/string'
6
6
 
7
7
  require 'gamefic/describable'
8
- require 'gamefic/index'
8
+ # require 'gamefic/index'
9
9
  require 'gamefic/serialize'
10
10
  require 'gamefic/element'
11
11
  require 'gamefic/entity'
@@ -17,5 +17,6 @@ require "gamefic/action"
17
17
  require "gamefic/syntax"
18
18
  require 'gamefic/world'
19
19
  require 'gamefic/scriptable'
20
- require "gamefic/plot"
20
+ require 'gamefic/plot'
21
+ require 'gamefic/plot/index'
21
22
  require 'gamefic/subplot'
@@ -7,13 +7,17 @@ module Gamefic
7
7
  #
8
8
  class Element
9
9
  include Gamefic::Describable
10
- include Gamefic::Index
10
+ # include Gamefic::Index
11
+ include Gamefic::Serialize
11
12
 
12
13
  # @todo It would be nice if this initialization wasn't necessary.
13
14
  def initialize(args = {})
14
- super self.class.default_attributes.merge(args)
15
- post_initialize
16
- yield self if block_given?
15
+ # super self.class.default_attributes.merge(args)
16
+ self.class.default_attributes.merge(args).each_pair do |k, v|
17
+ public_send "#{k}=", v
18
+ end
19
+ post_initialize
20
+ yield self if block_given?
17
21
  end
18
22
 
19
23
  def post_initialize
@@ -16,20 +16,23 @@ module Gamefic
16
16
  # @return [Hash]
17
17
  attr_reader :metadata
18
18
 
19
+ attr_reader :static
20
+
19
21
  include World
20
22
  include Scriptable
21
23
  # @!parse extend Scriptable::ClassMethods
22
24
  include Snapshot
23
25
  include Host
26
+ include Serialize
27
+
28
+ exclude_from_serial [:@static]
24
29
 
25
30
  # @param structure [Gamefic::Structure]
26
31
  # @param metadata [Hash]
27
32
  def initialize metadata: {}
28
- Gamefic::Index.clear
29
33
  @metadata = metadata
30
34
  run_scripts
31
- mark_static_entities
32
- Gamefic::Index.stick
35
+ @static = [self] + scene_classes + entities
33
36
  end
34
37
 
35
38
  def player_class cls = nil
@@ -13,14 +13,21 @@ module Gamefic
13
13
  # Create a snapshot of the plot.
14
14
  #
15
15
  # @return [Hash]
16
- def save reduce: false
17
- result = {
18
- 'elements' => Gamefic::Index.serials,
19
- 'entities' => plot.entities.map(&:to_serial),
20
- 'players' => plot.players.map(&:to_serial),
21
- 'theater_instance_variables' => plot.theater.serialize_instance_variables,
22
- 'subplots' => plot.subplots.reject(&:concluded?).map { |s| serialize_subplot(s) },
23
- 'metadata' => plot.metadata
16
+ def save
17
+ index = plot.static + plot.players
18
+ plot.to_serial(index)
19
+ {
20
+ 'program' => {}, # @todo Metadata for version control, etc.
21
+ 'index' => index.map do |i|
22
+ if i.is_a?(Gamefic::Serialize)
23
+ {
24
+ 'class' => i.class.to_s,
25
+ 'ivars' => i.serialize_instance_variables(index)
26
+ }
27
+ else
28
+ i.to_serial(index)
29
+ end
30
+ end
24
31
  }
25
32
  end
26
33
 
@@ -28,64 +35,45 @@ module Gamefic
28
35
  #
29
36
  # @param snapshot [Hash]
30
37
  def restore snapshot
31
- Gamefic::Index.elements.map(&:destroy)
32
- Gamefic::Index.unserialize snapshot['elements']
33
- plot.entities.clear
34
- snapshot['entities'].each do |ser|
35
- plot.entities.push Index.from_serial(ser)
36
- end
38
+ # @todo Use `program` for verification
37
39
 
38
- snapshot['theater_instance_variables'].each_pair do |k, s|
39
- v = Gamefic::Index.from_serial(s)
40
- next if v == "#<UNKNOWN>"
41
- plot.theater.instance_variable_set(k, v)
42
- end
40
+ plot.subplots.each(&:conclude)
41
+ plot.subplots.clear
43
42
 
44
- snapshot['subplots'].each { |s| unserialize_subplot(s) }
45
- end
46
-
47
- private
48
-
49
- def namespace_to_constant string
50
- space = Object
51
- string.split('::').each do |part|
52
- space = space.const_get(part)
43
+ index = plot.static + plot.players
44
+ snapshot['index'].each_with_index do |obj, idx|
45
+ next if index[idx]
46
+ elematch = obj['class'].match(/^#<ELE_([\d]+)>$/)
47
+ if elematch
48
+ klass = index[elematch[1].to_i]
49
+ else
50
+ klass = Gamefic::Serialize.string_to_constant(obj['class'])
51
+ end
52
+ index.push klass.allocate
53
53
  end
54
- space
55
- end
56
54
 
57
- def serialize_subplot s
58
- {
59
- 'class' => s.class.to_s,
60
- 'entities' => s.entities.map(&:to_serial),
61
- 'instance_variables' => s.serialize_instance_variables,
62
- 'theater_instance_variables' => s.theater.serialize_instance_variables
63
- }
64
- end
65
-
66
- def unserialize_subplot s
67
- cls = namespace_to_constant(s['class'])
68
- sp = cls.allocate
69
- sp.instance_variable_set(:@plot, plot)
70
- s['entities'].each do |e|
71
- sp.entities.push Gamefic::Index.from_serial(e)
72
- end
73
- s['instance_variables'].each_pair do |k, v|
74
- next if v == "#<UNKNOWN>"
75
- sp.instance_variable_set(k, Gamefic::Index.from_serial(v))
76
- end
77
- s['theater_instance_variables'].each_pair do |k, v|
78
- next if v == "#<UNKNOWN>"
79
- sp.theater.instance_variable_set(k, Gamefic::Index.from_serial(v))
80
- end
81
- plot.subplots.push sp
82
- sp.send(:run_scripts)
83
- # @todo Assuming one player
84
- if plot.players.first
85
- sp.players.push plot.players.first
86
- plot.players.first.playbooks.push sp.playbook unless plot.players.first.playbooks.include?(sp.playbook)
55
+ snapshot['index'].each_with_index do |obj, idx|
56
+ if index[idx].class.to_s != obj['class']
57
+ STDERR.puts "MISMATCH: #{index[idx].class} is not #{obj['class']}"
58
+ STDERR.puts obj.inspect
59
+ end
60
+ obj['ivars'].each_pair do |k, v|
61
+ next if k == '@subplots'
62
+ uns = v.from_serial(index)
63
+ next if uns == "#<UNKNOWN>"
64
+ index[idx].instance_variable_set(k, uns)
65
+ end
66
+ if index[idx].is_a?(Gamefic::Subplot)
67
+ index[idx].extend Gamefic::Scriptable
68
+ index[idx].instance_variable_set(:@theater, nil)
69
+ index[idx].send(:run_scripts)
70
+ index[idx].players.each do |pl|
71
+ pl.playbooks.push index[idx].playbook unless pl.playbooks.include?(index[idx].playbook)
72
+ end
73
+ index[idx].instance_variable_set(:@static, [index[idx]] + index[idx].scene_classes + index[idx].entities)
74
+ plot.subplots.push index[idx]
75
+ end
87
76
  end
88
- sp
89
77
  end
90
78
  end
91
79
  end
@@ -0,0 +1,92 @@
1
+ module Gamefic
2
+ class Plot
3
+ # A fixed index of plot elements. Plots and subplots use an index to track
4
+ # objects created in scripts.
5
+ #
6
+ class Index
7
+ # @param elements [Array]
8
+ def initialize elements
9
+ @elements = elements.uniq.freeze
10
+ end
11
+
12
+ # @return [Boolean]
13
+ def include? element
14
+ @elements.include?(element)
15
+ end
16
+
17
+ def all
18
+ @elements
19
+ end
20
+
21
+ def concat elements
22
+ @elements = (@elements + elements).uniq.freeze
23
+ end
24
+
25
+ def remove elements
26
+ @elements = (@elements - elements).uniq.freeze
27
+ end
28
+
29
+ # @return [Object]
30
+ def element index
31
+ @elements[index]
32
+ end
33
+
34
+ def id_for(element)
35
+ include?(element) ? "#<ELE_#{@elements.index(element)}>" : nil
36
+ end
37
+
38
+ # def self.from_serial serial, static
39
+ # if serial.is_a?(Hash) && (serial['class'] || serial['element'])
40
+ # if serial['class']
41
+ # elematch = serial['class'].match(/^#<ELE_([\d]+)>$/)
42
+ # if elematch
43
+ # klass = static.element(elematch[1].to_i)
44
+ # else
45
+ # klass = eval(serial['class'])
46
+ # end
47
+ # object = klass.allocate
48
+ # elsif serial['element']
49
+ # object = static.element(serial['element'])
50
+ # end
51
+ # serial.each_pair do |k, v|
52
+ # next unless k.to_s.start_with?('@')
53
+ # object.instance_variable_set(k, from_serial(v, static))
54
+ # end
55
+ # object
56
+ # elsif serial.is_a?(Numeric)
57
+ # serial
58
+ # elsif serial.is_a?(String)
59
+ # match = serial.match(/#<ELE_([0-9]+)>/)
60
+ # return static.element(match[1].to_i) if match
61
+ # match = serial.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
62
+ # return match[1].to_sym if match
63
+ # serial
64
+ # elsif serial.is_a?(Array)
65
+ # result = serial.map { |e| from_serial(e, static) }
66
+ # result = "#<UNKNOWN>" if result.any? { |e| e == "#<UNKNOWN>" }
67
+ # result
68
+ # elsif serial.is_a?(Hash)
69
+ # result = {}
70
+ # unknown = false
71
+ # serial.each_pair do |k, v|
72
+ # k2 = from_serial(k, static)
73
+ # v2 = from_serial(v, static)
74
+ # if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
75
+ # unknown = true
76
+ # break
77
+ # end
78
+ # result[k2] = v2
79
+ # end
80
+ # result = "#<UNKNOWN>" if unknown
81
+ # result
82
+ # elsif serial && serial != true
83
+ # STDERR.puts "Unable to unserialize #{serial.class}"
84
+ # nil
85
+ # else
86
+ # # true, false, or nil
87
+ # serial
88
+ # end
89
+ # end
90
+ end
91
+ end
92
+ end
@@ -2,18 +2,26 @@ require 'json'
2
2
 
3
3
  module Gamefic
4
4
  module Plot::Snapshot
5
+ # Save the current game state as a data hash.
6
+ # See Gamefic::Plot::Darkroom for more information about
7
+ # the data format.
8
+ #
5
9
  # @return [Hash]
6
10
  def save
7
11
  Gamefic::Plot::Darkroom.new(self).save
8
12
  end
9
13
 
14
+ # Restore the game state from a snapshot.
15
+ #
16
+ # If `snapshot` is a string, parse it as a JSON object.
17
+ #
18
+ # @note The string conversion is performed as a convenience for web apps.
19
+ #
20
+ # @param snapshot [Hash, String]
21
+ # @return [void]
10
22
  def restore snapshot
11
- snapshot = JSON.parse(snapshot, symbolize_names: false) if snapshot.is_a?(String)
12
- # HACK: Force conclusion of current subplots
13
- subplots.each { |s| s.conclude }
14
- subplots.clear
23
+ snapshot = JSON.parse(snapshot) if snapshot.is_a?(String)
15
24
  Gamefic::Plot::Darkroom.new(self).restore(snapshot)
16
- entities.each { |e| e.flush }
17
25
  end
18
26
  end
19
27
  end
@@ -3,6 +3,9 @@ module Gamefic
3
3
  # should inherit from it.
4
4
  #
5
5
  class Scene::Base
6
+ include Gamefic::Serialize
7
+ extend Gamefic::Serialize
8
+
6
9
  # The scene's primary actor.
7
10
  #
8
11
  # @return [Gamefic::Actor]
@@ -80,6 +80,7 @@ Gamefic::Scriptable.module_exec do
80
80
  instance.public_send :public_send, symbol, *args, &block
81
81
  end
82
82
  end
83
+ theater.extend Gamefic::Serialize
83
84
  theater
84
85
  end
85
86
  end
@@ -1,15 +1,66 @@
1
1
  module Gamefic
2
2
  module Serialize
3
- def to_serial
4
- {
5
- 'class' => self.class.to_s
6
- }.merge serialize_instance_variables
3
+ def to_serial(index = [])
4
+ if index.include?(self)
5
+ {
6
+ 'instance' => "#<ELE_#{index.index(self)}>",
7
+ 'ivars' => {}
8
+ }
9
+ else
10
+ if self.class == Class && self.name
11
+ {
12
+ 'class' => 'Class',
13
+ 'name' => name
14
+ }
15
+ else
16
+ index.push self if self.is_a?(Gamefic::Serialize)
17
+ {
18
+ 'class' => serialized_class(index),
19
+ 'ivars' => serialize_instance_variables(index)
20
+ }
21
+ end
22
+ end
23
+ end
24
+
25
+ def serialized_class index
26
+ if index.include?(self.class)
27
+ "#<ELE_#{index.index(self.class)}>"
28
+ else
29
+ self.class.to_s
30
+ end
31
+ end
32
+
33
+ def self.instances
34
+ GC.start
35
+ result = []
36
+ ObjectSpace.each_object(Gamefic::Serialize) { |obj| result.push obj }
37
+ result
38
+ end
39
+
40
+ # @param string [String]
41
+ # @return [Object]
42
+ def self.string_to_constant string
43
+ space = Object
44
+ string.split('::').each do |part|
45
+ space = space.const_get(part)
46
+ end
47
+ space
7
48
  end
8
49
  end
9
50
  end
10
51
 
11
52
  class Object
12
- def to_serial
53
+ class << self
54
+ def exclude_from_serial ary
55
+ @excluded_from_serial = ary
56
+ end
57
+
58
+ def excluded_from_serial
59
+ @excluded_from_serial ||= []
60
+ end
61
+ end
62
+
63
+ def to_serial(_index)
13
64
  return self if [true, false, nil].include?(self)
14
65
  # @todo This warning is a little too spammy. Set up a logger so it can be
15
66
  # limited to an info or debug level.
@@ -17,51 +68,142 @@ class Object
17
68
  "#<UNKNOWN>"
18
69
  end
19
70
 
20
- def serialize_instance_variables
71
+ def from_serial(index = [])
72
+ if self.is_a?(Hash) && (self['class'] || self['instance'])
73
+ if self['instance']
74
+ elematch = self['instance'].match(/^#<ELE_([\d]+)>$/)
75
+ object = index[elematch[1].to_i]
76
+ raise "Unable to load indexed element ##{elematch[1]} #{self}" if object.nil?
77
+ elsif self['class']
78
+ if self['class'] == 'Hash'
79
+ object = {}
80
+ self['data'].each do |arr|
81
+ object[arr[0].from_serial(index)] = arr[1].from_serial(index)
82
+ end
83
+ return object
84
+ elsif self['class'] == 'Class'
85
+ return Gamefic::Serialize.string_to_constant(self['name'])
86
+ else
87
+ elematch = self['class'].match(/^#<ELE_([\d]+)>$/)
88
+ if elematch
89
+ klass = index[elematch[1].to_i]
90
+ else
91
+ klass = Gamefic::Serialize.string_to_constant(self['class'])
92
+ end
93
+ raise "Unable to find class #{self['class']} #{self}" if klass.nil?
94
+ object = klass.allocate
95
+ index.push object if object.is_a?(Gamefic::Serialize)
96
+ end
97
+ end
98
+ self['ivars'].each_pair do |k, v|
99
+ object.instance_variable_set(k, v.from_serial(index))
100
+ end
101
+ object
102
+ elsif self.is_a?(Numeric)
103
+ self
104
+ elsif self.is_a?(String)
105
+ match = self.match(/#<ELE_([0-9]+)>/)
106
+ return index.index(match[1].to_i) if match
107
+ match = self.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
108
+ return match[1].to_sym if match
109
+ # return nil if self == '#<UNKNOWN>'
110
+ self
111
+ elsif self.is_a?(Hash)
112
+ result = {}
113
+ unknown = false
114
+ self.each_pair do |k, v|
115
+ k2 = k.from_serial(index)
116
+ v2 = v.from_serial(index)
117
+ if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
118
+ unknown = true
119
+ break
120
+ end
121
+ result[k2] = v2
122
+ end
123
+ result = "#<UNKNOWN>" if unknown
124
+ result
125
+ elsif self && self != true
126
+ STDERR.puts "Unable to unserialize #{self.class}"
127
+ nil
128
+ else
129
+ # true, false, or nil
130
+ self
131
+ end
132
+ end
133
+
134
+ def serialize_instance_variables(index)
21
135
  result = {}
22
136
  instance_variables.each do |k|
23
- result[k.to_s] = instance_variable_get(k).to_serial
137
+ next if self.class.excluded_from_serial.include?(k)
138
+ val = instance_variable_get(k)
139
+ if index.include?(val)
140
+ result[k.to_s] = {
141
+ 'instance' => "#<ELE_#{index.index(val)}>",
142
+ 'ivars' => {}
143
+ }
144
+ else
145
+ result[k.to_s] = val.to_serial(index)
146
+ end
24
147
  end
25
148
  result
26
149
  end
27
150
  end
28
151
 
152
+ class Class
153
+ def to_serial(index = [])
154
+ if name.nil?
155
+ super
156
+ else
157
+ {
158
+ 'class' => 'Class',
159
+ 'name' => name
160
+ }
161
+ end
162
+ end
163
+ end
164
+
29
165
  class Symbol
30
- def to_serial
166
+ def to_serial(_index = [])
31
167
  "#<SYM:#{self}>"
32
168
  end
33
169
  end
34
170
 
35
171
  class String
36
- def to_serial
172
+ def to_serial(_index = [])
37
173
  self
38
174
  end
39
175
  end
40
176
 
41
177
  class Numeric
42
- def to_serial
178
+ def to_serial(_index = [])
43
179
  self
44
180
  end
45
181
  end
46
182
 
47
183
  class Array
48
- def to_serial
184
+ def to_serial(index = [])
49
185
  map do |e|
50
- s = e.to_serial
186
+ s = e.to_serial(index)
51
187
  return "#<UNKNOWN>" if s == "#<UNKNOWN>"
52
188
  s
53
189
  end
54
190
  end
191
+
192
+ def from_serial(index = [])
193
+ result = map { |e| e.from_serial(index) }
194
+ result = "#<UNKNOWN>" if result.any? { |e| e == "#<UNKNOWN>" }
195
+ result
196
+ end
55
197
  end
56
198
 
57
199
  class Hash
58
- def to_serial
59
- result = {}
200
+ def to_serial(index = [])
201
+ result = {'class' => 'Hash', 'data' => []}
60
202
  each_pair do |key, value|
61
- k2 = key.to_serial
62
- v2 = value.to_serial
203
+ k2 = key.to_serial(index)
204
+ v2 = value.to_serial(index)
63
205
  return "#<UNKNOWN>" if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
64
- result[k2] = v2
206
+ result['data'].push [k2, v2]
65
207
  end
66
208
  result
67
209
  end
@@ -4,6 +4,7 @@ module Gamefic
4
4
  class Subplot #< Container
5
5
  include World
6
6
  include Scriptable
7
+ include Gamefic::Serialize
7
8
  # @!parse extend Scriptable::ClassMethods
8
9
 
9
10
  # @return [Gamefic::Plot]
@@ -16,10 +17,16 @@ module Gamefic
16
17
  @plot = plot
17
18
  @next_cue = next_cue
18
19
  @concluded = false
20
+ @more = more
19
21
  configure more
20
22
  run_scripts
21
23
  playbook.freeze
22
24
  self.introduce introduce unless introduce.nil?
25
+ @static = [self] + scene_classes + entities
26
+ end
27
+
28
+ def static
29
+ plot.static
23
30
  end
24
31
 
25
32
  def players
@@ -63,6 +70,7 @@ module Gamefic
63
70
  # @todo I'm not sure why rejecting nils is necessary here. It's only an
64
71
  # issue in Opal.
65
72
  entities.reject(&:nil?).each { |e| destroy e }
73
+ # plot.static.remove(scene_classes + entities)
66
74
  end
67
75
 
68
76
  def concluded?
@@ -90,5 +98,15 @@ module Gamefic
90
98
  #
91
99
  def configure more
92
100
  end
101
+
102
+ # def to_serial(index)
103
+ # puts "Serializing #{self}"
104
+ # super
105
+ # end
106
+
107
+ # def from_serial index = []
108
+ # # @todo Customize subplot unserialization
109
+ # super
110
+ # end
93
111
  end
94
112
  end
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '2.0.1'
2
+ VERSION = '2.0.2'
3
3
  end
@@ -43,11 +43,18 @@ module Gamefic
43
43
  # @param [Gamefic::Entity] The entity to remove
44
44
  def destroy entity
45
45
  entity.parent = nil
46
- index = entities.index(entity)
47
- return if index.nil? || index < static_entity_length - 1
48
- entities.delete_at index
46
+ # index = entities.index(entity)
47
+ # return if index.nil? || index < static_entity_length - 1
48
+ # entities.delete_at index
49
+ # players.delete entity
50
+ # entity.destroy
51
+
52
+ # @todo It might make sense to destroy the entity completely now. It
53
+ # will still have a reference in the index, but that shouldn't impact
54
+ # the current state of the plot.
55
+ return if static.include?(entity)
56
+ entities.delete entity
49
57
  players.delete entity
50
- entity.destroy
51
58
  end
52
59
 
53
60
  # Pick an entity based on its description.
@@ -87,15 +94,15 @@ module Gamefic
87
94
  @players ||= []
88
95
  end
89
96
 
90
- private
97
+ # private
91
98
 
92
- def mark_static_entities
93
- @static_entity_length ||= entities.length
94
- end
99
+ # def mark_static_entities
100
+ # @static_entity_length ||= entities.length
101
+ # end
95
102
 
96
- def static_entity_length
97
- @static_entity_length || 0
98
- end
103
+ # def static_entity_length
104
+ # @static_entity_length || 0
105
+ # end
99
106
  end
100
107
  end
101
108
  end
@@ -35,7 +35,7 @@ module Gamefic
35
35
  players.push player
36
36
  @introduction.call(player) unless @introduction.nil?
37
37
  # @todo Find a better way to persist player characters
38
- Gamefic::Index.stick
38
+ # Gamefic::Index.stick
39
39
  end
40
40
 
41
41
  # Create a multiple-choice scene.
@@ -144,7 +144,7 @@ module Gamefic
144
144
  scene_classes.push s
145
145
  s
146
146
  end
147
-
147
+
148
148
  # Create a custom scene.
149
149
  #
150
150
  # Custom scenes should always specify the next scene to be cued or
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamefic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-26 00:00:00.000000000 Z
11
+ date: 2020-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -74,6 +74,7 @@ files:
74
74
  - ".rspec"
75
75
  - ".rubocop.yml"
76
76
  - ".solargraph.yml"
77
+ - CHANGELOG.md
77
78
  - Gemfile
78
79
  - LICENSE
79
80
  - README.md
@@ -89,13 +90,13 @@ files:
89
90
  - lib/gamefic/describable.rb
90
91
  - lib/gamefic/element.rb
91
92
  - lib/gamefic/entity.rb
92
- - lib/gamefic/index.rb
93
93
  - lib/gamefic/keywords.rb
94
94
  - lib/gamefic/messaging.rb
95
95
  - lib/gamefic/node.rb
96
96
  - lib/gamefic/plot.rb
97
97
  - lib/gamefic/plot/darkroom.rb
98
98
  - lib/gamefic/plot/host.rb
99
+ - lib/gamefic/plot/index.rb
99
100
  - lib/gamefic/plot/snapshot.rb
100
101
  - lib/gamefic/query.rb
101
102
  - lib/gamefic/query/base.rb
@@ -1,121 +0,0 @@
1
- require 'json'
2
-
3
- module Gamefic
4
- module Index
5
- @@elements = []
6
- @@stuck_length = 0
7
-
8
- def initialize **data
9
- data.each_pair do |k, v|
10
- public_send "#{k}=", v
11
- end
12
- @@elements.push self
13
- end
14
-
15
- def to_serial
16
- index = @@elements.index(self)
17
- raise RuntimeError, "#{self} is not an indexed element" unless index
18
- "#<ELE_#{index}>"
19
- end
20
-
21
- def destroy
22
- @@elements.delete self unless Index.stuck?(self)
23
- end
24
-
25
- def self.elements
26
- @@elements
27
- end
28
-
29
- def self.serials
30
- result = []
31
- @@elements.each do |e|
32
- d = {}
33
- d['class'] = e.class.to_s
34
- e.instance_variables.each do |k|
35
- v = e.instance_variable_get(k)
36
- d[k] = v.to_serial
37
- end
38
- result.push d
39
- end
40
- result
41
- end
42
-
43
- def self.from_serial serial
44
- if serial.is_a?(Hash) && serial['class']
45
- klass = eval(serial['class'])
46
- object = klass.allocate
47
- serial.each_pair do |k, v|
48
- next unless k.to_s.start_with?('@')
49
- object.instance_variable_set(k, from_serial(v))
50
- end
51
- object
52
- elsif serial.is_a?(Numeric)
53
- serial
54
- elsif serial.is_a?(String)
55
- match = serial.match(/#<ELE_([0-9]+)>/)
56
- return Gamefic::Index.elements[match[1].to_i] if match
57
- match = serial.match(/#<SYM:([a-z0-9_\?\!]+)>/i)
58
- return match[1].to_sym if match
59
- serial
60
- elsif serial.is_a?(Array)
61
- result = serial.map { |e| from_serial(e) }
62
- result = "#<UNKNOWN>" if result.any? { |e| e == "#<UNKNOWN>" }
63
- result
64
- elsif serial.is_a?(Hash)
65
- result = {}
66
- unknown = false
67
- serial.each_pair do |k, v|
68
- k2 = from_serial(k)
69
- v2 = from_serial(v)
70
- if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
71
- unknown = true
72
- break
73
- end
74
- result[k2] = v2
75
- end
76
- result = "#<UNKNOWN>" if unknown
77
- result
78
- elsif serial && serial != true
79
- STDERR.puts "Unable to unserialize #{serial.class}"
80
- nil
81
- else
82
- # true, false, or nil
83
- serial
84
- end
85
- end
86
-
87
- def self.unserialize serials
88
- serials.each_with_index do |s, i|
89
- next if elements[i]
90
- klass = eval(s['class'])
91
- klass.new
92
- end
93
- serials.each_with_index do |s, i|
94
- s.each_pair do |k, v|
95
- next unless k.to_s.start_with?('@')
96
- next if v == "#<UNKNOWN>"
97
- elements[i].instance_variable_set(k, from_serial(v))
98
- end
99
- end
100
- elements
101
- end
102
-
103
- def self.stick
104
- @@stuck_length = @@elements.length
105
- end
106
-
107
- def self.stuck
108
- @@stuck_length
109
- end
110
-
111
- def self.clear
112
- @@stuck_length = 0
113
- @@elements.clear
114
- end
115
-
116
- def self.stuck? thing
117
- index = @@elements.index(thing)
118
- index && index <= @@stuck_length - 1
119
- end
120
- end
121
- end