gamefic 3.6.0 → 4.0.0

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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/CHANGELOG.md +19 -0
  4. data/Rakefile +1 -0
  5. data/gamefic.gemspec +1 -1
  6. data/lib/gamefic/action.rb +68 -54
  7. data/lib/gamefic/active/cue.rb +84 -6
  8. data/lib/gamefic/active/messaging.rb +8 -0
  9. data/lib/gamefic/active/narratives.rb +101 -0
  10. data/lib/gamefic/active.rb +80 -92
  11. data/lib/gamefic/binding.rb +44 -0
  12. data/lib/gamefic/chapter.rb +30 -46
  13. data/lib/gamefic/command.rb +22 -40
  14. data/lib/gamefic/core_ext/array.rb +7 -7
  15. data/lib/gamefic/core_ext/string.rb +2 -2
  16. data/lib/gamefic/describable.rb +13 -0
  17. data/lib/gamefic/dispatcher.rb +35 -55
  18. data/lib/gamefic/entity.rb +6 -5
  19. data/lib/gamefic/expression.rb +1 -11
  20. data/lib/gamefic/logging.rb +3 -10
  21. data/lib/gamefic/match.rb +23 -0
  22. data/lib/gamefic/messenger.rb +1 -1
  23. data/lib/gamefic/narrative.rb +38 -74
  24. data/lib/gamefic/narrator.rb +77 -0
  25. data/lib/gamefic/node.rb +40 -8
  26. data/lib/gamefic/order.rb +53 -0
  27. data/lib/gamefic/plot.rb +41 -59
  28. data/lib/gamefic/props/default.rb +5 -17
  29. data/lib/gamefic/props/multiple_choice.rb +5 -2
  30. data/lib/gamefic/props/multiple_partial.rb +16 -0
  31. data/lib/gamefic/props/output.rb +7 -5
  32. data/lib/gamefic/props/yes_or_no.rb +2 -2
  33. data/lib/gamefic/props.rb +1 -0
  34. data/lib/gamefic/proxy/attr.rb +11 -0
  35. data/lib/gamefic/proxy/base.rb +3 -15
  36. data/lib/gamefic/proxy/config.rb +2 -2
  37. data/lib/gamefic/proxy/pick.rb +3 -3
  38. data/lib/gamefic/proxy/pick_ex.rb +11 -0
  39. data/lib/gamefic/proxy.rb +3 -71
  40. data/lib/gamefic/query/ascendants.rb +16 -0
  41. data/lib/gamefic/query/base.rb +47 -73
  42. data/lib/gamefic/query/children.rb +15 -0
  43. data/lib/gamefic/query/descendants.rb +17 -0
  44. data/lib/gamefic/query/extended.rb +20 -0
  45. data/lib/gamefic/query/family.rb +27 -0
  46. data/lib/gamefic/query/global.rb +22 -0
  47. data/lib/gamefic/query/integer.rb +32 -0
  48. data/lib/gamefic/query/myself.rb +13 -0
  49. data/lib/gamefic/query/parent.rb +13 -0
  50. data/lib/gamefic/query/result.rb +1 -1
  51. data/lib/gamefic/query/siblings.rb +12 -0
  52. data/lib/gamefic/query/subqueries.rb +17 -0
  53. data/lib/gamefic/query/text.rb +8 -9
  54. data/lib/gamefic/query.rb +11 -3
  55. data/lib/gamefic/request.rb +60 -0
  56. data/lib/gamefic/response.rb +46 -72
  57. data/lib/gamefic/scanner/nesting.rb +6 -6
  58. data/lib/gamefic/scanner/result.rb +3 -0
  59. data/lib/gamefic/scanner/strict.rb +14 -4
  60. data/lib/gamefic/scanner.rb +11 -6
  61. data/lib/gamefic/scene/active_choice.rb +75 -0
  62. data/lib/gamefic/scene/activity.rb +7 -3
  63. data/lib/gamefic/scene/base.rb +123 -0
  64. data/lib/gamefic/scene/conclusion.rb +4 -1
  65. data/lib/gamefic/scene/multiple_choice.rb +14 -11
  66. data/lib/gamefic/scene/pause.rb +5 -1
  67. data/lib/gamefic/scene/yes_or_no.rb +9 -0
  68. data/lib/gamefic/scene.rb +2 -1
  69. data/lib/gamefic/scriptable/hooks.rb +161 -0
  70. data/lib/gamefic/scriptable/queries.rb +38 -29
  71. data/lib/gamefic/scriptable/responses.rb +70 -0
  72. data/lib/gamefic/scriptable/scenes.rb +88 -115
  73. data/lib/gamefic/scriptable/seeds.rb +69 -0
  74. data/lib/gamefic/scriptable/syntaxes.rb +29 -0
  75. data/lib/gamefic/scriptable.rb +14 -199
  76. data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
  77. data/lib/gamefic/scripting/hooks.rb +45 -0
  78. data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
  79. data/lib/gamefic/scripting/responses.rb +21 -0
  80. data/lib/gamefic/scripting/scenes.rb +57 -0
  81. data/lib/gamefic/scripting/seeds.rb +10 -0
  82. data/lib/gamefic/scripting/syntaxes.rb +13 -0
  83. data/lib/gamefic/scripting.rb +43 -0
  84. data/lib/gamefic/subplot.rb +11 -22
  85. data/lib/gamefic/syntax.rb +39 -24
  86. data/lib/gamefic/version.rb +1 -1
  87. data/lib/gamefic.rb +6 -7
  88. metadata +38 -41
  89. data/lib/gamefic/active/epic.rb +0 -74
  90. data/lib/gamefic/active/take.rb +0 -67
  91. data/lib/gamefic/block.rb +0 -28
  92. data/lib/gamefic/callback.rb +0 -16
  93. data/lib/gamefic/proxy/plot_pick.rb +0 -11
  94. data/lib/gamefic/query/abstract.rb +0 -12
  95. data/lib/gamefic/query/general.rb +0 -41
  96. data/lib/gamefic/query/scoped.rb +0 -27
  97. data/lib/gamefic/rulebook/calls.rb +0 -86
  98. data/lib/gamefic/rulebook/events.rb +0 -65
  99. data/lib/gamefic/rulebook/hooks.rb +0 -57
  100. data/lib/gamefic/rulebook/scenes.rb +0 -68
  101. data/lib/gamefic/rulebook.rb +0 -125
  102. data/lib/gamefic/scene/default.rb +0 -88
  103. data/lib/gamefic/scope/base.rb +0 -44
  104. data/lib/gamefic/scope/children.rb +0 -16
  105. data/lib/gamefic/scope/descendants.rb +0 -16
  106. data/lib/gamefic/scope/family.rb +0 -43
  107. data/lib/gamefic/scope/myself.rb +0 -13
  108. data/lib/gamefic/scope/parent.rb +0 -13
  109. data/lib/gamefic/scope/siblings.rb +0 -14
  110. data/lib/gamefic/scope.rb +0 -9
  111. data/lib/gamefic/scriptable/actions.rb +0 -137
  112. data/lib/gamefic/scriptable/events.rb +0 -71
  113. data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
  114. data/lib/gamefic/snapshot.rb +0 -44
  115. data/lib/gamefic/stage.rb +0 -51
  116. data/lib/gamefic/syntax/template.rb +0 -67
  117. data/lib/gamefic/vault.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5c61efc096ecbacdd43431dd3f0b755120650258a4a3e5d724692957ee45f53
