gamefic 2.4.0 → 3.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -40
  3. data/.rspec-opal +2 -0
  4. data/.solargraph.yml +20 -3
  5. data/CHANGELOG.md +9 -0
  6. data/Rakefile +11 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/gamefic.gemspec +5 -2
  10. data/lib/gamefic/action.rb +52 -183
  11. data/lib/gamefic/active/cue.rb +25 -0
  12. data/lib/gamefic/active/epic.rb +68 -0
  13. data/lib/gamefic/active/messaging.rb +43 -0
  14. data/lib/gamefic/active/take.rb +69 -0
  15. data/lib/gamefic/active.rb +95 -192
  16. data/lib/gamefic/actor.rb +2 -0
  17. data/lib/gamefic/block.rb +28 -0
  18. data/lib/gamefic/command.rb +16 -6
  19. data/lib/gamefic/core_ext/array.rb +4 -4
  20. data/lib/gamefic/core_ext/string.rb +10 -5
  21. data/lib/gamefic/describable.rb +39 -65
  22. data/lib/gamefic/dispatcher.rb +63 -32
  23. data/lib/gamefic/entity.rb +44 -19
  24. data/lib/gamefic/logging.rb +32 -0
  25. data/lib/gamefic/messenger.rb +66 -0
  26. data/lib/gamefic/narrative.rb +104 -0
  27. data/lib/gamefic/node.rb +44 -53
  28. data/lib/gamefic/plot.rb +60 -93
  29. data/lib/gamefic/props/default.rb +41 -0
  30. data/lib/gamefic/props/multiple_choice.rb +65 -0
  31. data/lib/gamefic/props/pause.rb +11 -0
  32. data/lib/gamefic/props/yes_or_no.rb +21 -0
  33. data/lib/gamefic/props.rb +10 -0
  34. data/lib/gamefic/query/base.rb +45 -126
  35. data/lib/gamefic/query/general.rb +46 -0
  36. data/lib/gamefic/query/result.rb +20 -0
  37. data/lib/gamefic/query/scoped.rb +41 -0
  38. data/lib/gamefic/query/text.rb +30 -31
  39. data/lib/gamefic/query.rb +7 -15
  40. data/lib/gamefic/response.rb +118 -0
  41. data/lib/gamefic/rulebook/calls.rb +90 -0
  42. data/lib/gamefic/rulebook/events.rb +79 -0
  43. data/lib/gamefic/rulebook/hooks.rb +57 -0
  44. data/lib/gamefic/rulebook/scenes.rb +68 -0
  45. data/lib/gamefic/rulebook.rb +139 -0
  46. data/lib/gamefic/scanner.rb +103 -0
  47. data/lib/gamefic/scene/activity.rb +9 -17
  48. data/lib/gamefic/scene/conclusion.rb +6 -5
  49. data/lib/gamefic/scene/default.rb +88 -0
  50. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  51. data/lib/gamefic/scene/pause.rb +9 -13
  52. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  53. data/lib/gamefic/scene.rb +11 -7
  54. data/lib/gamefic/scope/base.rb +44 -0
  55. data/lib/gamefic/scope/children.rb +16 -0
  56. data/lib/gamefic/scope/family.rb +20 -0
  57. data/lib/gamefic/scope/myself.rb +13 -0
  58. data/lib/gamefic/scope/parent.rb +13 -0
  59. data/lib/gamefic/scope/siblings.rb +14 -0
  60. data/lib/gamefic/scope.rb +8 -0
  61. data/lib/gamefic/scriptable/actions.rb +156 -0
  62. data/lib/gamefic/scriptable/entities.rb +76 -0
  63. data/lib/gamefic/scriptable/events.rb +65 -0
  64. data/lib/gamefic/scriptable/proxy.rb +55 -0
  65. data/lib/gamefic/scriptable/queries.rb +73 -0
  66. data/lib/gamefic/scriptable/scenes.rb +162 -0
  67. data/lib/gamefic/scriptable.rb +167 -73
  68. data/lib/gamefic/snapshot.rb +36 -0
  69. data/lib/gamefic/stage.rb +51 -0
  70. data/lib/gamefic/subplot.rb +51 -79
  71. data/lib/gamefic/syntax/template.rb +67 -0
  72. data/lib/gamefic/syntax.rb +102 -83
  73. data/lib/gamefic/vault.rb +50 -0
  74. data/lib/gamefic/version.rb +1 -1
  75. data/lib/gamefic.rb +26 -15
  76. data/spec-opal/spec_helper.rb +24 -0
  77. metadata +91 -29
  78. data/lib/gamefic/element.rb +0 -46
  79. data/lib/gamefic/keywords.rb +0 -52
  80. data/lib/gamefic/messaging.rb +0 -43
  81. data/lib/gamefic/plot/darkroom.rb +0 -120
  82. data/lib/gamefic/plot/host.rb +0 -42
  83. data/lib/gamefic/plot/snapshot.rb +0 -27
  84. data/lib/gamefic/query/children.rb +0 -9
  85. data/lib/gamefic/query/descendants.rb +0 -15
  86. data/lib/gamefic/query/external.rb +0 -39
  87. data/lib/gamefic/query/family.rb +0 -18
  88. data/lib/gamefic/query/itself.rb +0 -13
  89. data/lib/gamefic/query/matches.rb +0 -75
  90. data/lib/gamefic/query/parent.rb +0 -9
  91. data/lib/gamefic/query/siblings.rb +0 -13
  92. data/lib/gamefic/query/tree.rb +0 -17
  93. data/lib/gamefic/scene/base.rb +0 -142
  94. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  95. data/lib/gamefic/serialize.rb +0 -196
  96. data/lib/gamefic/world/callbacks.rb +0 -135
  97. data/lib/gamefic/world/commands.rb +0 -181
  98. data/lib/gamefic/world/entities.rb +0 -98
  99. data/lib/gamefic/world/playbook.rb +0 -233
  100. data/lib/gamefic/world/players.rb +0 -37
  101. data/lib/gamefic/world/scenes.rb +0 -228
  102. data/lib/gamefic/world.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2288262e86ac4a5cf259b3a37e80ba56a3a4ed334b8842770109e1930799585d
