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.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +41 -40
- data/.rspec-opal +2 -0
- data/.solargraph.yml +20 -3
- data/CHANGELOG.md +9 -0
- data/Rakefile +11 -1
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gamefic.gemspec +5 -2
- data/lib/gamefic/action.rb +52 -183
- data/lib/gamefic/active/cue.rb +25 -0
- data/lib/gamefic/active/epic.rb +68 -0
- data/lib/gamefic/active/messaging.rb +43 -0
- data/lib/gamefic/active/take.rb +69 -0
- data/lib/gamefic/active.rb +95 -192
- data/lib/gamefic/actor.rb +2 -0
- data/lib/gamefic/block.rb +28 -0
- data/lib/gamefic/command.rb +16 -6
- data/lib/gamefic/core_ext/array.rb +4 -4
- data/lib/gamefic/core_ext/string.rb +10 -5
- data/lib/gamefic/describable.rb +39 -65
- data/lib/gamefic/dispatcher.rb +63 -32
- data/lib/gamefic/entity.rb +44 -19
- data/lib/gamefic/logging.rb +32 -0
- data/lib/gamefic/messenger.rb +66 -0
- data/lib/gamefic/narrative.rb +104 -0
- data/lib/gamefic/node.rb +44 -53
- data/lib/gamefic/plot.rb +60 -93
- data/lib/gamefic/props/default.rb +41 -0
- data/lib/gamefic/props/multiple_choice.rb +65 -0
- data/lib/gamefic/props/pause.rb +11 -0
- data/lib/gamefic/props/yes_or_no.rb +21 -0
- data/lib/gamefic/props.rb +10 -0
- data/lib/gamefic/query/base.rb +45 -126
- data/lib/gamefic/query/general.rb +46 -0
- data/lib/gamefic/query/result.rb +20 -0
- data/lib/gamefic/query/scoped.rb +41 -0
- data/lib/gamefic/query/text.rb +30 -31
- data/lib/gamefic/query.rb +7 -15
- data/lib/gamefic/response.rb +118 -0
- data/lib/gamefic/rulebook/calls.rb +90 -0
- data/lib/gamefic/rulebook/events.rb +79 -0
- data/lib/gamefic/rulebook/hooks.rb +57 -0
- data/lib/gamefic/rulebook/scenes.rb +68 -0
- data/lib/gamefic/rulebook.rb +139 -0
- data/lib/gamefic/scanner.rb +103 -0
- data/lib/gamefic/scene/activity.rb +9 -17
- data/lib/gamefic/scene/conclusion.rb +6 -5
- data/lib/gamefic/scene/default.rb +88 -0
- data/lib/gamefic/scene/multiple_choice.rb +14 -69
- data/lib/gamefic/scene/pause.rb +9 -13
- data/lib/gamefic/scene/yes_or_no.rb +6 -46
- data/lib/gamefic/scene.rb +11 -7
- data/lib/gamefic/scope/base.rb +44 -0
- data/lib/gamefic/scope/children.rb +16 -0
- data/lib/gamefic/scope/family.rb +20 -0
- data/lib/gamefic/scope/myself.rb +13 -0
- data/lib/gamefic/scope/parent.rb +13 -0
- data/lib/gamefic/scope/siblings.rb +14 -0
- data/lib/gamefic/scope.rb +8 -0
- data/lib/gamefic/scriptable/actions.rb +156 -0
- data/lib/gamefic/scriptable/entities.rb +76 -0
- data/lib/gamefic/scriptable/events.rb +65 -0
- data/lib/gamefic/scriptable/proxy.rb +55 -0
- data/lib/gamefic/scriptable/queries.rb +73 -0
- data/lib/gamefic/scriptable/scenes.rb +162 -0
- data/lib/gamefic/scriptable.rb +167 -73
- data/lib/gamefic/snapshot.rb +36 -0
- data/lib/gamefic/stage.rb +51 -0
- data/lib/gamefic/subplot.rb +51 -79
- data/lib/gamefic/syntax/template.rb +67 -0
- data/lib/gamefic/syntax.rb +102 -83
- data/lib/gamefic/vault.rb +50 -0
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +26 -15
- data/spec-opal/spec_helper.rb +24 -0
- metadata +91 -29
- data/lib/gamefic/element.rb +0 -46
- data/lib/gamefic/keywords.rb +0 -52
- data/lib/gamefic/messaging.rb +0 -43
- data/lib/gamefic/plot/darkroom.rb +0 -120
- data/lib/gamefic/plot/host.rb +0 -42
- data/lib/gamefic/plot/snapshot.rb +0 -27
- data/lib/gamefic/query/children.rb +0 -9
- data/lib/gamefic/query/descendants.rb +0 -15
- data/lib/gamefic/query/external.rb +0 -39
- data/lib/gamefic/query/family.rb +0 -18
- data/lib/gamefic/query/itself.rb +0 -13
- data/lib/gamefic/query/matches.rb +0 -75
- data/lib/gamefic/query/parent.rb +0 -9
- data/lib/gamefic/query/siblings.rb +0 -13
- data/lib/gamefic/query/tree.rb +0 -17
- data/lib/gamefic/scene/base.rb +0 -142
- data/lib/gamefic/scene/multiple_scene.rb +0 -29
- data/lib/gamefic/serialize.rb +0 -196
- data/lib/gamefic/world/callbacks.rb +0 -135
- data/lib/gamefic/world/commands.rb +0 -181
- data/lib/gamefic/world/entities.rb +0 -98
- data/lib/gamefic/world/playbook.rb +0 -233
- data/lib/gamefic/world/players.rb +0 -37
- data/lib/gamefic/world/scenes.rb +0 -228
- 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,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
|