gamefic 2.2.2 → 2.3.0

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: 7d03f9cc05efbae7569a6dfa66f5ffeccc629180581e8b6acabeddd40f8dca12
4
- data.tar.gz: f48f8890c0d894be3c2e888a4d7325b936d66ebda39c5e2dd1c1e324dd1c091b
3
+ metadata.gz: aa8d74d06ad87105050c1ca487acd976d293f4ca52fa6cc760061ccdbb3a14e7
4
+ data.tar.gz: f791519291a168cf31045d6bc2f136d4829f5ddce2fa7c370d6c4887f83f560e
5
5
  SHA512:
6
- metadata.gz: 7776a069549d44db264d012a00306f95edcf890988cbf2669edd61de97d8cf9527306136bb8c7d07ccb7975c5a48c04029f7401a2e7ea94baf372df58c454722
7
- data.tar.gz: ed07e4a417207f63b6bd3f84467efb2a0cd32e73d07ecc57a642b05e6dab4bf788f98b2ee3184e42a7ac0cacbb7e2985d2cdade1df513f6f7d3539f5e7ef56c2
6
+ metadata.gz: e8abcf0f9ca24db9b8bc3f61be5d0d28dcd86c24f047e420eb117bd09411b70d5f3ae7e9c2c5d2277062c4c72a970d2c9144eb0990a46872eb80b7a7bd75369e
7
+ data.tar.gz: 2bdec46fe7d946a05c3d9bf258314db25c61f32459993f03f1c2096dd31b0056917332fcd5c05ee011e3c352e373a1dcaa74f670d069398e481334ff64c07f1d
data/.rubocop.yml CHANGED
@@ -10,7 +10,4 @@ Style/StringLiterals:
10
10
 
11
11
  Style/Documentation:
12
12
  Description: 'Document classes and non-namespace modules.'
13
- Enabled: false
14
-
15
- Style/MethodDefParentheses:
16
- Enabled: false
13
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 2.3.0 - January 25, 2023
2
+ - Remove unused Active#actions method
3
+ - Add before_action and after_action hooks
4
+
5
+ ## 2.2.3 - July 9, 2022
6
+ - Fix Ruby version incompatibilities
7
+ - Fix private attr_accessor call
8
+
1
9
  ## 2.2.2 - September 6, 2021
2
10
  - Darkroom indexes non-static elements
3
11
 
@@ -1,5 +1,8 @@
1
1
  module Gamefic
2
2
  class Action
3
+ # @return [Gamefic::Actor]
4
+ attr_reader :actor
5
+
3
6
  # An array of objects on which the action will operate, e.g., an entity
4
7
  # that is a direct object of a command.
5
8
  #
@@ -7,17 +10,31 @@ module Gamefic
7
10
  attr_reader :arguments
8
11
  alias parameters arguments
9
12
 
10
- def initialize actor, arguments
13
+ # @param actor [Gamefic::Actor]
14
+ # @param arguments [Array<Object>]
15
+ def initialize actor, arguments, with_callbacks = false
11
16
  @actor = actor
12
17
  @arguments = arguments
13
18
  @executed = false
19
+ @with_callbacks = with_callbacks
14
20
  end
15
21
 
16
22
  # Perform the action.
17
23
  #
18
24
  def execute
19
- @executed = true
25
+ return if @cancelled
26
+ run_before_actions
27
+ return if @cancelled
20
28
  self.class.executor.call(@actor, *arguments) unless self.class.executor.nil?
29
+ @executed = true
30
+ run_after_actions
31
+ end
32
+
33
+ # Cancel an action. This method can be called in a before_action hook to
34
+ # prevent subsequent hooks and the action itself from being executed.
35
+ #
36
+ def cancel
37
+ @cancelled = true
21
38
  end
22
39
 
23
40
  # True if the #execute method has been called for this action.
@@ -68,6 +85,25 @@ module Gamefic
68
85
  act
