gamefic 1.7.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +16 -0
  5. data/.solargraph.yml +5 -0
  6. data/CHANGELOG.md +10 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +20 -0
  9. data/README.md +28 -0
  10. data/Rakefile +10 -0
  11. data/gamefic.gemspec +27 -0
  12. data/lib/gamefic.rb +7 -7
  13. data/lib/gamefic/action.rb +66 -60
  14. data/lib/gamefic/active.rb +331 -280
  15. data/lib/gamefic/actor.rb +8 -5
  16. data/lib/gamefic/command.rb +9 -7
  17. data/lib/gamefic/core_ext/array.rb +27 -49
  18. data/lib/gamefic/core_ext/string.rb +25 -16
  19. data/lib/gamefic/describable.rb +21 -23
  20. data/lib/gamefic/element.rb +47 -31
  21. data/lib/gamefic/entity.rb +6 -12
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -44
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +69 -91
  26. data/lib/gamefic/plot/darkroom.rb +80 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +14 -19
  29. data/lib/gamefic/query.rb +15 -18
  30. data/lib/gamefic/query/base.rb +50 -37
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/descendants.rb +2 -2
  33. data/lib/gamefic/query/external.rb +18 -14
  34. data/lib/gamefic/query/family.rb +3 -7
  35. data/lib/gamefic/query/matches.rb +75 -67
  36. data/lib/gamefic/query/parent.rb +0 -0
  37. data/lib/gamefic/query/siblings.rb +0 -0
  38. data/lib/gamefic/query/text.rb +12 -12
  39. data/lib/gamefic/query/tree.rb +17 -0
  40. data/lib/gamefic/scene.rb +0 -2
  41. data/lib/gamefic/scene/activity.rb +24 -26
  42. data/lib/gamefic/scene/base.rb +71 -10
  43. data/lib/gamefic/scene/conclusion.rb +1 -3
  44. data/lib/gamefic/scene/multiple_choice.rb +19 -14
  45. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  46. data/lib/gamefic/scene/pause.rb +8 -3
  47. data/lib/gamefic/scene/yes_or_no.rb +22 -10
  48. data/lib/gamefic/scriptable.rb +88 -0
  49. data/lib/gamefic/serialize.rb +223 -0
  50. data/lib/gamefic/subplot.rb +38 -35
  51. data/lib/gamefic/syntax.rb +15 -13
  52. data/lib/gamefic/version.rb +3 -3
  53. data/lib/gamefic/world.rb +18 -0
  54. data/lib/gamefic/world/callbacks.rb +135 -0
  55. data/lib/gamefic/world/commands.rb +184 -0
  56. data/lib/gamefic/{plot → world}/entities.rb +33 -35
  57. data/lib/gamefic/{plot → world}/playbook.rb +245 -240
  58. data/lib/gamefic/world/players.rb +37 -0
  59. data/lib/gamefic/world/scenes.rb +226 -0
  60. metadata +37 -88
  61. data/bin/gamefic +0 -9
  62. data/lib/gamefic/engine.rb +0 -7
  63. data/lib/gamefic/engine/base.rb +0 -59
  64. data/lib/gamefic/engine/tty.rb +0 -24
  65. data/lib/gamefic/grammar.rb +0 -13
  66. data/lib/gamefic/grammar/conjugator.rb +0 -20
  67. data/lib/gamefic/grammar/gender.rb +0 -11
  68. data/lib/gamefic/grammar/person.rb +0 -10
  69. data/lib/gamefic/grammar/plural.rb +0 -13
  70. data/lib/gamefic/grammar/pronouns.rb +0 -106
  71. data/lib/gamefic/grammar/tense.rb +0 -6
  72. data/lib/gamefic/grammar/verb_set.rb +0 -43
  73. data/lib/gamefic/grammar/verbs.rb +0 -26
  74. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  75. data/lib/gamefic/plot/articles.rb +0 -22
  76. data/lib/gamefic/plot/callbacks.rb +0 -126
  77. data/lib/gamefic/plot/commands.rb +0 -120
  78. data/lib/gamefic/plot/players.rb +0 -15
  79. data/lib/gamefic/plot/scenes.rb +0 -187
  80. data/lib/gamefic/plot/theater.rb +0 -73
  81. data/lib/gamefic/plot/you_mount.rb +0 -22
  82. data/lib/gamefic/scene/custom.rb +0 -9
  83. data/lib/gamefic/script.rb +0 -13
  84. data/lib/gamefic/script/base.rb +0 -42
  85. data/lib/gamefic/script/file.rb +0 -14
  86. data/lib/gamefic/script/text.rb +0 -14
  87. data/lib/gamefic/shell.rb +0 -76
  88. data/lib/gamefic/source.rb +0 -14
  89. data/lib/gamefic/source/base.rb +0 -12
  90. data/lib/gamefic/source/file.rb +0 -23
  91. data/lib/gamefic/source/text.rb +0 -16
  92. data/lib/gamefic/tester.rb +0 -19
  93. data/lib/gamefic/text.rb +0 -8
  94. data/lib/gamefic/text/ansi.rb +0 -53
  95. data/lib/gamefic/text/html.rb +0 -68
  96. data/lib/gamefic/text/html/conversions.rb +0 -250
  97. data/lib/gamefic/text/html/entities.rb +0 -9
  98. data/lib/gamefic/tty.rb +0 -10
  99. data/lib/gamefic/user.rb +0 -7
  100. data/lib/gamefic/user/base.rb +0 -29
  101. data/lib/gamefic/user/tty.rb +0 -38