4
- data.tar.gz: 542aaff9c3f4d0aae1a4471fba5d52ff35b569971c861af006ddc78f0d1cc568
3
+ metadata.gz: 6647dc3319080277826fecd19b8466cfbe82a42f4f4c68aeb89fa74bd0de4319
4
+ data.tar.gz: 0dc0680cb42b981d6e93c12d03e95b4ffcce0a98acb0678595723ab2d5ae7db9
5
5
  SHA512:
6
- metadata.gz: 6a89c7fb5978047517827e3cc2de5f172da5e6905a7e7efe0725763b4ce01d56725df46a443b903906effd962cebdbd238d81719b315eb26f3b8bf289d337089
7
- data.tar.gz: 9976d0c58957309ba605e645a8c7e214d798c12e114b71e5d5fa2bb4b6d39878537612e670da6e42168dfd31ee5fce7304eee9983d1177d878262af630e6e582
6
+ metadata.gz: 1ec19185367b63b0ebe352e620af403e0aef00bdd7afac7135801551568ef5c35c923b45092cb4d781cd8458bba71776b042efe9837a0e9d2f144141b6b8d606
7
+ data.tar.gz: 300d2c30b05993ee4689f14ce8daeeec3585bfdf1cd7636ec6a931bfc568e8227a97771fdf6d20ec859080f3792b914a29ba1fc6a7246c9f9ef9eb563d81920c
@@ -1,40 +1,41 @@
1
- # This workflow uses actions that are not certified by GitHub.
2
- # They are provided by a third-party and are governed by
3
- # separate terms of service, privacy policy, and support
4
- # documentation.
5
- # This workflow will download a prebuilt Ruby version, install dependencies and run tests with rspec.
6
- # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
-
8
- name: RSpec
9
-
10
- on:
11
- push:
12
- branches: [ master ]
13
- pull_request:
14
- branches: [ master ]
15
-
16
- permissions:
17
- contents: read
18
-
19
- jobs:
20
- test:
21
-
22
- runs-on: ubuntu-latest
23
- strategy:
24
- matrix:
25
- ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1']
26
-
27
- steps:
28
- - uses: actions/checkout@v3
29
- - name: Set up Ruby
30
- # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
31
- # change this to (see https://github.com/ruby/setup-ruby#versioning):
32
- uses: ruby/setup-ruby@v1
33
- # uses: ruby/setup-ruby@2b019609e2b0f1ea1a2bc8ca11cb82ab46ada124
34
- with:
35
- ruby-version: ${{ matrix.ruby-version }}
36
- bundler-cache: false
37
- - name: Install dependencies
38
- run: bundle install
39
- - name: Run tests
40
- run: bundle exec rspec
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with rspec.
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: RSpec
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ ruby-version: ['2.7', '3.0', '3.1']
26
+
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ - name: Set up Ruby
30
+ uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: false
34
+ - name: Set up Node
35
+ uses: actions/setup-node@v3
36
+ - name: Install dependencies
37
+ run: bundle install
38
+ - name: Run Ruby tests
39
+ run: bundle exec rspec
40
+ - name: Run Opal tests
41
+ run: bundle exec rake opal
data/.rspec-opal ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.solargraph.yml CHANGED
@@ -1,5 +1,22 @@
1
+ ---
1
2
  include:
2
- - lib/**/*.rb
3
+ - "**/*.rb"
3
4
  exclude:
4
- - spec/**/*
5
- - test/**/*
5
+ - spec/**/*
6
+ - test/**/*
7
+ - vendor/**/*
8
+ - ".bundle/**/*"
9
+ require: []
10
+ domains: []
11
+ reporters:
12
+ - rubocop
13
+ - require_not_found
14
+ formatter:
15
+ rubocop:
16
+ cops: safe
17
+ except: []
18
+ only: []
19
+ extra_args: []
20
+ require_paths: []
21
+ plugins: []
22
+ max_files: 5000
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 3.0.0
2
+ - Instantiate subplots from snapshots
3
+ - Split Action into Response and Action
4
+ - Logging
5
+ - Remove deprecated Active#perform behavior
6
+ - Snapshots use single static index
7
+ - Hydration improvements
8
+ - Snapshot metadata validation
9
+
1
10
  ## 2.4.0 - February 11, 2023
2
11
  - Fix arity of delegated methods in scripts
3
12
  - Action hooks accept verb filters
data/Rakefile CHANGED
@@ -1,5 +1,9 @@
1
- require 'rake'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
2
5
  require 'rspec/core/rake_task'
6
+ require 'opal/rspec/rake_task'
3
7
 
4
8
  RSpec::Core::RakeTask.new(:spec) do |t|
5
9
  t.pattern = Dir.glob('spec/**/*_spec.rb')
@@ -8,3 +12,9 @@ RSpec::Core::RakeTask.new(:spec) do |t|
8
12
  #t.rcov = true
9
13
  end
10
14
  task :default => :spec
15
+
16
+ Opal::RSpec::RakeTask.new(:opal) do |_, config|
17
+ Opal.append_path File.expand_path('../lib', __FILE__)
18
+ config.pattern = 'spec/**/*_spec.rb'
19
+ config.requires = ['spec_helper']
20
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gamefic-sdk"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/gamefic.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.description = "An adventure game and interactive fiction framework"
12
12
  s.authors = ["Fred Snyder"]
13
13
  s.email = 'fsnyder@gamefic.com'
14
- s.homepage = 'http://gamefic.com'
14
+ s.homepage = 'https://gamefic.com'
15
15
  s.license = 'MIT'
16
16
 
17
17
  s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
@@ -19,8 +19,11 @@ Gem::Specification.new do |s|
19
19
  end
20
20
  s.require_paths = ['lib']
21
21
 
22
- s.required_ruby_version = '>= 2.3.0'
22
+ s.required_ruby_version = '>= 2.7.0'
23
23
 
24
+ s.add_development_dependency 'opal', '~> 1.7'
25
+ s.add_development_dependency 'opal-rspec', '~> 1.0'
26
+ s.add_development_dependency 'opal-sprockets', '~> 1.0'
24
27
  s.add_development_dependency 'rake', '~> 12.3', '>= 12.3'
25
28
  s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
26
29
  s.add_development_dependency 'simplecov', '~> 0.14'