4
- data.tar.gz: 499d740594a35e767c86fae7610a51dafa9ba647658af7b3ab46c61de4335501
3
+ metadata.gz: ab6b04620f4d61106e3d7a3321dfc1833f0a7cce7eeb0e509ca77bdc5388d7c0
4
+ data.tar.gz: d8d37b580f71e20870cbe4124a8af41a953448b4bd5296e8f6f73d5e8a0e5092
5
5
  SHA512:
6
- metadata.gz: dd430f5dfa970cacfe12915ff09284ef286b1116aa701ce5484472fdf880b68a5de91953182189c647e90c527653bde92db26016045ac5ea5304e660082979fe
7
- data.tar.gz: 1726fc542b40c55443bc9b452df85d94fe0831fa19bc73e40076cd6768e92bc71e504fd2da7998ef65b913c21f8b3ccb7bd4f04329186b88e71eb9dc675b0b69
6
+ metadata.gz: ac226d280d8e1a07ad2bc154ae1cbc49a4496a847986f4d8919aef89999386432122673b568c64e2012ef3b910652aa67a31545e59eb65633105854fd0228d4b
7
+ data.tar.gz: 2b271b4061e74ab43d4cd11e080261b595404f7add90ea017934e49c4d1beda47a44c3ed88b53d4ba57cc6a6602fabf5e330a18544e783b09c56129642f6f82a
data/.rubocop.yml CHANGED
@@ -11,6 +11,3 @@ Style/StringLiterals:
11
11
  Style/Documentation:
