gamefic 2.0.0 → 2.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 758b3f682dd5837841ce39281a53e6d8beada80ca0f0f0308f2b3909d12349b7
4
- data.tar.gz: c330934f0a69040e96cf7c18936b93e5b5cf7580ba95a0ef46e633a79975f513
3
+ metadata.gz: 5d4af1f6add0c467fce62acde7d4903e2165a0007312720ac0a43fb324e2a594
4
+ data.tar.gz: fe6adad9a8c85fd9eac1b9b6921e13f356da73489e9d82c95b63974f94f45df0
5
5
  SHA512:
6
- metadata.gz: f95a19e039ef00076f23f90902077ba78941b2b186bad9963222da21001148741800058c97c7a110662f660af3334ef0a4732046210946ce9a9c54a340d8d566
7
- data.tar.gz: dfb08b610f13a75c5515bf95d41834db415bee8a3d196238f53385c6d2983a84dcd8906d048440c21601858f13c48a0f2359da21ea0596455ee7fac7ddd66acf
6
+ metadata.gz: b2b0fda9b69825c655f6dfa90fa4c1bca78a6a53ea625946d54f9981d11104660f1e4f312bc53086559345f0c211edf979f237e18fd4bf95d83a2755e7b1cc9b
7
+ data.tar.gz: b1c85ff6f5c103354af38588bc129c989929c259ef4062abd8e9a0e5f92100fcf2134e1d16f3d8e3180bbcacd672d85b3cf5d1f162a8ff19217a64dc419e9bd7
data/.rubocop.yml CHANGED
@@ -10,4 +10,7 @@ Style/StringLiterals:
10
10
 
11
11
  Style/Documentation:
12
12
  Description: 'Document classes and non-namespace modules.'
13
- Enabled: false
13
+ Enabled: false
14
+
15
+ Style/MethodDefParentheses:
16
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # 2.1.1 - July 23, 2021
2
+ - Remove gamefic/scene/custom autoload
3
+
4
+ # 2.1.0 - June 21, 2021
5
+ - Remove redundant MultipleChoice prompt
6
+ - Deprecate Scene::Custom
7
+
8
+ # 2.0.3 - December 14, 2020
9
+ - Remove unused Index class
10
+ - Active#conclude accepts data argument
11
+
12
+ # 2.0.2 - April 25, 2020
13
+ - Improved snapshot serialization
data/lib/gamefic.rb CHANGED
@@ -5,7 +5,6 @@ require 'gamefic/core_ext/array'
5
5
  require 'gamefic/core_ext/string'
6
6
 
7
7
  require 'gamefic/describable'
8
- require 'gamefic/index'
9
8
  require 'gamefic/serialize'
10
9
  require 'gamefic/element'
11
10
  require 'gamefic/entity'
@@ -17,5 +16,5 @@ require "gamefic/action"
17
16
  require "gamefic/syntax"
18
17
  require 'gamefic/world'
19
18
  require 'gamefic/scriptable'
20
- require "gamefic/plot"
19
+ require 'gamefic/plot'
21
20
  require 'gamefic/subplot'
@@ -1,9 +1,4 @@
1
1
  module Gamefic
2
- # Exception raised when the Action's proc arity is not compatible with the
3
- # number of queries
4
- class ActionArgumentError < ArgumentError
5
- end
6
-
7
2
  class Action
8
3
  # An array of objects on which the action will operate, e.g., an entity
9
4
  # that is a direct object of a command.
@@ -54,25 +49,32 @@ module Gamefic
54
49
  self.class.meta?
55
50
  end
56
51
 
57
- def self.subclass verb, *q, meta: false, &block
52
+ # @param verb [Symbol]
53
+ # @param queries [Array<Gamefic::Query::Base>]
54
+ # @param meta [Boolean]
55
+ # @return [Class<Action>]
56
+ def self.subclass verb, *queries, meta: false, &block
58
57
  act = Class.new(self) do
59
58
  self.verb = verb
60
59
  self.meta = meta
61
- q.each { |q|
60
+ queries.each do |q|
62
61
  add_query q
63
- }
62
+ end
64
63
  on_execute &block
65
64
  end
66
- if !block.nil? and act.queries.length + 1 != block.arity and block.arity > 0
67
- raise ActionArgumentError.new("Number of parameters is not compatible with proc arguments")
65
+ if !block.nil? && act.queries.length + 1 != block.arity && block.arity > 0
66
+ raise ArgumentError.new("Number of parameters is not compatible with proc arguments")
68
67
  end
