gamefic 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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