12
12
  Description: 'Document classes and non-namespace modules.'
13
13
  Enabled: false
14
-
15
- Style/MethodDefParentheses:
16
- Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 4.0.0
2
+ - Nuanced scans
3
+ - Command hooks
4
+ - Refactored queries
5
+ - Cue scenes by class or name
6
+ - Deprecate underscore verbs
7
+ - Move rules to Scriptable
8
+ - JIT Scriptable binding
9
+ - Sunset Rulebook
10
+ - Separate Scriptable and Scripting modules
11
+ - ActiveChoice scenes
12
+ - Narrator class runs narratives
13
+ - Use construct for static entities
14
+ - Parent relations
15
+ - Consolidate Syntax and Template
16
+ - Remove Snapshot module
17
+ - Track command activity
18
+ - MultiplePartial props
19
+
1
20
  ## 3.6.0 - October 6, 2024
2
21
  - Normalized arguments accept strings
3
22
  - Smarter picks and proxies
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bundler/setup'
3
4
  require 'bundler/gem_tasks'
4
5
  require 'rspec/core/rake_task'
5
6
  require 'opal/rspec/rake_task'
data/gamefic.gemspec CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency 'opal', '~> 1.7'
25
25
  s.add_development_dependency 'opal-rspec', '~> 1.0'
26
26
  s.add_development_dependency 'opal-sprockets', '~> 1.0'
27
- s.add_development_dependency 'rake', '~> 12.3', '>= 12.3'
27
+ s.add_development_dependency 'rake', '~> 13.2'
28
28
  s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
29
29
  s.add_development_dependency 'simplecov', '~> 0.14'
30
30
  end
@@ -1,91 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- # The handler for executing responses for a provided actor and array of
5
- # arguments. It's also responsible for executing before_action and
6
- # after_action hooks if necessary.
4
+ # The handler for executing a command response.
7
5
  #
8
6
  class Action
9
- include Logging
7
+ include Scriptable::Queries
10
8
 
11
- # Hooks are blocks of code that get executed before or after an actor
12
- # performs an action. A before action hook is capable of cancelling the
13
- # action's performance.
14
- #
15
- class Hook
16
- # @param [Array<Symbol>]
17
- attr_reader :verbs
18
-
19
- # @param [Callback]
20
- attr_reader :callback
21
-
22
- def initialize verbs, callback
23
- @verbs = verbs
24
- @callback = callback
25
- end
26
-
27
- def match?(input)
28
- verbs.empty? || verbs.include?(input)
29
- end
30
- end
31
-
32
- # @return [Active]
9
+ # @return [Actor]
33
10
  attr_reader :actor
34
11
 
35
- # @return [Array]
36
- attr_reader :arguments
37
-
38
12
  # @return [Response]
39
13
  attr_reader :response
40
14
 
41
- # @param actor [Active]
42
- # @param arguments [Array]
15
+ # @return [Array<Match>]
16
+ attr_reader :matches
17
+
18
+ # @return [String, nil]
19
+ attr_reader :input
20
+
21
+ # @param actor [Actor]
43
22
  # @param response [Response]
44
- def initialize actor, arguments, response
23
+ # @param matches [Array<Match>]
24
+ # @param input [String, nil]
25
+ def initialize(actor, response, matches, input = nil)
45
26
  @actor = actor
46
- @arguments = arguments
47
27
  @response = response
28
+ @matches = matches
29
+ @input = input
48
30
  end
49
31
 
50
- # @return [self]
51
- def execute
52
- return self if cancelled? || executed?
32
+ def verb
33
+ response.verb
34
+ end
53
35
 
54
- Gamefic.logger.info "Executing #{([verb] + [arguments]).flatten.map(&:inspect).join(', ')}"
55
- @executed = true
56
- response.execute actor, *arguments
36
+ def command
37
+ @command ||= Command.new(response.verb, matches.map(&:argument), response.meta?, input)
38
+ end
39
+
40
+ def queries
41
+ response.queries
42
+ end
43
+
44
+ def arguments
45
+ matches.map(&:argument)
46
+ end
47
+
48
+ def execute
49
+ response.execute(actor, *arguments)
57
50
  self
58
51
  end
59
52
 