@@ -1,213 +1,82 @@
1
+ # frozen_string_literal: true
2
+
1
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.
7
+ #
2
8
  class Action
3
- # @return [Gamefic::Actor]
4
- attr_reader :actor
9
+ include Logging
5
10
 
6
- # An array of objects on which the action will operate, e.g., an entity
7
- # that is a direct object of a command.
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.
8
14
  #
9
- # @return [Array<Object>]
15
+ class Hook
16
+ attr_reader :verbs
17
+
18
+ attr_reader :block
19
+
20
+ def initialize *verbs, &block
21
+ @verbs = verbs
22
+ @block = block
23
+ end
24
+
25
+ def match?(input)
26
+ verbs.empty? || verbs.include?(input)
27
+ end
28
+ end
29
+
30
+ # @return [Active]
31
+ attr_reader :actor
32
+
33
+ # @return [Array]
10
34
  attr_reader :arguments
11
- alias parameters arguments
12
35
 
13
- # @param actor [Gamefic::Actor]
14
- # @param arguments [Array<Object>]
15
- def initialize actor, arguments, with_callbacks = false
36
+ # @return [Response]
37
+ attr_reader :response
38
+
39
+ # @param actor [Active]
40
+ # @param arguments [Array]
41
+ # @param response [Response]
42
+ def initialize actor, arguments, response
16
43
  @actor = actor
17
44
  @arguments = arguments
18
- @executed = false
19
- @with_callbacks = with_callbacks
45
+ @response = response
20
46
  end
21
47
 
22
- # Perform the action.
23
- #
24
48
  def execute
25
- return if @cancelled
26
- run_before_actions
27
- return if @cancelled
28
- self.class.executor.call(@actor, *arguments) unless self.class.executor.nil?
49
+ return if cancelled?
50
+
29
51
  @executed = true
30
- run_after_actions
52
+ response.execute actor, *arguments
31
53
  end
32
54
 
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
- # Cancelling an action in an after_action hook has no effect.
55
+ # True if the response has been executed. False typically means that the
56
+ # #execute method has not been called or the action was cancelled in a
57
+ # before_action hook.
36
58
  #
37
- def cancel
38
- # @todo Emit a warning for attempts to cancel an action after it's been
39
- # executed
40
- @cancelled = true
59
+ def executed?
60
+ @executed ||= false
41
61
  end
42
62
 
43
- # True if the #execute method has been called for this action.
63
+ # Cancel an action. This method can be called in an action hook to
64
+ # prevent subsequent hooks and/or the action itself from being executed.
44
65
  #
45
- # @return [Boolean]
46
- def executed?
47
- @executed
66
+ def cancel
67
+ @cancelled = true
48
68
  end
49
69
 
50
70
  def cancelled?
51
- !@executed && @cancelled
71
+ @cancelled ||= false
52
72
  end
53
73
 
54
- # The verb associated with this action.
55
- #
56
- # @return [Symbol] The symbol representing the verb
57
74
  def verb
58
- self.class.verb
59
- end
60
-
61
- def signature
62
- self.class.signature
63
- end
64
-
65
- def rank
66
- self.class.rank
75
+ response.verb
67
76
  end
68
77
 
69
- # True if the action is flagged as meta.
70
- #
71
- # @return [Boolean]
72
78
  def meta?