@@ -1,264 +1,80 @@
1
- module Gamefic
2
- # Create and restore plot snapshots.
3
- #
4
- class Plot::Darkroom
5
- attr_reader :plot
6
-
7
- def initialize plot
8
- @plot = plot
9
- end
10
-
11
- # Create a snapshot of the plot.
12
- #
13
- # @return [Hash]
14
- def save
15
- result = { entities: [], players: [], subplots: [], instance_variables: {} }
16
- entity_store.clear
17
- player_store.clear
18
- entity_store.concat plot.entities
19
- player_store.concat plot.players
20
- plot.subplots.each { |s| entity_store.concat s.entities }
21
- entity_store.uniq!
22
- entity_store.each do |e|
23
- result[:entities].push hash_entity(e)
24
- end
25
- player_store.each do |p|
26
- result[:players].push hash_entity(p)
27
- end
28
- plot.theater.instance_variables.each { |i|
29
- v = plot.theater.instance_variable_get(i)
30
- result[:instance_variables][i] = serialize(v) if can_serialize?(v)
31
- }
32
- plot.subplots.each { |s|
33
- result[:subplots].push hash_subplot(s)
34
- }
35
- result
36
- end
37
-
38
- # Restore a snapshot.
39
- #
40
- def restore snapshot
41
- entity_store.clear
42
- player_store.clear
43
- plot.subplots.each { |s| s.conclude }
44
- plot.entities[plot.initial_state[:entities].length..-1].each { |e| plot.destroy e }
45
- entity_store.concat plot.entities[0..plot.initial_state[:entities].length-1]
46
- entity_store.uniq!
47
- player_store.concat plot.players
48
- i = 0
49
- snapshot[:entities].each { |h|
50
- if entity_store[i].nil?
51
- e = plot.stage do
52
- cls = self.const_get(h[:class])
53
- make cls
54
- end
55
- entity_store.push e
56
- end
57
- i += 1
58
- }
59
- snapshot[:subplots].each { |s|
60
- sp = plot.stage do
61
- cls = const_get(s[:class])
62
- branch cls
63
- end
64
- # @todo Assuming one player
65
- sp.introduce player_store[0] unless player_store.empty?
66
- rebuild_subplot sp, s
67
- }
68
- i = 0
69
- snapshot[:entities].each { |h|
70
- rebuild1 entity_store[i], h
71
- i += 1
72
- }
73
- i = 0
74
- snapshot[:players].each { |p|
75
- rebuild1 player_store[i], p
76
- i += 1
77
- }
78
- i = 0
79
- snapshot[:entities].each { |h|
80
- rebuild2 entity_store[i], h
81
- i += 1
82
- }
83
- i = 0
84
- snapshot[:players].each { |h|
85
- rebuild2 player_store[i], h
86
- i += 1
87
- }
88
- snapshot[:instance_variables].each_pair { |k, v|
89
- plot.theater.instance_variable_set(k, unserialize(v))
90
- }
91
- end
92
-
93
- private
94
-
95
- def hash_blacklist
96
- [:@parent, :@children, :@last_action, :@scene, :@next_scene, :@playbook, :@performance_stack, :@buffer_stack, :@messages, :@state]
97
- end
98
-
99
- def can_serialize? v
100
- return true if v.kind_of?(String) or v.kind_of?(Numeric) or v.kind_of?(Symbol) or v.kind_of?(Gamefic::Entity) or is_scene_class?(v) or v == true or v == false or v.nil?
101
- if v.kind_of?(Array)
102
- v.each do |e|
103
- result = can_serialize?(e)
104
- return false if result == false
105
- end
106
- true
107
- elsif v.kind_of?(Hash)
108
- v.each_pair do |k, v|
109
- result = can_serialize?(k)
110
- return false if result == false
111
- result = can_serialize?(v)
112
- return false if result == false
113
- end
114
- true
115
- else
116
- false
117
- end
118
- end
119
-
120
- def is_scene_class?(v)
121
- if v.kind_of?(Class)
122
- s = v
123
- until s.nil?
124
- return true if s == Gamefic::Scene::Base
125
- s = s.superclass
126
- end
127
- false
128
- else
129
- false
130
- end
131
- end
132
-
133
- def serialize v
134
- if v.kind_of?(Array)
135
- result = []
136
- v.each do |e|
137
- result.push serialize(e)
138
- end
139
- result
140
- elsif v.kind_of?(Hash)
141
- result = {}
142
- v.each_pair do |k, v|
143
- result[serialize(k)] = serialize(v)
144
- end
145
- result
146
- elsif is_scene_class?(v)
147
- i = plot.scene_classes.index(v)
148
- "#<SIN_#{i}>"
149
- elsif v.kind_of?(Gamefic::Entity)
150
- i = entity_store.index(v)
151
- if i.nil?
152
- i = player_store.index(v)
153
- if i.nil?
154
- raise "#{v} not found in plot"
155
- nil
156
- else
157
- "#<PIN_#{i}>"
158
- end
159
- else
160
- "#<EIN_#{i}>"
161
- end
162
- else
163
- v
164
- end
165
- end
166
-
167
- def unserialize v
168
- if v.kind_of?(Array)
169
- result = []
170
- v.each do |e|
171
- result.push unserialize(e)
172
- end
173
- result
174
- elsif v.kind_of?(Hash)
175
- result = {}
176
- v.each_pair do |k, v|
177
- result[unserialize(k)] = unserialize(v)
178
- end
179
- result
180
- elsif v.kind_of?(String)
181
- if m = v.match(/#<SIN_([0-9]+)>/)
182
- plot.scene_classes[m[1].to_i]
183
- elsif m = v.match(/#<EIN_([0-9]+)>/)
184
- entity_store[m[1].to_i]
185
- elsif m = v.match(/#<PIN_([0-9]+)>/)
186
- player_store[m[1].to_i]
187
- else
188
- v
189
- end
190
- else
191
- v
192
- end
193
- end
194
-
195
- def rebuild1 e, h
196
- h.each_pair do |k, v|
197
- if k.to_s.start_with?('@')
198
- e.instance_variable_set(k, unserialize(v))
199
- end
200
- end
201
- end
202
-
203
- def rebuild2 e, h
204
- h.each_pair do |k, v|
205
- if k.to_s != 'class' and !k.to_s.start_with?('@')
206
- e.send("#{k}=", unserialize(v))
207
- end
208
- end
209
- end
210
-
211
- def hash_subplot s
212
- result = { entities: [], instance_variables: {}, theater_instance_variables: {} }
213
- s.instance_variables.each { |i|
214
- v = s.instance_variable_get(i)
215
- result[:instance_variables][i] = serialize(v) if can_serialize?(v)
216
- }
217
- s.theater.instance_variables.each { |i|
218
- v = s.theater.instance_variable_get(i)
219
- result[:theater_instance_variables][i] = serialize(v) if can_serialize?(v)
220
- }
221
- s.entities.each { |s|
222
- result[:entities].push serialize(s)
223
- }
224
- result[:class] = s.class.to_s.split('::').last
225
- result
226
- end
227
-
228
- def rebuild_subplot s, h
229
- s.entities.each { |e|
230
- s.destroy e
231
- }
232
- h[:instance_variables].each_pair { |k, v|
233
- s.instance_variable_set(k, unserialize(v))
234
- }
235
- h[:theater_instance_variables].each_pair { |k, v|
236
- s.theater.instance_variable_set(k, unserialize(v))
237
- }
238
- i = 0
239
- h[:entities].each { |e|
240
- s.add_entity unserialize(e)
241
- i += 1
242
- }
243
- end
244
-
245
- def entity_store
246
- @entity_store ||= []
247
- end
248
-
249
- def player_store
250
- @player_store ||= []
251
- end
252
-
253
- def hash_entity e
254
- h = {}
255
- e.instance_variables.each { |i|
256
- v = e.instance_variable_get(i)
257
- h[i] = serialize(v) unless hash_blacklist.include?(i) or !can_serialize?(v)
258
- }
259
- h[:class] = e.class.to_s.split('::').last
260
- h[:parent] = serialize(e.parent)
261
- h
262
- end
263
- end
264
- end
1
+ module Gamefic
2
+ # Create and restore plot snapshots.
3
+ #
4
+ class Plot
5
+ class Darkroom
6
+ # @return [Gamefic::Plot]
7
+ attr_reader :plot
8
+
9
+ def initialize plot
10
+ @plot = plot
11
+ end
12
+
13
+ # Create a snapshot of the plot.
14
+ #
15
+ # @return [Hash]
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
31
+ }
32
+ end
33
+
34
+ # Restore a snapshot.
35
+ #
36
+ # @param snapshot [Hash]
37
+ def restore snapshot
38
+ # @todo Use `program` for verification
39
+
40
+ plot.subplots.each(&:conclude)
41
+ plot.subplots.clear
42
+
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
+ end
54
+
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
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,48 +1,42 @@
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 subplot_class [Class] The class of the subplot to be created (Subplot by default)
16
- # @return [Subplot]
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
- p_subplots.push subplot
20
- subplot
21
- end
22
-
23
- # Get the player's current subplots.
24
- #
25
- # @return [Array<Subplot>]
26
- def subplots_featuring player
27
- result = []
28
- subplots.each { |s|
29
- result.push s if s.players.include?(player)
30
- }
31
- result
32
- end
33
-
34
- # Determine whether the player is involved in a subplot.
35
- #
36
- # @return [Boolean]
37
- def in_subplot? player
38
- !subplots_featuring(player).empty?
39
- end
40
-
41
- private
42
-
43
- def p_subplots
44
- @p_subplots ||= []
45
- end
46
- end
47
-
48
- end
1
+ require 'gamefic/subplot'
2
+
3
+ module Gamefic
4
+ # Methods for hosting and managing subplots.
5
+ #
6
+ module Plot::Host
7
+ # Get an array of all the current subplots.
8
+ #
9
+ # @return [Array<Subplot>]
10
+ def subplots
11
+ @subplots ||= []
12
+ end
13
+
14
+ # Start a new subplot based on the provided class.
15
+ #
16
+ # @param subplot_class [Class<Gamefic::Subplot>] The class of the subplot to be created (Subplot by default)
17
+ # @return [Gamefic::Subplot]
18
+ def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil, **more
19
+ subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, **more)
20
+ subplots.push subplot
21
+ subplot
22
+ end
23
+
24
+ # Get the player's current subplots.
25
+ #
26
+ # @return [Array<Subplot>]
27
+ def subplots_featuring player
28
+ result = []
29
+ subplots.each { |s|
30
+ result.push s if s.players.include?(player)
31
+ }
32
+ result
33
+ end
34
+
35
+ # Determine whether the player is involved in a subplot.
36
+ #
37
+ # @return [Boolean]
38
+ def in_subplot? player
39
+ !subplots_featuring(player).empty?
40
+ end
41
+ end
42
+ end
@@ -2,31 +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
- initial_state
8
- internal_save
11
+ Gamefic::Plot::Darkroom.new(self).save
9
12
  end
10
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]
11
22
  def restore snapshot
12
- # HACK Force conclusion of current subplots
13
- p_subplots.each { |s| s.conclude }
14
- p_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
- end
18
-
19
- def initial_state
20
- if @initial_state.nil?
21
- @initial_state = internal_save
22
- end
23
- @initial_state
24
- end
25
-
26
- private
27
-
28
- def internal_save
29
- Gamefic::Plot::Darkroom.new(self).save
30
25
  end
31
26
  end
32
27
  end