69
86
  end
70
87
 
88
+ private
89
+
90
+ def run_before_actions
91
+ return unless @with_callbacks
92
+ @actor.playbooks
93
+ .flat_map(&:before_actions)
94
+ .each do |block|
95
+ block.call(self)
96
+ break if @cancelled
97
+ end
98
+ end
99
+
100
+ def run_after_actions
101
+ return unless @with_callbacks
102
+ @actor.playbooks
103
+ .flat_map(&:after_actions)
104
+ .each { |block| block.call(self) }
105
+ end
106
+
71
107
  class << self
72
108
  attr_reader :verb
73
109
 
@@ -135,7 +171,7 @@ module Gamefic
135
171
  # @param action [Gamefic::Entity]
136
172
  # @param command [Command]
137
173
  # @return [self, nil]
138
- def attempt actor, command
174
+ def attempt actor, command, with_callbacks = false
139
175
  return nil if command.verb != verb
140
176
  tokens = command.arguments
141
177
  result = []
@@ -153,7 +189,7 @@ module Gamefic
153
189
  result.push accepted.first
154
190
  end
155
191
  end
156
- new(actor, result)
192
+ new(actor, result, with_callbacks)
157
193
  end
158
194
 
159
195
  protected
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Gamefic
2
4
  class NotConclusionError < RuntimeError; end
3
5
 
@@ -37,7 +39,7 @@ module Gamefic
37
39
  end
38
40
 
39
41
  def syntaxes
40
- playbooks.map(&:syntaxes).flatten
42
+ playbooks.flat_map(&:syntaxes)
41
43
  end
42
44
 
43
45
  # An array of actions waiting to be performed.
@@ -84,28 +86,18 @@ module Gamefic
84
86
  end
85
87
 
86
88
  # Perform a command.
87
- # The command can be specified as a String or a verb with a list of
88
- # parameters. Either form should yield the same result, but the
89
- # verb/parameter form can yield better performance since it bypasses the
90
- # parser.
91
89
  #
92
- # The command will be executed immediately regardless of the entity's
93
- # state.
90
+ # The command's action will be executed immediately regardless of the
91
+ # entity's state.
94
92
  #
95
93
  # @example Send a command as a string
96
94
  # character.perform "take the key"
97
95
  #
98
- # @example Send a command as a verb with parameters
99
- # character.perform :take, @key
100
- #
101
- # @todo Modify this method so it only accepts a single command.
102
- # Verbs with parameters should use Active#execute instead.
103
- # It might be necessary to support command splats with a deprecation
104
- # warning until version 3.
105
- #
96
+ # @param command [String, Symbol]
106
97
  # @return [Gamefic::Action]
107
98
  def perform(*command)
108
99
  if command.length > 1
100
+ STDERR.puts "[WARN] #{caller[0]}: Passing a verb and arguments to #perform is deprecated. Use #execute instead."
109
101
  execute command.first, *command[1..-1]
110
102
  else
111
103
  dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
@@ -118,16 +110,18 @@ module Gamefic
118
110
  # This method executes the command exactly as #perform does, except it
119
111
  # buffers the resulting output instead of sending it to the user.
120
112
  #
121
- # @todo Modify this method so it only accepts a single command.
122
- # See Active#perform for more information.
123
- #
113
+ # @param command [String, Symbol]
124
114
  # @return [String] The output that resulted from performing the command.
125
115
  def quietly(*command)
126
- clear_buffer if buffer_stack == 0
127
- set_buffer_stack buffer_stack + 1
128
- self.perform *command
129
- set_buffer_stack buffer_stack - 1
130
- buffer
116
+ if command.length > 1
117
+ STDERR.puts "#{caller[0]}: Passing a verb and arguments to #quietly is deprecated. Use #execute instead"
118
+ execute command.first, *command[1..-1], quietly: true
119
+ else
120
+ dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
121
+ result = proceed quietly: true
122
+ dispatchers.pop
123
+ result
124
+ end
131
125
  end