69
68
  act
70
69
  end
71
70
 
72
71
  class << self
73
- def verb
74
- @verb
75
- end
72
+ attr_reader :verb
73
+
74
+ # The proc to call when the action is executed
75
+ #
76
+ # @return [Proc]
77
+ attr_reader :executor
76
78
 
77
79
  def meta?
78
80
  @meta ||= false
@@ -93,7 +95,7 @@ module Gamefic
93
95
 
94
96
  def signature
95
97
  # @todo This is clearly unfinished
96
- "#{verb} #{queries.map{|m| m.signature}.join(', ')}"
98
+ "#{verb} #{queries.map {|m| m.signature}.join(', ')}"
97
99
  end
98
100
 
99
101
  # True if this action is not intended to be performed directly by a
@@ -107,19 +109,13 @@ module Gamefic
107
109
  verb.to_s.start_with?('_')
108
110
  end
109
111
 
110
- # The proc to call when the action is executed
111
- #
112
- # @return [Proc]
113
- def executor
114
- @executor
115
- end
116
-
112
+ # @return [Integer]
117
113
  def rank
118
114
  if @rank.nil?
119
115
  @rank = 0
120
- queries.each { |q|
116
+ queries.each do |q|
121
117
  @rank += (q.rank + 1)
122
- }
118
+ end
123
119
  @rank -= 1000 if verb.nil?
124
120
  end
125
121
  @rank
@@ -128,22 +124,27 @@ module Gamefic
128
124
  def valid? actor, objects
129
125
  return false if objects.length != queries.length
130
126
  i = 0
131
- queries.each { |p|
127
+ queries.each do |p|
132
128
  return false unless p.include?(actor, objects[i])
133
129
  i += 1
134
- }
130
+ end
135
131
  true
136
132
  end
137
133
 
134
+ # Return an instance of this Action if the actor can execute it with the
135
+ # provided tokens, or nil if the tokens are invalid.
136
+ #
137
+ # @param action [Gamefic::Entity]
138
+ # @param tokens [Array<String>]
139
+ # @return [self, nil]
138
140
  def attempt actor, tokens
139
- i = 0
140
141
  result = []
141
142
  matches = Gamefic::Query::Matches.new([], '', '')
142
- queries.each { |p|
143
- return nil if tokens[i].nil? and matches.remaining == ''
143
+ queries.each_with_index do |p, i|
144
+ return nil if tokens[i].nil? && matches.remaining == ''
144
145
  matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
145
146
  return nil if matches.objects.empty?
146
- accepted = matches.objects.select{|o| p.accept?(o)}
147
+ accepted = matches.objects.select { |o| p.accept?(o) }
147
148
  return nil if accepted.empty?
148
149
  if p.ambiguous?
149
150
  result.push accepted
@@ -151,20 +152,15 @@ module Gamefic
151
152
  return nil if accepted.length != 1
152
153
  result.push accepted.first
153
154
  end
154
- i += 1
155
- }
156
- self.new(actor, result)
155
+ end
156
+ new(actor, result)
157
157
  end
158
158
 
159
159
  protected
160
160
 
161
- def verb= sym
162
- @verb = sym
163
- end
161
+ attr_writer :verb
164
162
 
165
- def meta= bool
166
- @meta = bool
167
- end
163
+ attr_writer :meta
168
164
  end
169
165
  end
170
166
  end
@@ -117,9 +117,7 @@ module Gamefic
117
117
  #
118
118
  # @return [String] The output that resulted from performing the command.
119
119
  def quietly(*command)
120
- if buffer_stack == 0
121
- clear_buffer
122
- end
120
+ clear_buffer if buffer_stack == 0
123
121
  set_buffer_stack buffer_stack + 1
124
122
  self.perform *command
125
123
  set_buffer_stack buffer_stack - 1
@@ -152,10 +150,12 @@ module Gamefic
152
150
  # introduction do |actor|
153
151
  # actor[:has_eaten] = false # Initial value
154
152
  # end
153
+ #
155
154
  # respond :eat do |actor|
156
155
  # actor.tell "You eat something."
157
156
  # actor[:has_eaten] = true
158
157
  # end
158
+ #
159
159
  # respond :eat do |actor|
160
160
  # # This version will be executed first because it was implemented last
161
161
  # if actor[:has_eaten]
@@ -187,13 +187,14 @@ module Gamefic
187
187
  # Use #prepare if you want to declare a scene to be started at the
