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