132
126
 
133
127
  # Perform an action.
@@ -141,6 +135,9 @@ module Gamefic
141
135
  # @example
142
136
  # character.execute :take, @key
143
137
  #
138
+ # @param verb [Symbol]
139
+ # @param params [Array]
140
+ # @params quietly [Boolean]
144
141
  # @return [Gamefic::Action]
145
142
  def execute(verb, *params, quietly: false)
146
143
  dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
@@ -171,21 +168,15 @@ module Gamefic
171
168
  # end
172
169
  # end
173
170
  #
171
+ # @param quietly [Boolean] If true, return the action's output instead of appending it to #messages
172
+ # @return [String, nil]
174
173
  def proceed quietly: false
175
174
  return if dispatchers.empty?
176
175
  a = dispatchers.last.next
177
176
  return if a.nil?
178
- if quietly
179
- if buffer_stack == 0
180
- @buffer = ""
181
- end
182
- set_buffer_stack(buffer_stack + 1)
183
- end
177
+ prepare_buffer quietly
184
178
  a.execute
185
- if quietly
186
- set_buffer_stack(buffer_stack - 1)
187
- @buffer
188
- end
179
+ flush_buffer quietly
189
180
  end
190
181
 
191
182
  # Immediately start a new scene for the character.
@@ -251,8 +242,8 @@ module Gamefic
251
242
  # Track the entity's performance of a scene.
252
243
  #
253
244
  def entered scene
254
- klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
255
- entered_scenes.push klass unless entered_scenes.include?(klass)
245
+ klass = (scene.is_a?(Gamefic::Scene::Base) ? scene.class : scene)
246
+ entered_scenes.add klass
256
247
  end
257
248
 
258
249
  # Determine whether the entity has performed the specified scene.
@@ -265,9 +256,25 @@ module Gamefic
265
256
 
266
257
  private
267
258
 
268
- # @return [Array<Gamefic::Scene::Base>]
259
+ def prepare_buffer quietly
260
+ if quietly
261
+ if buffer_stack == 0
262
+ @buffer = ""
263
+ end
264
+ set_buffer_stack(buffer_stack + 1)
265
+ end
266
+ end
267
+
268
+ def flush_buffer quietly
269
+ if quietly
270
+ set_buffer_stack(buffer_stack - 1)
271
+ @buffer
272
+ end
273
+ end
274
+
275
+ # @return [Set<Gamefic::Scene::Base>]
269
276
  def entered_scenes
270
- @entered_scenes ||= []
277
+ @entered_scenes ||= Set.new
271
278
  end
272
279
 
273
280
  def buffer_stack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Array
2
4
  # Get a subset of the array that matches the arguments.
3
5
  # If the argument is a Class or Module, the elements must be of the type.
@@ -12,7 +14,7 @@ class Array
12
14
  #
13
15
  # @return [Array]
14
16
  def that_are(*cls)
15
- result = self.clone
17
+ result = clone
16
18
  cls.each do |c|
17
19
  _keep result, c, true
18
20
  end
@@ -24,7 +26,7 @@ class Array
24
26
  #
25
27
  # @return [Array]
26
28
  def that_are_not(*cls)
27
- result = self.clone
29
+ result = clone
28
30
  cls.each do |c|
29
31
  _keep result, c, false
30
32
  end
@@ -34,7 +36,7 @@ class Array
34
36
  # Pop a random element from the array.
35
37
  #
36
38
  def pop_sample
37
- delete_at(rand(self.length))
39
+ delete_at(rand(length))
38
40
  end
39
41
 
40
42
  # Get a string representation of the array that separates elements with
@@ -48,31 +50,32 @@ class Array
48
50
  # @param andSep [String] The separator for the last element
49
51
  # @param serial [Boolean] Use serial separators (e.g., serial commas)
50
52
  # @return [String]