188
188
  # beginning of the next turn.
189
189
  #
190
- # @param new_scene [Class]
191
- def cue new_scene, **options
190
+ # @param new_scene [Class<Scene::Base>]
191
+ # @param data [Hash] Additional scene data
192
+ def cue new_scene, **data
192
193
  @next_scene = nil
193
194
  if new_scene.nil?
194
195
  @scene = nil
195
196
  else
196
- @scene = new_scene.new(self, **options)
197
+ @scene = new_scene.new(self, **data)
197
198
  @scene.start
198
199
  end
199
200
  end
@@ -202,10 +203,11 @@ module Gamefic
202
203
  # next turn. As opposed to #cue, a prepared scene will not start until the
203
204
  # current scene finishes.
204
205
  #
205
- # @param new_scene [Class]
206
- def prepare new_scene, **options
206
+ # @param new_scene [Class<Scene::Base>]
207
+ # @oaram data [Hash] Additional scene data
208
+ def prepare new_scene, **data
207
209
  @next_scene = new_scene
208
- @next_options = options
210
+ @next_options = data
209
211
  end
210
212
 
211
213
  # Return true if the character is expected to be in the specified scene on
@@ -219,9 +221,11 @@ module Gamefic
219
221
  # Cue a conclusion. This method works like #cue, except it will raise a
220
222
  # NotConclusionError if the scene is not a Scene::Conclusion.
221
223
  #
222
- def conclude scene
223
- raise NotConclusionError unless scene <= Scene::Conclusion
224
- cue scene
224
+ # @param new_scene [Class<Scene::Base>]
225
+ # @oaram data [Hash] Additional scene data
226
+ def conclude new_scene, **data
227
+ raise NotConclusionError unless new_scene <= Scene::Conclusion
228
+ cue new_scene, **data
225
229
  end
226
230
 
227
231
  # True if the character is in a conclusion.
@@ -269,6 +273,8 @@ module Gamefic
269
273
  @entered_scenes ||= []
270
274
  end
271
275
 
276
+ # @param actions [Array<Gamefic::Action>]
277
+ # @param quietly [Boolean]
272
278
  def execute_stack actions, quietly: false
273
279
  return nil if actions.empty?
274
280
  a = actions.first
@@ -44,6 +44,9 @@ class Array
44
44
  # animals = ['a dog', 'a cat', 'a mouse']
45
45
  # animals.join_and #=> 'a dog, a cat, and a mouse'
46
46
  #
47
+ # @param sep [String] The separator for all but the last element
48
+ # @param andSep [String] The separator for the last element
49
+ # @param serial [Boolean] Use serial separators (e.g., serial commas)
47
50
  # @return [String]
48
51
  def join_and(sep = ', ', andSep = ' and ', serial = true)
49
52
  if self.length < 3
@@ -7,11 +7,15 @@ 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
+ # 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
15
19
  post_initialize
16
20
  yield self if block_given?
17
21
  end
data/lib/gamefic/plot.rb CHANGED
@@ -16,25 +16,22 @@ 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
- # @param structure [Gamefic::Structure]
26
30
  # @param metadata [Hash]
27
31
  def initialize metadata: {}
28
- Gamefic::Index.clear
29
32
  @metadata = metadata
30
33
  run_scripts
31
- mark_static_entities
32
- Gamefic::Index.stick
33
- end
34
-
35
- def player_class cls = nil
36
- @player_class = cls unless cls.nil?
37
- @player_class ||= Gamefic::Actor
34
+ @static = [self] + scene_classes + entities
38
35
  end
39
36
 
40
37
  # Get an Array of the Plot's current Syntaxes.
@@ -59,7 +56,8 @@ module Gamefic
59
56
  .merge({
60
57
  messages: p.messages,
61
58
  last_prompt: p.last_prompt,
62
- last_input: p.last_input
59
+ last_input: p.last_input,
60
+ queue: p.queue
63
61
  })
64
62
  .merge(p.state)
65
63
  )
@@ -89,8 +87,8 @@ module Gamefic
89
87
  end
90
88
  call_player_update
91
89
  call_update
92
- subplots.each { |s| s.update unless s.concluded? }
93
- subplots.delete_if { |s| s.concluded? }
90
+ subplots.delete_if(&:concluded?)
91
+ subplots.each(&:update)
94
92
  end
95
93
 
96
94
  # Send a message to a group of entities.
@@ -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