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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scope
5
+ # A query scope that matches the entity's siblings, i.e., the other
6
+ # entities that share its parent.
7
+ #
8
+ class Siblings < Base
9
+ def matches
10
+ context.parent.children - [context]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gamefic/scope/base'
4
+ require 'gamefic/scope/children'
5
+ require 'gamefic/scope/family'
6
+ require 'gamefic/scope/parent'
7
+ require 'gamefic/scope/siblings'
8
+ require 'gamefic/scope/myself'
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable methods related to creating actions.
6
+ #
7
+ module Actions
8
+ include Queries
9
+
10
+ # Create a response to a command.
11
+ # A Response uses the `verb` argument to identify the imperative verb
12
+ # that triggers the action. It can also accept queries to tokenize the
13
+ # remainder of the input and filter for particular entities or
14
+ # properties. The `block`` argument is the proc to execute when the input
15
+ # matches all of the Response's criteria (i.e., verb and queries).
16
+ #
17
+ # @example A simple Response.
18
+ # respond :wave do |actor|
19
+ # actor.tell "Hello!"
20
+ # end
21
+ # # The command "wave" will respond "Hello!"
22
+ #
23
+ # @example A Response that accepts a Character
24
+ # respond :salute, available(Character) do |actor, character|
25
+ # actor.tell "#{The character} returns your salute."
26
+ # end
27
+ #
28
+ # @param verb [Symbol] An imperative verb for the command
29
+ # @param queries [Array<Query::Base, Query::Text>] Filters for the command's tokens
30
+ # @yieldparam [Gamefic::Actor]
31
+ # @return [Symbol]
32
+ def respond(verb, *queries, &proc)
33
+ args = map_response_args(queries)
34
+ rulebook.calls.add_response Response.new(verb, rulebook.narrative, *args, &proc)
35
+ verb
36
+ end
37
+
38
+ # Create a meta response for a command.
39
+ # Meta responses are very similar to standard responses, except they're
40
+ # flagged as meta (`Response#meta?`) to indicate that they provide a
41
+ # feature that is not considered an in-game action, such as displaying
42
+ # help documentation or a scoreboard.
43
+ #
44
+ # @example A simple meta Response
45
+ # meta :credits do |actor|
46
+ # actor.tell "This game was written by John Smith."
47
+ # end
48
+ #
49
+ # @param verb [Symbol] An imperative verb for the command
50
+ # @param queries [Array<Query::Base, Query::Text>] Filters for the command's tokens
51
+ # @yieldparam [Gamefic::Actor]
52
+ # @return [Symbol]
53
+ def meta(verb, *queries, &proc)
54
+ args = map_response_args(queries)
55
+ rulebook.calls.add_response Response.new(verb, rulebook.narrative, *args, meta: true, &proc)
56
+ verb
57
+ end
58
+
59
+ # Add a proc to be evaluated before a character executes an action.
60
+ # When verbs are specified, the proc will only be evaluated if the
61
+ # action's verb matches them.
62
+ #
63
+ # @param verbs [Array<Symbol>]
64
+ # @yieldparam [Gamefic::Action]
65
+ # @return [Action::Hook]
66
+ def before_action *verbs, &block
67
+ rulebook.hooks.before_action *verbs, &block
68
+ end
69
+
70
+ # Add a proc to be evaluated after a character executes an action.
71
+ # When a verbs are specified, the proc will only be evaluated if the
72
+ # action's verb matches them.
73
+ #
74
+ # @param verbs [Array<Symbol>]
75
+ # @yieldparam [Gamefic::Action]
76
+ # @return [Action::Hook]
77
+ def after_action *verbs, &block
78
+ rulebook.hooks.after_action *verbs, &block
79
+ end
80
+
81
+ # Create an alternate Syntax for a response.
82
+ # The command and its translation can be parameterized.
83
+ #
84
+ # @example Create a synonym for an `inventory` response.
85
+ # interpret "catalogue", "inventory"
86
+ # # The command "catalogue" will be translated to "inventory"
87
+ #
88
+ # @example Create a parameterized synonym for a `look` response.
89
+ # interpret "scrutinize :entity", "look :entity"
90
+ # # The command "scrutinize chair" will be translated to "look chair"
91
+ #
92
+ # @param command [String] The format of the original command
93
+ # @param translation [String] The format of the translated command
94
+ # @return [Syntax] the Syntax object
95
+ def interpret command, translation
96
+ rulebook.calls.add_syntax Syntax.new(command, translation)
97
+ end
98
+
99
+ # Verbs are the symbols that have responses defined in the rulebook.
100
+ #
101
+ # @example
102
+ # class MyPlot < Gamefic::Plot
103
+ # script do
104
+ # respond :think { |actor| actor.tell 'You think.' }
105
+ #
106
+ # verbs #=> [:think]
107
+ # end
108
+ # end
109
+ #
110
+ # @return [Array<Symbol>]
111
+ def verbs
112
+ rulebook.verbs
113
+ end
114
+
115
+ # Synonyms are a combination of the rulebook's concrete verbs plus the
116
+ # alternative variants defined in syntaxes.
117
+ #
118
+ # @example
119
+ # class MyPlot < Gamefic::Plot
120
+ # respond :think { |actor| actor.tell 'You think.' }
121
+ # interpret 'ponder', 'think'
122
+ #
123
+ # verbs #=> [:think]
124
+ # synonyms #=> [:think, :ponder]
125
+ # end
126
+ # end
127
+ #
128
+ # @return [Array<Symbol>]
129
+ def synonyms
130
+ rulebook.synonyms
131
+ end
132
+
133
+ # @return [Array<Syntax>]
134
+ def syntaxes
135
+ rulebook.syntaxes
136
+ end
137
+
138
+ private
139
+
140
+ def map_response_args args
141
+ args.map do |arg|
142
+ case arg
143
+ when Entity, Class, Module, Proc, Proxy::Agent
144
+ available(arg)
145
+ when String, Regexp
146
+ plaintext(arg)
147
+ when Gamefic::Query::Base, Gamefic::Query::Text
148
+ arg
149
+ else
150
+ raise ArgumentError, "invalid argument in response: #{arg.inspect}"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable methods related to managing entities.
6
+ #
7
+ # @note The public versions of the entity and player arrays are frozen.
8
+ # Authors need access to them but shouldn't modify them directly. Use
9
+ # #make and #destroy instead.
10
+ #
11
+ module Entities
12
+ include Proxy
13
+
14
+ def entity_vault
15
+ @entity_vault ||= Vault.new
16
+ end
17
+
18
+ def player_vault
19
+ @player_vault ||= Vault.new
20
+ end
21
+
22
+ # @return [Array<Gamefic::Entity>]
23
+ def entities
24
+ entity_vault.array
25
+ end
26
+
27
+ # @return [Array<Gamefic::Actor, Gamefic::Active>]
28
+ def players
29
+ player_vault.array
30
+ end
31
+
32
+ # Create an entity.
33
+ #
34
+ # @example
35
+ # class MyPlot < Gamefic::Plot
36
+ # seed { make Gamefic::Entity, name: 'thing' }
37
+ # end
38
+ #
39
+ # @param [Class<Gamefic::Entity>]
40
+ # @param args [Hash]
41
+ # @return [Gamefic::Entity]
42
+ def make klass, **opts
43
+ entity_vault.add klass.new(**unproxy(opts))
44
+ end
45
+
46
+ def destroy entity
47
+ entity.children.each { |child| destroy child }
48
+ entity.parent = nil
49
+ entity_vault.delete entity
50
+ end
51
+
52
+ # Pick an entity based on a unique name or description. Return nil if an
53
+ # entity could not be found or there is more than one possible match.
54
+ #
55
+ # @param description [String]
56
+ # @return [Gamefic::Entity, nil]
57
+ def pick description
58
+ Gamefic::Query::General.new(entities).query(nil, description).match
59
+ end
60
+
61
+ # Same as #pick, but raise an error if a unique match could not be found.
62
+ #
63
+ # @param description [String]
64
+ # @return [Gamefic::Entity, nil]
65
+ def pick! description
66
+ ary = Gamefic::Query::General.new(entities, ambiguous: true).query(nil, description).match
67
+
68
+ raise "no entity matching '#{description}'" if ary.nil?
69
+
70
+ raise "multiple entities matching '#{description}': #{ary.join_and}" unless ary.one?
71
+
72
+ ary.first
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable methods related to creating event callbacks.
6
+ #
7
+ module Events
8
+ # Add a block to be executed on preparation of every turn.
9
+ #
10
+ # @example Increment a turn counter
11
+ # turn = 0
12
+ # on_ready do
13
+ # turn += 1
14
+ # end
15
+ #
16
+ def on_ready &block
17
+ rulebook.events.on_ready(&block)
18
+ end
19
+
20
+ # Add a block to be executed for each player at the beginning of a turn.
21
+ #
22
+ # @example Tell the player how many turns they've played.
23
+ # on_player_ready do |player|
24
+ # player[:turns] ||= 1
25
+ # player.tell "Turn #{player[:turns]}"
26
+ # player[:turns] += 1
27
+ # end
28
+ #
29
+ # @yieldparam [Gamefic::Actor]
30
+ def on_player_ready &block
31
+ rulebook.events.on_player_ready(&block)
32
+ end
33
+
34
+ # Add a block to be executed after the Plot is finished updating a turn.
35
+ #
36
+ def on_update &block
37
+ rulebook.events.on_update(&block)
38
+ end
39
+
40
+ # Add a block to be executed for each player at the end of a turn.
41
+ #
42
+ # @yieldparam [Gamefic::Actor]
43
+ def on_player_update &block
44
+ rulebook.events.on_player_update(&block)
45
+ end
46
+
47
+ def on_conclude &block
48
+ rulebook.events.on_conclude(&block)
49
+ end
50
+
51
+ # @yieldparam [Actor]
52
+ # @return [Proc]
53
+ def on_player_conclude &block
54
+ rulebook.events.on_player_conclude(&block)
55
+ end
56
+
57
+ # @yieldparam [Actor]
58
+ # @yieldparam [Hash]
59
+ # @return [Proc]
60
+ def on_player_output &block
61
+ rulebook.events.on_player_output(&block)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,55 @@
1
+ module Gamefic
2
+ module Scriptable
3
+ # Functions that provide proxies for referencing a narrative's entities
4
+ # from class-level scripts.
5
+ #
6
+ module Proxy
7
+ # The object that fetches a proxied entity.
8
+ #
9
+ class Agent
10
+ attr_reader :symbol
11
+
12
+ # @param symbol [Symbol, Integer]
13
+ def initialize symbol
14
+ @symbol = symbol
15
+ end
16
+
17
+ def fetch container
18
+ if symbol.to_s =~ /^\d+$/
19
+ Stage.run(container, symbol) { |sym| entities[sym] }
20
+ elsif symbol.to_s.start_with?('@')
21
+ Stage.run(container, symbol) { |sym| instance_variable_get(sym) }
22
+ else
23
+ Stage.run(container, symbol) { |sym| send(sym) }
24
+ end
25
+ end
26
+ end
27
+
28
+ # Proxy a method or instance variable.
29
+ #
30
+ # @example
31
+ # proxy(:method_name)
32
+ # proxy(:@instance_variable_name)
33
+ #
34
+ # @param symbol [Symbol]
35
+ def proxy symbol
36
+ Agent.new(symbol)
37
+ end
38
+
39
+ # @param object [Object]
40
+ # @return [Object]
41
+ def unproxy object
42
+ case object
43
+ when Agent
44
+ object.fetch self
45
+ when Array
46
+ object.map { |obj| unproxy obj }
47
+ when Hash
48
+ object.transform_values { |val| unproxy val }
49
+ else
50
+ object
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable methods related to creating action queries.
6
+ #
7
+ module Queries
8
+ include Proxy
9
+
10
+ # Define a query that searches the entire plot's entities.
11
+ #
12
+ # @param args [Array<Object>] Query arguments
13
+ # @return [Query::General]
14
+ def anywhere *args, ambiguous: false
15
+ Query::General.new -> { entities }, *unproxy(args), ambiguous: ambiguous
16
+ end
17
+
18
+ # Define a query that searches an actor's family of entities. The
19
+ # results include the parent, siblings, children, and accessible
20
+ # descendants of siblings and children.
21
+ #
22
+ # @param args [Array<Object>] Query arguments
23
+ # @return [Query::Scoped]
24
+ def available *args, ambiguous: false
25
+ Query::Scoped.new Scope::Family, *unproxy(args), ambiguous: ambiguous
26
+ end
27
+ alias family available
28
+
29
+ # Define a query that returns the actor's parent.
30
+ #
31
+ # @param args [Array<Object>] Query arguments
32
+ # @return [Query::Scoped]
33
+ def parent *args, ambiguous: false
34
+ Query::Scoped.new Scope::Parent, *unproxy(args), ambiguous: ambiguous
35
+ end
36
+
37
+ # Define a query that searches an actor's children.
38
+ #
39
+ # @param args [Array<Object>] Query arguments
40
+ # @return [Query::Scoped]
41
+ def children *args, ambiguous: false
42
+ Query::Scoped.new Scope::Children, *unproxy(args), ambiguous: ambiguous
43
+ end
44
+
45
+ # Define a query that searches an actor's siblings.
46
+ #
47
+ # @param args [Array<Object>] Query arguments
48
+ # @return [Query::Scoped]
49
+ def siblings *args, ambiguous: false
50
+ Query::Scoped.new Scope::Siblings, *unproxy(args), ambiguous: ambiguous
51
+ end
52
+
53
+ # Define a query that returns the actor itself.
54
+ #
55
+ # @param args [Array<Object>] Query arguments
56
+ # @return [Query::Scoped]
57
+ def myself *args, ambiguous: false
58
+ Query::Scoped.new Scope::Myself, *unproxy(args), ambiguous: ambiguous
59
+ end
60
+
61
+ # Define a query that performs a plaintext search. It can take a String
62
+ # or a RegExp as an argument. If no argument is provided, it will match
63
+ # any text it finds in the command. A successful query returns the
64
+ # corresponding text instead of an entity.
65
+ #
66
+ # @param arg [String, Regrxp] The string or regular expression to match
67
+ # @return [Query::Text]
68
+ def plaintext arg = nil
69
+ Query::Text.new arg
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable methods related to creating scenes.
6
+ #
7
+ module Scenes
8
+ # Block a new scene.
9
+ #
10
+ # @example Prompt the player for a name
11
+ # block :name_of_scene do |scene|
12
+ # # The scene's start occurs before the user gets prompted for input
13
+ # scene.on_start do |actor, props|
14
+ # props.prompt = 'What's your name?'
15
+ # end
16
+ #
17
+ # # The scene's finish is where you can process the user's input
18
+ # scene.on_finish do |actor, props|
19
+ # if props.input.empty?
20
+ # # You can use recue to start the scene again
21
+ # actor.recue
22
+ # else
23
+ # actor.tell "Hello, #{props.input}!"
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # @param name [Symbol]
29
+ # @param klass [Class<Scene::Default>]
30
+ # @param on_start [Proc, nil]
31
+ # @param on_finish [Proc, nil]
32
+ # @param block [Proc]
33
+ # @yieldparam [Scene]
34
+ # @return [Symbol]
35
+ def block name, klass = Scene::Default, on_start: nil, on_finish: nil, &block
36
+ rulebook.scenes.add klass.new(name, rulebook.narrative, on_start: on_start, on_finish: on_finish, &block)
37
+ name
38
+ end
39
+ alias scene block
40
+
41
+ # Add a block to be executed when a player is added to the game.
42
+ # Each Plot should only have one introduction.
43
+ #
44
+ # @example Welcome the player to the game
45
+ # introduction do |actor|
46
+ # actor.tell "Welcome to the game!"
47
+ # end
48
+ #
49
+ # @raise [ArgumentError] if an introduction already exists
50
+ #
51
+ # @yieldparam [Gamefic::Actor]
52
+ # @yieldparam [Props::Default]
53
+ # @return [Symbol]
54
+ def introduction(&start)
55
+ rulebook.scenes
56
+ .introduction Scene::Default.new nil,
57
+ rulebook.narrative,
58
+ on_start: proc { |actor, _props| instance_exec(actor, &start) }
59
+ end
60
+
61
+ # Create a multiple-choice scene.
62
+ # The user will be required to make a choice to continue. The scene
63
+ # will restart if the user input is not a valid choice.
64
+ #
65
+ # @example
66
+ # multiple_choice :go_somewhere, ['Go to work', 'Go to school'] do |actor, props|
67
+ # # Assuming the user selected the first choice:
68
+ # props.selection #=> 'Go to work'
69
+ # props.index #=> 0
70
+ # props.number #=> 1
71
+ # end
72
+ #
73
+ # @param name [Symbol]
74
+ # @param choices [Array<String>]
75
+ # @param prompt [String, nil]
76
+ # @param proc [Proc]
77
+ # @yieldparam [Actor]
78
+ # @yieldparam [Props::MultipleChoice]
79
+ # @return [Symbol]
80
+ def multiple_choice name, choices = [], prompt = 'What is your choice?', &block
81
+ block name,
82
+ Scene::MultipleChoice,
83
+ on_start: proc { |_actor, props|
84
+ props.prompt = prompt
85
+ props.options.concat choices
86
+ },
87
+ on_finish: block
88
+ end
89
+
90
+ # Create a yes-or-no scene.
91
+ # The user will be required to answer Yes or No to continue. The scene
92
+ # will restart if the user input is not a valid choice.
93
+ #
94
+ # @example
95
+ # yes_or_no :answer_scene, 'What is your answer?' do |actor, props|
96
+ # if props.yes?
97
+ # actor.tell "You said yes."
98
+ # else
99
+ # actor.tell "You said no."
100
+ # end
101
+ # end
102
+ #
103
+ # @param name [Symbol]
104
+ # @param prompt [String, nil]
105
+ # @yieldparam [Actor]
106
+ # @yieldparam [Props::YesOrNo]
107
+ # @return [Symbol]
108
+ def yes_or_no name, prompt = 'Answer:', &block
109
+ block name,
110
+ Scene::YesOrNo,
111
+ on_start: proc { |_actor, props|
112
+ props.prompt = prompt
113
+ },
114
+ on_finish: block
115
+ end
116
+
117
+ # Create a scene that pauses the game.
118
+ # This scene will execute the specified block and wait for input from the
119
+ # the user (e.g., pressing Enter) to continue.
120
+ #
121
+ # @example
122
+ # pause :wait do |actor|
123
+ # actor.tell "After you continue, you will be prompted for a command."
124
+ # end
125
+ #
126
+ # @param name [Symbol]
127
+ # @param prompt [String, nil] The text to display when prompting the user to continue
128
+ # @yieldparam [Actor]
129
+ # @return [Symbol]
130
+ def pause name, prompt: 'Press enter to continue...', &start
131
+ block name,
132
+ Scene::Pause,
133
+ on_start: proc { |actor, props|
134
+ props.prompt = prompt if prompt
135
+ instance_exec(actor, props, &start)
136
+ }
137
+ end
138
+
139
+ # Create a conclusion.
140
+ # The game (or the character's participation in it) will end after this
141
+ # scene is complete.
142
+ #
143
+ # @example
144
+ # conclusion :ending do |actor|
145
+ # actor.tell 'GAME OVER'
146
+ # end
147
+ #
148
+ # @param name [Symbol]
149
+ # @yieldparam [Actor]
150
+ # @return [Symbol]
151
+ def conclusion name, &start
152
+ block name,
153
+ Scene::Conclusion,
154
+ on_start: start
155
+ end
156
+
157
+ def scenes
158
+ rulebook.scenes.names
159
+ end
160
+ end
161
+ end
162
+ end