60
- # True if the response has been executed. False typically means that the
61
- # #execute method has not been called or the action was cancelled in a
62
- # before_action hook.
53
+ # The total substantiality of the action, based on how many of the
54
+ # arguments are concrete entities and whether the action has a verb.
63
55
  #
64
- def executed?
65
- @executed ||= false
56
+ def substantiality
57
+ arguments.that_are(Entity).length + (verb ? 1 : 0)
66
58
  end
67
59
 
68
- # Cancel an action. This method can be called in an action hook to
69
- # prevent subsequent hooks and/or the action itself from being executed.
60
+ # The total strictness of all the matches.
70
61
  #
71
- def cancel
72
- @cancelled = true
62
+ # The higher the strictness, the more precisely the tokens from the user
63
+ # input match the arguments. For example, if the user is interacting with a
64
+ # pencil, the command TAKE PENCIL is stricter than TAKE PEN.
65
+ #
66
+ # @return [Integer]
67
+ def strictness
68
+ matches.sum(0, &:strictness)
73
69
  end
74
70
 
75
- def cancelled?
76
- @cancelled ||= false
71
+ # The precision of the response.
72
+ #
73
+ # @return [Integer]
74
+ def precision
75
+ response.precision
77
76
  end
78
77
 
79
- def verb
80
- response.verb
78
+ def valid?
79
+ response.accept?(actor, command)
81
80
  end
82
81
 
83
- def narrative
84
- response.narrative
82
+ def invalid?
83
+ !valid?
85
84
  end
86
85
 
87
86
  def meta?
88
87
  response.meta?
89
88
  end
89
+
90
+ # Sort an array of actions in the order in which a Dispatcher should
91
+ # attempt to execute them.
92
+ #
93
+ # Order is determined by the actions' substantiality, strictness, and
94
+ # precision. In the event of a tie, the most recently defined action has
95
+ # higher priority.
96
+ #
97
+ # @param actions [Array<Action>]
98
+ # @return [Array<Action>]
99
+ def self.sort(actions)
100
+ actions.sort_by.with_index do |action, idx|
101
+ [-action.substantiality, -action.strictness, -action.precision, idx]
102
+ end
103
+ end
90
104
  end
91
105
  end
@@ -2,24 +2,102 @@
2
2
 
3
3
  module Gamefic
4
4
  module Active
5
- # The data that actors use to configure a Take.
5
+ # The object that actors use to perform a scene.
6
6
  #
7
7
  class Cue
8
- # @return [Symbol]
9
- attr_reader :scene
8
+ # @return [Actor]
9
+ attr_reader :actor
10
+
11
+ # @return [Class<Scene::Base>, Symbol]
12
+ attr_reader :key
13
+
14
+ # @return [Narrative]
15
+ attr_reader :narrative
10
16
 
11
17
  # @return [Hash]
12
18
  attr_reader :context
13
19
 
14
- # @param scene [Symbol]
15
- def initialize scene, **context
16
- @scene = scene
20
+ # @return [Props::Default, nil]
21
+ attr_reader :props
22
+
23
+ # @param actor [Actor]
24
+ # @param key [Class<Scene::Base>, Symbol]
25
+ # @param narrative [Narrative]
26
+ def initialize actor, key, narrative, **context
27
+ @actor = actor
28
+ @key = key
29
+ @narrative = narrative
17
30
  @context = context
18
31
  end
19
32
 
33
+ # @return [void]
34
+ def start
35
+ @props = scene.start
36
+ prepare_output
37
+ actor.rotate_cue
38
+ end
39
+
40
+ # @return [void]
41
+ def finish
42
+ props&.enter(actor.queue.shift&.strip)
43
+ scene.finish
44
+ end
45
+
46
+ # @return [Props::Output]
47
+ def output
48
+ props&.output.clone.freeze || Props::Output::EMPTY
49
+ end
50
+
51
+ # @return [Cue]
52
+ def restart
53
+ Cue.new(actor, key, narrative, **context)
54
+ end
55
+
56
+ def type
57
+ scene&.type
58
+ end
59
+
20
60
  def to_s
21
61
  scene.to_s
22
62
  end