51
- def join_and(sep = ', ', andSep = ' and ', serial = true)
52
- if self.length < 3
53
- self.join(andSep)
53
+ def join_and(sep = ', ', and_sep = ' and ', serial = true)
54
+ if length < 3
55
+ join(and_sep)
54
56
  else
55
57
  start = self[0..-2]
56
- start.join(sep) + "#{serial ? sep.strip : ''}#{andSep}#{self.last}"
58
+ start.join(sep) + "#{serial ? sep.strip : ''}#{and_sep}#{last}"
57
59
  end
58
60
  end
59
61
 
60
62
  # @see Array#join_and
61
63
  #
62
64
  # @return [String]
63
- def join_or(sep = ', ', orSep = ' or ', serial = true)
64
- join_and(sep, orSep, serial)
65
+ def join_or(sep = ', ', or_sep = ' or ', serial = true)
66
+ join_and(sep, or_sep, serial)
65
67
  end
66
68
 
67
69
  private
68
70
 
69
71
  def _keep(arr, cls, bool)
70
- if (cls.kind_of?(Class) or cls.kind_of?(Module))
72
+ case cls
73
+ when Class, Module
71
74
  arr.keep_if { |i| i.is_a?(cls) == bool }
72
- elsif cls.kind_of?(Symbol)
75
+ when Symbol
73
76
  arr.keep_if { |i| i.send(cls) == bool }
74
77
  else
75
- arr.keep_if {|i| (i == cls) == bool}
78
+ arr.keep_if { |i| (i == cls) == bool }
76
79
  end
77
80
  end
78
81
  end
@@ -9,6 +9,7 @@ module Gamefic
9
9
  @actor = actor
10
10
  @commands = commands
11
11
  @actions = actions
12
+ @started = false
12
13
  end
13
14
 
14
15
  def merge dispatcher
@@ -21,13 +22,9 @@ module Gamefic
21
22
  while instance.nil? && !@actions.empty?
22
23
  action = actions.shift
23
24
  commands.each do |cmd|
24
- instance = action.attempt(actor, cmd)
25
+ instance = action.attempt(actor, cmd, !@started)
25
26
  if instance
26
- unless instance.meta?
27
- actor.playbooks.reverse.each do |playbook|
28
- return nil unless validate_playbook(playbook, instance)
29
- end
30
- end
27
+ @started = true
31
28
  break
32
29
  end
33
30
  end
@@ -66,11 +63,5 @@ module Gamefic
66
63
 
67
64
  # @return [Array<Action>]
68
65
  attr_reader :actions
69
-
70
- private
71
-
72
- def validate_playbook playbook, action
73
- playbook.validators.all? { |v| v.call(actor, action.verb, action.parameters) != false }
74
- end
75
66
  end
76
67
  end
data/lib/gamefic/plot.rb CHANGED
@@ -34,6 +34,10 @@ module Gamefic
34
34
  @static = [self] + scene_classes + entities
35
35
  end
36
36
 
37
+ def plot
38
+ self
39
+ end
40
+
37
41
  # Get an Array of the Plot's current Syntaxes.
38
42
  #
39
43
  # @return [Array<Syntax>]
@@ -1,7 +1,7 @@
1
1
  module Gamefic
2
2
  module Query
3
3
  class Base
4
- NEST_REGEXP = / in | on | of | from | inside /
4
+ NEST_REGEXP = / in | on | of | from | inside | from inside /
5
5
 
6
6
  attr_reader :arguments
7
7
 
@@ -31,6 +31,9 @@ module Gamefic
31
31
  # Get a collection of objects that exist in the subject's context and
32
32
  # match the provided token. The result is provided as a Matches object.
33
33
  #
34
+ # @param subject [Entity]
35
+ # @param token [String]
36
+ # @param continued [Boolean]
34
37
  # @return [Gamefic::Query::Matches]
35
38
  def resolve(subject, token, continued: false)
36
39
  available = context_from(subject)