73
- self.class.meta?
74
- end
75
-
76
- # @param verb [Symbol]
77
- # @param queries [Array<Gamefic::Query::Base>]
78
- # @param meta [Boolean]
79
- # @return [Class<Action>]
80
- def self.subclass verb, *queries, meta: false, &block
81
- act = Class.new(self) do
82
- self.verb = verb
83
- self.meta = meta
84
- queries.each do |q|
85
- add_query q
86
- end
87
- on_execute &block
88
- end
89
- if !block.nil? && act.queries.length + 1 != block.arity && block.arity > 0
90
- raise ArgumentError.new("Number of parameters is not compatible with proc arguments")
91
- end
92
- act
93
- end
94
-
95
- private
96
-
97
- def run_before_actions
98
- return unless @with_callbacks
99
- @actor.playbooks
100
- .flat_map(&:before_actions)
101
- .each do |hook|
102
- next unless hook.verb.nil? || hook.verb == verb
103
- hook.block.call(self)
104
- break if @cancelled
105
- end
106
- end
107
-
108
- def run_after_actions
109
- return unless @with_callbacks
110
- @actor.playbooks
111
- .flat_map(&:after_actions)
112
- .each do |hook|
113
- next unless hook.verb.nil? || hook.verb == verb
114
- hook.block.call(self)
115
- end
116
- end
117
-
118
- class << self
119
- attr_reader :verb
120
-
121
- # The proc to call when the action is executed
122
- #
123
- # @return [Proc]
124
- attr_reader :executor
125
-
126
- def meta?
127
- @meta ||= false
128
- end
129
-
130
- def add_query q
131
- @specificity = nil
132
- queries.push q
133
- end
134
-
135
- def queries
136
- @queries ||= []
137
- end
138
-
139
- def on_execute &block
140
- @executor = block
141
- end
142
-
143
- def signature
144
- # @todo This is clearly unfinished
145
- "#{verb} #{queries.map {|m| m.signature}.join(', ')}"
146
- end
147
-
148
- # True if this action is not intended to be performed directly by a
149
- # character.
150
- # If the action is hidden, users should not be able to perform it with a
151
- # direct command. By default, any action whose verb starts with an
152
- # underscore is hidden.
153
- #
154
- # @return [Boolean]
155
- def hidden?
156
- verb.to_s.start_with?('_')
157
- end
158
-
159
- # @return [Integer]
160
- def rank
161
- if @rank.nil?
162
- @rank = 0
163
- queries.each do |q|
164
- @rank += (q.rank + 1)
165
- end
166
- @rank -= 1000 if verb.nil?
167
- end
168
- @rank
169
- end
170
-
171
- def valid? actor, objects
172
- return false if objects.length != queries.length
173
- queries.each_with_index do |p, i|
174
- return false unless p.include?(actor, objects[i])
175
- end
176
- true
177
- end
178
-
179
- # Return an instance of this Action if the actor can execute it with the
180
- # provided tokens, or nil if the tokens are invalid.
181
- #
182
- # @param action [Gamefic::Entity]
183
- # @param command [Command]
184
- # @return [self, nil]
185
- def attempt actor, command, with_callbacks = false
186
- return nil if command.verb != verb
187
- tokens = command.arguments
188
- result = []
189
- matches = Gamefic::Query::Matches.new([], '', '')
190
- queries.each_with_index do |p, i|
191
- return nil if tokens[i].nil? && matches.remaining == ''
192
- matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
193
- return nil if matches.objects.empty?
194
- accepted = matches.objects.select { |o| p.accept?(o) }
195
- return nil if accepted.empty?
196
- if p.ambiguous?
197
- result.push accepted
198
- else
199
- return nil if accepted.length != 1
200
- result.push accepted.first
201
- end
202
- end
203
- new(actor, result, with_callbacks)
204
- end
205
-
206
- protected
207
-
208
- attr_writer :verb
209
-
210
- attr_writer :meta
79
+ response.meta?
211
80
  end
212
81
  end