63
+
64
+ # @return [void]
65
+ def prepare
66
+ props.output.merge!({
67
+ scene: scene.to_hash,
68
+ prompt: props.prompt,
69
+ messages: actor.messages,
70
+ queue: actor.queue
71
+ })
72
+ actor.narratives.player_output_blocks.each { |block| block.call actor, props.output }
73
+ end
74
+
75
+ # @return [Scene::Base]
76
+ def scene
77
+ # @note This method always returns a new instance. Scenes identified
78
+ # by symbolic keys can be instances of anonymous classes that cannot
79
+ # be serialized, so memoizing them breaks snapshots.
80
+ narrative&.prepare(key, actor, props, **context) ||
81
+ try_unblocked_class ||
82
+ raise("Failed to cue #{key.inspect} in #{narrative.inspect}")
83
+ end
84
+
85
+ private
86
+
87
+ # @return [Scene::Base]
88
+ def try_unblocked_class
89
+ return unless key.is_a?(Class) && key <= Scene::Base
90
+
91
+ Gamefic.logger.warn "Cueing scene #{key} without narrative" unless narrative
92
+ key.new(actor, narrative, props, **context)
93
+ end
94
+
95
+ # @return [void]
96
+ def prepare_output
97
+ scene
98
+ props.output.last_input = actor.last_cue&.props&.input
99
+ props.output.last_prompt = actor.last_cue&.props&.prompt
100
+ end
23
101
  end
24
102
  end
25
103
  end
@@ -30,14 +30,22 @@ module Gamefic
30
30
  messenger.stream message
31
31
  end
32
32
 
33
+ # @return [String]
33
34
  def messages
34
35
  messenger.messages
35
36
  end
36
37
 
38
+ # Create a temporary buffer while yielding the given block and return the
39
+ # buffered text.
40
+ #
41
+ # @return [String]
37
42
  def buffer &block
38
43
  messenger.buffer(&block)
39
44
  end
40
45
 
46
+ # Clear the current buffer.
47
+ #
48
+ # @return [String] The buffer's messages
41
49
  def flush
42
50
  messenger.flush
43
51
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Active
5
+ # A narrative container for active entities.
6
+ #
7
+ class Narratives
8
+ include Enumerable
9
+
10
+ # @param narrative [Narrative]
11
+ # @return [self]
12
+ def add(narrative)
13
+ narrative_set.add(narrative)
14
+ self
15
+ end
16
+
17
+ # @param narrative [Narrative]
18
+ # @return [self]
19
+ def delete(narrative)
20
+ narrative_set.delete(narrative)
21
+ self
22
+ end
23
+
24
+ def empty?
25
+ narrative_set.empty?
26
+ end
27
+
28
+ # @return [Integer]
29
+ def length
30
+ narrative_set.length
31
+ end
32
+
33
+ def one?
34
+ narrative_set.one?
35
+ end
36
+
37
+ # @return [Array<Response>]
38
+ def responses
39
+ narrative_set.flat_map(&:responses)
40
+ end
41
+
42
+ # @return [Array<Response>]
43
+ def responses_for(*verbs)
44
+ narrative_set.flat_map { |narr| narr.responses_for(*verbs) }
45
+ end
46
+
47
+ # @return [Array<Syntax>]
48
+ def syntaxes
49
+ narrative_set.flat_map(&:syntaxes)
50
+ end
51
+
52
+ # True if the specified verb is understood by any of the narratives.
53
+ #
54
+ # @param verb [String, Symbol]
55
+ def understand?(verb)
56
+ verb ? narrative_set.flat_map(&:synonyms).include?(verb.to_sym) : false
57
+ end
58
+
59
+ # @return [Array<Binding>]
60
+ def before_commands
61
+ narrative_set.flat_map(&:before_commands)
62
+ end
63
+
64
+ # @return [Array<Binding>]
65
+ def after_commands
66
+ narrative_set.flat_map(&:after_commands)
67
+ end
68
+
69
+ def each(&block)
70
+ narrative_set.each(&block)
71
+ end
72
+
73
+ # @return [Array<Narrative>]
74
+ def that_are(*args)
75
+ narrative_set.to_a.that_are(*args)
76
+ end
77
+
78
+ # @return [Array<Narrative>]
79
+ def that_are_not(*args)
80
+ narrative_set.to_a.that_are_not(*args)
81
+ end
82
+
83
+ # @return [Array<Entity>]
84
+ def entities
85
+ narrative_set.flat_map(&:entities)
86
+ end
87
+
88
+ # @return [Array<Binding>]
89
+ def player_output_blocks
90
+ narrative_set.flat_map(&:player_output_blocks).uniq(&:code)
91
+ end
92
+
93
+ private
94
+
95
+ # @return [Set<Narrative>]
96
+ def narrative_set
97
+ @narrative_set ||= Set.new
98
+ end
99
+ end
100
+ end
101
+ end