@@ -38,14 +41,13 @@ module Gamefic
38
41
  if continued
39
42
  return Matches.execute(available, token, continued: continued)
40
43
  elsif nested?(token)
41
- drill = denest(available, token)
42
- drill.keep_if{ |e| accept?(e) }
44
+ drill = denest(available, token).select { |e| accept?(e) }
43
45
  return Matches.new(drill, token, '') unless drill.length != 1
44
46
  return Matches.new([], '', token)
45
47
  end
46
- result = available.select{ |e| e.specified?(token) }
47
- result = available.select{ |e| e.specified?(token, fuzzy: true) } if result.empty?
48
- result.keep_if{ |e| accept? e }
48
+ result = available.select { |e| e.specified?(token) }
49
+ result = available.select { |e| e.specified?(token, fuzzy: true) } if result.empty?
50
+ result.keep_if { |e| accept? e }
49
51
  Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
50
52
  end
51
53
 
@@ -82,6 +84,7 @@ module Gamefic
82
84
 
83
85
  # Determine whether the specified entity passes the query's arguments.
84
86
  #
87
+ # @param [Entity]
85
88
  # @return [Boolean]
86
89
  def accept?(entity)
87
90
  result = true
@@ -106,6 +109,7 @@ module Gamefic
106
109
  # recursively append its children.
107
110
  # The result will NOT include the original entity itself.
108
111
  #
112
+ # @param [Entity]
109
113
  # @return [Array<Object>]
110
114
  def subquery_accessible entity
111
115
  return [] if entity.nil?
@@ -1,17 +1,38 @@
1
1
  module Gamefic
2
2
  module Query
3
3
  class External < Base
4
- def initialize objects, *args
4
+ # @param container [Plot, Subplot, Array]
5
+ def initialize container, *args
5
6
  super(*args)
6
- @objects = objects
7
+ @container = container
7
8
  end
8
9
 
9
10
  def context_from subject
10
- @objects
11
+ Set.new
12
+ .merge(container_entities)
13
+ .merge(container_subplots_for(@container, subject))
14
+ .to_a
11
15
  end
12
16
 
13
- def accept?(entity)
14
- @objects.include?(entity) && super(entity)
17
+ private
18
+
19
+ # @return [Array<Entity>]
20
+ def container_entities
21
+ if @container.is_a?(World::Entities)
22
+ @container.entities
23
+ elsif @container.is_a?(Enumerable)
24
+ @container
25
+ else
26
+ raise ArgumentError, "Unable to derive entities from #{@container}"
27
+ end
28
+ end
29
+
30
+ # @return [Array<Entity>]
31
+ def container_subplots_for container, subject
32
+ return [] unless container.is_a?(Plot::Host)
33
+ container.subplots_featuring(subject).flat_map do |subplot|
34
+ subplot.entities + container_subplots_for(subplot, subject)
35
+ end
15
36
  end
16
37
  end
17
38
  end
@@ -76,8 +76,9 @@ Gamefic::Scriptable.module_exec do
76
76
  instance = self
77
77
  theater ||= Object.new
78
78
  theater.instance_exec do
79
- define_singleton_method :method_missing do |symbol, *args, &block|
80
- instance.public_send :public_send, symbol, *args, &block
79
+ define_singleton_method :method_missing do |symbol, *args, **splat, &block|
80
+ result = instance.public_send :public_send, symbol, *args, **splat, &block
81
+ result
81
82
  end
82
83
  end
83
84
  theater.extend Gamefic::Serialize
@@ -1,7 +1,10 @@
1
1
  require 'gamefic/plot'
2
2
 
3
3
  module Gamefic
4
- class Subplot #< Container
4
+ # Subplots are disposable plots that run inside a parent plot. They can be
5
+ # started and concluded at any time during the parent plot's execution.
6
+ #
7
+ class Subplot
5
8
  include World
6
9
  include Scriptable
7
10
  include Gamefic::Serialize
