gamefic 2.0.0 → 2.1.1

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: 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