213
82
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Active
5
+ # The data that actors use to configure a Take.
6
+ #
7
+ class Cue
8
+ # @return [Symbol]
9
+ attr_reader :scene
10
+
11
+ # @return [Hash]
12
+ attr_reader :context
13
+
14
+ # @param scene [Symbol]
15
+ def initialize scene, **context
16
+ @scene = scene
17
+ @context = context
18
+ end
19
+
20
+ def to_s
21
+ scene.to_s
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Active
5
+ # A collection of narratives.
6
+ #
7
+ class Epic
8
+ include Logging
9
+
10
+ # @return [Set<Narrative>]
11
+ attr_reader :narratives
12
+
13
+ def initialize
14
+ @narratives = Set.new
15
+ end
16
+
17
+ # @param narrative [Narrative]
18
+ def add narrative
19
+ narratives.add narrative
20
+ end
21
+
22
+ # @param narrative [Narrative]
23
+ def delete narrative
24
+ narratives.delete narrative
25
+ end
26
+
27
+ # @return [Array<Rulebook>]
28
+ def rulebooks
29
+ narratives.map(&:rulebook)
30
+ end
31
+
32
+ # @return [Array<Symbol>]
33
+ def verbs
34
+ rulebooks.flat_map(&:verbs).uniq
35
+ end
36
+
37
+ # @return [Array<Symbol>]
38
+ def synonyms
39
+ rulebooks.flat_map(&:synonyms).uniq
40
+ end
41
+
42
+ def empty?
43
+ narratives.empty?
44
+ end
45
+
46
+ def one?
47
+ narratives.one?
48
+ end
49
+
50
+ # @param name [Symbol]
51
+ def conclusion? name
52
+ select_scene(name).conclusion?
53
+ end
54
+
55
+ # @param name [Symbol]
56
+ # @return [Scene]
57
+ def select_scene name
58
+ scenes = rulebooks.map { |rlbk| rlbk.scenes[name] }
59
+ .compact
60
+ raise ArgumentError, "Scene named `#{name}` does not exist" if scenes.empty?
61
+
62
+ logger.warn "Found #{scenes.length} scenes named `#{name}`" unless scenes.one?
63
+
64
+ scenes.last
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ module Gamefic
2
+ module Active
3
+ # A module for active entities that provides a default Messenger with
4
+ # a few shortcuts.
5
+ #
6
+ module Messaging
7
+ def messenger
8
+ @messenger ||= Messenger.new
9
+ end
10
+
11
+ # Send a message to the entity.
12
+ #
13
+ # This method will automatically wrap the message in HTML paragraphs.
14
+ # To send a message without paragraph formatting, use #stream instead.
15
+ #
16
+ # @param message [String]
17
+ def tell(message)
18
+ messenger.tell message
19
+ end
20
+
21
+ # Send a message to the entity as raw text.
22
+ #
23
+ # Unlike #tell, this method will not wrap the message in HTML paragraphs.
24
+ #
25
+ # @param message [String]
26
+ def stream(message)
27
+ messenger.stream message
28
+ end
29
+
30
+ def messages
31
+ messenger.messages
32
+ end
33
+
34
+ def buffer &block
35
+ messenger.buffer(&block)
36
+ end
37
+
38
+ def flush
39
+ messenger.flush
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Active
5
+ # The combination of an actor and a scene to be performed in a plot turn.
6
+ #
7
+ class Take
8
+ # @return [Active]
9
+ attr_reader :actor
10
+
11
+ # @return [Active::Cue]
12
+ attr_reader :cue
13
+
14
+ # @return [Scene::Default]
15
+ attr_reader :scene
16
+
17
+ # @param actor [Active]
18
+ # @param cue [Active::Cue]
19
+ # @param props [Props::Default]
20
+ def initialize actor, cue, props = nil
21
+ @actor = actor
22
+ @cue = cue
23
+ @scene = actor.epic.select_scene(cue.scene)
24
+ @props = props
25
+ end
26
+
27
+ # @return [Props::Default]
28
+ def props
29
+ @props ||= @scene.new_props(**cue.context)
30
+ end
31
+
32
+ # @return [Props::Default]
33
+ def start
34
+ actor.output[:scene] = scene.to_hash
35
+ scene.run_start_blocks actor, props
36
+ scene.start actor, props
37
+ # @todo See if this can be handled better
38
+ actor.epic.rulebooks.each { |rlbk| rlbk.run_player_output_blocks actor, actor.output }
39
+ actor.output.merge!({
40
+ messages: actor.messages,
41
+ queue: actor.queue
42
+ })
43
+ props
44
+ end
45
+
46
+ # @return [void]
47
+ def finish
48
+ actor.flush
49
+ scene.finish(actor, props)
50
+ actor.output.replace(last_prompt: props.prompt, last_input: props.input)
51
+ scene.run_finish_blocks actor, props
52
+ end
53
+
54
+ # @param actor [Active]
55
+ # @param cue [Active::Cue]
56
+ # @return [Props::Default]
57
+ def self.start actor, cue
58
+ Take.new(actor, cue).start
59
+ end
60
+
61
+ # @param actor [Active]
62
+ # @param cue [Active::Cue]
63
+ # @return [void]
64
+ def self.finish actor, cue, props
65
+ Take.new(actor, cue, props).finish
66
+ end
67
+ end
68
+ end
69
+ end