@@ -19,7 +22,7 @@ module Gamefic
19
22
  @next_cue = next_cue
20
23
  @concluded = false
21
24
  @more = more
22
- configure more
25
+ configure **more
23
26
  run_scripts
24
27
  playbook.freeze
25
28
  self.introduce introduce unless introduce.nil?
@@ -97,7 +100,7 @@ module Gamefic
97
100
  # Subclasses can override this method to handle additional configuration
98
101
  # options.
99
102
  #
100
- def configure more
103
+ def configure **more
101
104
  end
102
105
  end
103
106
  end
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '2.2.2'
2
+ VERSION = '2.3.0'
3
3
  end
@@ -95,11 +95,19 @@ module Gamefic
95
95
  playbook.meta command, *queries, &proc
96
96
  end
97
97
 
98
- # Validate an order before a character can execute its command.
98
+ # Add a proc to be evaluated before a character executes an action.
99
99
  #
100
- # @yieldparam [Gamefic::Director::Order]
101
- def validate &block
102
- playbook.validate &block
100
+ # @yieldparam [Gamefic::Action]
101
+ def before_action &block
102
+ playbook.before_actions.push block
103
+ end
104
+ alias validate before_action
105
+
106
+ # Add a proc to be evaluated after a character executes an action.
107
+ #
108
+ # @yieldparam [Gamefic::Action]
109
+ def after_action &block
110
+ playbook.after_actions.push block
103
111
  end
104
112
 
105
113
  # Create an alternate Syntax for an Action.
@@ -10,19 +10,26 @@ module Gamefic
10
10
  # @return [Array<Gamefic::Syntax>]
11
11
  attr_reader :syntaxes
12
12
 
13
- # An array of defined validators.
13
+ # An array of blocks to execute before actions.
14
14
  #
15
15
  # @return [Array<Proc>]
16
- attr_reader :validators
16
+ attr_reader :before_actions
17
+
18
+ # An array of blocks to execute after actions.
19
+ #
20
+ # @return [Array<Proc>]
21
+ attr_reader :after_actions
17
22
 
18
23
  # @param commands [Hash]
19
24
  # @param syntaxes [Array<Syntax>, Set<Syntax>]
20
- # @param validators [Array]
21
- def initialize commands: {}, syntaxes: [], validators: []
25
+ # @param before_actions [Array]
26
+ # @param after_actions [Array]
27
+ def initialize commands: {}, syntaxes: [], before_actions: [], after_actions: []
22
28
  @commands = commands
23
29
  @syntax_set = syntaxes.to_set
24
30
  sort_syntaxes
25
- @validators = validators
31
+ @before_actions = before_actions
32
+ @after_actions = after_actions
26
33
  end
27
34
 
28
35
  # An array of available actions.
@@ -39,10 +46,19 @@ module Gamefic
39
46
  @commands.keys
40
47
  end
41
48
 
42
- # Add a block that determines whether an action can be executed.
49
+ # Add a proc to be evaluated before a character executes an action.
50
+ #
51
+ # @yieldparam [Gamefic::Action]
52
+ def before_action &block
53
+ @before_actions.push block
54
+ end
55
+ alias validate before_action
56
+
57
+ # Add a proc to be evaluated after a character executes an action.
43
58
  #
44
- def validate &block
45
- @validators.push block
59
+ # @yieldparam [Gamefic::Action]
60
+ def after_action &block
61
+ @after_actions.push block
46
62
  end
47
63
 
48
64
  # Get an Array of all Actions associated with the specified verb.
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.2.2
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-06 00:00:00.000000000 Z
11
+ date: 2023-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -149,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  - !ruby/object:Gem::Version
150
150
  version: '0'
151
151
  requirements: []
152
- rubygems_version: 3.1.6
152
+ rubygems_version: 3.3.7
153
153
  signing_key:
154
154
  specification_version: 4
155
155
  summary: Gamefic