gamefic 3.6.0 → 4.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/.rubocop.yml +0 -3
- data/CHANGELOG.md +19 -0
- data/Rakefile +1 -0
- data/gamefic.gemspec +1 -1
- data/lib/gamefic/action.rb +68 -54
- data/lib/gamefic/active/cue.rb +84 -6
- data/lib/gamefic/active/messaging.rb +8 -0
- data/lib/gamefic/active/narratives.rb +101 -0
- data/lib/gamefic/active.rb +80 -92
- data/lib/gamefic/binding.rb +44 -0
- data/lib/gamefic/chapter.rb +30 -46
- data/lib/gamefic/command.rb +22 -40
- data/lib/gamefic/core_ext/array.rb +7 -7
- data/lib/gamefic/core_ext/string.rb +2 -2
- data/lib/gamefic/describable.rb +13 -0
- data/lib/gamefic/dispatcher.rb +35 -55
- data/lib/gamefic/entity.rb +6 -5
- data/lib/gamefic/expression.rb +1 -11
- data/lib/gamefic/logging.rb +3 -10
- data/lib/gamefic/match.rb +23 -0
- data/lib/gamefic/messenger.rb +1 -1
- data/lib/gamefic/narrative.rb +38 -74
- data/lib/gamefic/narrator.rb +77 -0
- data/lib/gamefic/node.rb +40 -8
- data/lib/gamefic/order.rb +53 -0
- data/lib/gamefic/plot.rb +41 -59
- data/lib/gamefic/props/default.rb +5 -17
- data/lib/gamefic/props/multiple_choice.rb +5 -2
- data/lib/gamefic/props/multiple_partial.rb +16 -0
- data/lib/gamefic/props/output.rb +7 -5
- data/lib/gamefic/props/yes_or_no.rb +2 -2
- data/lib/gamefic/props.rb +1 -0
- data/lib/gamefic/proxy/attr.rb +11 -0
- data/lib/gamefic/proxy/base.rb +3 -15
- data/lib/gamefic/proxy/config.rb +2 -2
- data/lib/gamefic/proxy/pick.rb +3 -3
- data/lib/gamefic/proxy/pick_ex.rb +11 -0
- data/lib/gamefic/proxy.rb +3 -71
- data/lib/gamefic/query/ascendants.rb +16 -0
- data/lib/gamefic/query/base.rb +47 -73
- data/lib/gamefic/query/children.rb +15 -0
- data/lib/gamefic/query/descendants.rb +17 -0
- data/lib/gamefic/query/extended.rb +20 -0
- data/lib/gamefic/query/family.rb +27 -0
- data/lib/gamefic/query/global.rb +22 -0
- data/lib/gamefic/query/integer.rb +32 -0
- data/lib/gamefic/query/myself.rb +13 -0
- data/lib/gamefic/query/parent.rb +13 -0
- data/lib/gamefic/query/result.rb +1 -1
- data/lib/gamefic/query/siblings.rb +12 -0
- data/lib/gamefic/query/subqueries.rb +17 -0
- data/lib/gamefic/query/text.rb +8 -9
- data/lib/gamefic/query.rb +11 -3
- data/lib/gamefic/request.rb +60 -0
- data/lib/gamefic/response.rb +46 -72
- data/lib/gamefic/scanner/nesting.rb +6 -6
- data/lib/gamefic/scanner/result.rb +3 -0
- data/lib/gamefic/scanner/strict.rb +14 -4
- data/lib/gamefic/scanner.rb +11 -6
- data/lib/gamefic/scene/active_choice.rb +75 -0
- data/lib/gamefic/scene/activity.rb +7 -3
- data/lib/gamefic/scene/base.rb +123 -0
- data/lib/gamefic/scene/conclusion.rb +4 -1
- data/lib/gamefic/scene/multiple_choice.rb +14 -11
- data/lib/gamefic/scene/pause.rb +5 -1
- data/lib/gamefic/scene/yes_or_no.rb +9 -0
- data/lib/gamefic/scene.rb +2 -1
- data/lib/gamefic/scriptable/hooks.rb +161 -0
- data/lib/gamefic/scriptable/queries.rb +38 -29
- data/lib/gamefic/scriptable/responses.rb +70 -0
- data/lib/gamefic/scriptable/scenes.rb +88 -115
- data/lib/gamefic/scriptable/seeds.rb +69 -0
- data/lib/gamefic/scriptable/syntaxes.rb +29 -0
- data/lib/gamefic/scriptable.rb +14 -199
- data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
- data/lib/gamefic/scripting/hooks.rb +45 -0
- data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
- data/lib/gamefic/scripting/responses.rb +21 -0
- data/lib/gamefic/scripting/scenes.rb +57 -0
- data/lib/gamefic/scripting/seeds.rb +10 -0
- data/lib/gamefic/scripting/syntaxes.rb +13 -0
- data/lib/gamefic/scripting.rb +43 -0
- data/lib/gamefic/subplot.rb +11 -22
- data/lib/gamefic/syntax.rb +39 -24
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +6 -7
- metadata +38 -41
- data/lib/gamefic/active/epic.rb +0 -74
- data/lib/gamefic/active/take.rb +0 -67
- data/lib/gamefic/block.rb +0 -28
- data/lib/gamefic/callback.rb +0 -16
- data/lib/gamefic/proxy/plot_pick.rb +0 -11
- data/lib/gamefic/query/abstract.rb +0 -12
- data/lib/gamefic/query/general.rb +0 -41
- data/lib/gamefic/query/scoped.rb +0 -27
- data/lib/gamefic/rulebook/calls.rb +0 -86
- data/lib/gamefic/rulebook/events.rb +0 -65
- data/lib/gamefic/rulebook/hooks.rb +0 -57
- data/lib/gamefic/rulebook/scenes.rb +0 -68
- data/lib/gamefic/rulebook.rb +0 -125
- data/lib/gamefic/scene/default.rb +0 -88
- data/lib/gamefic/scope/base.rb +0 -44
- data/lib/gamefic/scope/children.rb +0 -16
- data/lib/gamefic/scope/descendants.rb +0 -16
- data/lib/gamefic/scope/family.rb +0 -43
- data/lib/gamefic/scope/myself.rb +0 -13
- data/lib/gamefic/scope/parent.rb +0 -13
- data/lib/gamefic/scope/siblings.rb +0 -14
- data/lib/gamefic/scope.rb +0 -9
- data/lib/gamefic/scriptable/actions.rb +0 -137
- data/lib/gamefic/scriptable/events.rb +0 -71
- data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
- data/lib/gamefic/snapshot.rb +0 -44
- data/lib/gamefic/stage.rb +0 -51
- data/lib/gamefic/syntax/template.rb +0 -67
- data/lib/gamefic/vault.rb +0 -52
data/lib/gamefic/entity.rb
CHANGED
|
@@ -52,7 +52,7 @@ module Gamefic
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def inspect
|
|
55
|
-
"#<#{self.class} #{name}>"
|
|
55
|
+
"#<#{self.class} '#{name}'>"
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Move this entity to its parent entity.
|
|
@@ -75,10 +75,11 @@ module Gamefic
|
|
|
75
75
|
#
|
|
76
76
|
# @param message [String]
|
|
77
77
|
# @return [void]
|
|
78
|
-
def broadcast
|
|
79
|
-
Query::
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
def broadcast(message)
|
|
79
|
+
Query::Descendants.new
|
|
80
|
+
.select(self)
|
|
81
|
+
.that_are(Active, proc(&:participating?))
|
|
82
|
+
.each { |actor| actor.tell message }
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
class << self
|
data/lib/gamefic/expression.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Gamefic
|
|
|
12
12
|
|
|
13
13
|
# @param verb [Symbol, nil]
|
|
14
14
|
# @param tokens [Array<String>]
|
|
15
|
-
def initialize
|
|
15
|
+
def initialize(verb, tokens)
|
|
16
16
|
@verb = verb
|
|
17
17
|
@tokens = tokens
|
|
18
18
|
end
|
|
@@ -20,15 +20,5 @@ module Gamefic
|
|
|
20
20
|
def inspect
|
|
21
21
|
"#<#{self.class} #{([verb] + tokens).map(&:inspect).join(', ')}>"
|
|
22
22
|
end
|
|
23
|
-
# Compare two syntaxes for the purpose of ordering them by relevance while
|
|
24
|
-
# dispatching.
|
|
25
|
-
#
|
|
26
|
-
def compare other
|
|
27
|
-
if verb == other.verb
|
|
28
|
-
other.tokens.compact.length <=> tokens.compact.length
|
|
29
|
-
else
|
|
30
|
-
(other.verb ? 1 : 0) <=> (verb ? 1 : 0)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
23
|
end
|
|
34
24
|
end
|
data/lib/gamefic/logging.rb
CHANGED
|
@@ -16,17 +16,10 @@ module Gamefic
|
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
18
|
def logger
|
|
19
|
-
@logger ||=
|
|
20
|
-
|
|
19
|
+
@logger ||= Logger.new($stderr).tap do |lggr|
|
|
20
|
+
lggr.level = Logger::WARN
|
|
21
|
+
lggr.formatter = proc { |sev, _dt, _prog, msg| "[#{sev}] #{msg}\n" }
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def select_logger
|
|
27
|
-
# We use #tap here because `Logger.new(STDERR, level: Logger::WARN)`
|
|
28
|
-
# fails in Opal
|
|
29
|
-
Logger.new($stderr).tap { |log| log.level = Logger::WARN }
|
|
30
|
-
end
|
|
31
24
|
end
|
|
32
25
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
class Match
|
|
5
|
+
# @return [Object]
|
|
6
|
+
attr_reader :argument
|
|
7
|
+
|
|
8
|
+
# @return [Object]
|
|
9
|
+
attr_reader :token
|
|
10
|
+
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
attr_reader :strictness
|
|
13
|
+
|
|
14
|
+
# @param argument [Object]
|
|
15
|
+
# @param token [Object]
|
|
16
|
+
# @param strictness [Integer]
|
|
17
|
+
def initialize(argument, token, strictness)
|
|
18
|
+
@argument = argument
|
|
19
|
+
@token = token
|
|
20
|
+
@strictness = strictness
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/gamefic/messenger.rb
CHANGED
data/lib/gamefic/narrative.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'corelib/marshal' if RUBY_ENGINE == 'opal' # Required in browser
|
|
4
4
|
|
|
5
5
|
module Gamefic
|
|
6
6
|
# A base class for building and managing the resources that compose a story.
|
|
@@ -8,48 +8,14 @@ module Gamefic
|
|
|
8
8
|
# functionality.
|
|
9
9
|
#
|
|
10
10
|
class Narrative
|
|
11
|
-
|
|
11
|
+
include Scripting
|
|
12
|
+
# @!parse extend Gamefic::Scriptable
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
include Scriptable::Entities
|
|
16
|
-
include Scriptable::Events
|
|
17
|
-
include Scriptable::Proxies
|
|
18
|
-
include Scriptable::Queries
|
|
19
|
-
include Scriptable::Scenes
|
|
14
|
+
select_default_scene Scene::Activity
|
|
15
|
+
select_default_conclusion Scene::Conclusion
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def initialize(hydrate: true)
|
|
24
|
-
return unless hydrate
|
|
25
|
-
|
|
26
|
-
seed
|
|
27
|
-
script
|
|
28
|
-
post_script
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def seed
|
|
32
|
-
included_blocks.select(&:seed?).each { |blk| Stage.run self, &blk.code }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def script
|
|
36
|
-
@rulebook = Rulebook.new
|
|
37
|
-
included_blocks.select(&:script?).each { |blk| Stage.run self, &blk.code }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# @return [Array<Module>]
|
|
41
|
-
def included_blocks
|
|
42
|
-
self.class.included_blocks
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def post_script
|
|
46
|
-
entity_vault.lock
|
|
47
|
-
rulebook.freeze
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# @return [Array<Symbol>]
|
|
51
|
-
def scenes
|
|
52
|
-
rulebook.scenes.names
|
|
17
|
+
def initialize
|
|
18
|
+
seeds.each { |blk| instance_exec(&blk) }
|
|
53
19
|
end
|
|
54
20
|
|
|
55
21
|
# Introduce an actor to the story.
|
|
@@ -58,9 +24,7 @@ module Gamefic
|
|
|
58
24
|
# @return [Gamefic::Actor]
|
|
59
25
|
def introduce(player = Gamefic::Actor.new)
|
|
60
26
|
cast player
|
|
61
|
-
|
|
62
|
-
scene.run_start_blocks player, nil
|
|
63
|
-
end
|
|
27
|
+
introductions.each { |blk| blk[player] }
|
|
64
28
|
player
|
|
65
29
|
end
|
|
66
30
|
|
|
@@ -74,53 +38,53 @@ module Gamefic
|
|
|
74
38
|
|
|
75
39
|
# Add an active entity to the narrative.
|
|
76
40
|
#
|
|
77
|
-
# @param [Gamefic::Active]
|
|
41
|
+
# @param active [Gamefic::Active]
|
|
78
42
|
# @return [Gamefic::Active]
|
|
79
|
-
def cast
|
|
80
|
-
active.
|
|
81
|
-
|
|
82
|
-
|
|
43
|
+
def cast(active)
|
|
44
|
+
active.narratives.add self
|
|
45
|
+
player_set.add active
|
|
46
|
+
entity_set.add active
|
|
83
47
|
active
|
|
84
48
|
end
|
|
85
49
|
|
|
86
50
|
# Remove an active entity from the narrative.
|
|
87
51
|
#
|
|
88
|
-
# @param [Gamefic::Active]
|
|
52
|
+
# @param active [Gamefic::Active]
|
|
89
53
|
# @return [Gamefic::Active]
|
|
90
|
-
def uncast
|
|
91
|
-
active.
|
|
92
|
-
|
|
93
|
-
|
|
54
|
+
def uncast(active)
|
|
55
|
+
active.narratives.delete self
|
|
56
|
+
player_set.delete active
|
|
57
|
+
entity_set.delete active
|
|
94
58
|
active
|
|
95
59
|
end
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def detach
|
|
107
|
-
cache = @rulebook
|
|
108
|
-
@rulebook = nil
|
|
109
|
-
cache
|
|
61
|
+
# Complete a game turn.
|
|
62
|
+
#
|
|
63
|
+
# In the base Narrative class, this method runs all applicable player
|
|
64
|
+
# conclude blocks and the narrative's own conclude blocks.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
67
|
+
def turn
|
|
68
|
+
players.select(&:concluding?).each { |plyr| player_conclude_blocks.each { |blk| blk[plyr] } }
|
|
69
|
+
conclude_blocks.each(&:call) if concluding?
|
|
110
70
|
end
|
|
111
71
|
|
|
112
|
-
|
|
113
|
-
|
|
72
|
+
# @return [String]
|
|
73
|
+
def save
|
|
74
|
+
Marshal.dump(self)
|
|
114
75
|
end
|
|
115
76
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
77
|
+
# @param snapshot [String]
|
|
78
|
+
# @return [self]
|
|
79
|
+
def self.restore(snapshot)
|
|
80
|
+
Marshal.load(snapshot)
|
|
119
81
|
end
|
|
120
82
|
|
|
121
|
-
def self.inherited
|
|
83
|
+
def self.inherited(klass)
|
|
122
84
|
super
|
|
123
|
-
klass.
|
|
85
|
+
klass.seeds.concat seeds
|
|
86
|
+
klass.select_default_scene default_scene
|
|
87
|
+
klass.select_default_conclusion default_conclusion
|
|
124
88
|
end
|
|
125
89
|
end
|
|
126
90
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
# A narrative controller.
|
|
5
|
+
#
|
|
6
|
+
class Narrator
|
|
7
|
+
# @return [Plot]
|
|
8
|
+
attr_reader :plot
|
|
9
|
+
|
|
10
|
+
def initialize(plot)
|
|
11
|
+
@plot = plot
|
|
12
|
+
last_cues
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Cast a player character in the plot.
|
|
16
|
+
#
|
|
17
|
+
# @param character [Actor]
|
|
18
|
+
# @return [Actor]
|
|
19
|
+
def cast(character = plot.introduce)
|
|
20
|
+
plot.cast character
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Uncast a player character from the plot.
|
|
24
|
+
#
|
|
25
|
+
# @param character [Actor]
|
|
26
|
+
# @return [Actor]
|
|
27
|
+
def uncast(character)
|
|
28
|
+
plot.uncast character
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def players
|
|
32
|
+
plot.players
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Start a turn.
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
def start
|
|
39
|
+
next_cues
|
|
40
|
+
plot.ready_blocks.each(&:call)
|
|
41
|
+
plot.turn
|
|
42
|
+
cues.each(&:prepare)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Finish a turn.
|
|
46
|
+
#
|
|
47
|
+
# @return [void]
|
|
48
|
+
def finish
|
|
49
|
+
cues.each(&:finish)
|
|
50
|
+
cues.clear
|
|
51
|
+
plot.update_blocks.each(&:call)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def concluding?
|
|
55
|
+
plot.concluding?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# @return [Array<Active::Cue>]
|
|
61
|
+
def cues
|
|
62
|
+
@cues ||= []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [void]
|
|
66
|
+
def last_cues
|
|
67
|
+
cues.replace(plot.players.map(&:last_cue))
|
|
68
|
+
.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [void]
|
|
72
|
+
def next_cues
|
|
73
|
+
cues.replace(plot.players.map { |player| player.next_cue || player.cue(plot.default_scene) })
|
|
74
|
+
.each(&:start)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/gamefic/node.rb
CHANGED
|
@@ -39,25 +39,54 @@ module Gamefic
|
|
|
39
39
|
|
|
40
40
|
parent&.rem_child self
|
|
41
41
|
@parent = node
|
|
42
|
+
@relation = nil
|
|
42
43
|
parent&.add_child self
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# The node's relation to its parent.
|
|
47
|
+
#
|
|
48
|
+
# The inherently supported relations are `:in` and `:on`, but authors are
|
|
49
|
+
# free to define their own.
|
|
50
|
+
#
|
|
51
|
+
# @return [Symbol, nil]
|
|
52
|
+
def relation
|
|
53
|
+
@relation ||= (parent ? :in : nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param symbol [Symbol, nil]
|
|
57
|
+
def relation=(symbol)
|
|
58
|
+
raise NodeError, "Invalid relation #{symbol.inspect} on #{inspect} without parent" unless parent || !symbol
|
|
59
|
+
|
|
60
|
+
@relation = symbol
|
|
61
|
+
end
|
|
62
|
+
|
|
45
63
|
# Add children to the node. Return all the node's children.
|
|
46
64
|
#
|
|
47
65
|
# @param children [Array<Node, Array<Node>>]
|
|
66
|
+
# @param relation [Symbol, nil]
|
|
48
67
|
# @return [Array<Node>]
|
|
49
|
-
def take *children
|
|
50
|
-
children.flatten.each { |child| child.
|
|
68
|
+
def take *children, relation: nil
|
|
69
|
+
children.flatten.each { |child| child.put self, relation }
|
|
51
70
|
children
|
|
52
71
|
end
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
def put(parent, relation = nil)
|
|
74
|
+
self.parent = parent
|
|
75
|
+
@relation = relation
|
|
76
|
+
end
|
|
77
|
+
alias place put
|
|
78
|
+
|
|
79
|
+
# Get an array of children that are accessible to external entities.
|
|
80
|
+
#
|
|
81
|
+
# A child is considered accessible if external entities can interact with
|
|
82
|
+
# it. For Example, an author can designate that the contents of a bowl are
|
|
83
|
+
# accessible, while the contents of a locked safe are not. All of an
|
|
84
|
+
# entity's children are accessible by default. Authors should override this
|
|
85
|
+
# method if they need custom behavior.
|
|
57
86
|
#
|
|
58
|
-
# @return [
|
|
59
|
-
def accessible
|
|
60
|
-
|
|
87
|
+
# @return [Array<Entity>]
|
|
88
|
+
def accessible
|
|
89
|
+
children
|
|
61
90
|
end
|
|
62
91
|
|
|
63
92
|
# True if this node is the other's parent.
|
|
@@ -67,6 +96,9 @@ module Gamefic
|
|
|
67
96
|
other.parent == self
|
|
68
97
|
end
|
|
69
98
|
|
|
99
|
+
# True if this node and the other node have the same parent.
|
|
100
|
+
#
|
|
101
|
+
# @param other [Node]
|
|
70
102
|
def adjacent?(other)
|
|
71
103
|
other.parent == parent
|
|
72
104
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
# Build actions from explicit verbs and arguments.
|
|
5
|
+
#
|
|
6
|
+
# The Active#execute method uses Order to bypass the parser while
|
|
7
|
+
# generating actions to be executed in the Dispatcher.
|
|
8
|
+
#
|
|
9
|
+
class Order
|
|
10
|
+
# @param actor [Actor]
|
|
11
|
+
# @param verb [Symbol]
|
|
12
|
+
# @param arguments [Array<Object>]
|
|
13
|
+
def initialize(actor, verb, arguments)
|
|
14
|
+
@actor = actor
|
|
15
|
+
@verb = verb
|
|
16
|
+
@arguments = arguments
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Array<Action>]
|
|
20
|
+
def to_actions
|
|
21
|
+
Action.sort(
|
|
22
|
+
actor.narratives
|
|
23
|
+
.responses_for(verb)
|
|
24
|
+
.map { |response| match_arguments(response) }
|
|
25
|
+
.compact
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @return [Actor]
|
|
32
|
+
attr_reader :actor
|
|
33
|
+
|
|
34
|
+
# @return [Symbol]
|
|
35
|
+
attr_reader :verb
|
|
36
|
+
|
|
37
|
+
# @return [Array<Object>]
|
|
38
|
+
attr_reader :arguments
|
|
39
|
+
|
|
40
|
+
def match_arguments(response)
|
|
41
|
+
return nil if response.queries.length != arguments.length
|
|
42
|
+
|
|
43
|
+
matches = response.queries.zip(arguments).each_with_object([]) do |zipped, result|
|
|
44
|
+
query, param = zipped
|
|
45
|
+
return nil unless query.accept?(actor, param)
|
|
46
|
+
|
|
47
|
+
result.push Match.new(param, param, 1000)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Action.new(actor, response, matches, nil)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/gamefic/plot.rb
CHANGED
|
@@ -5,49 +5,15 @@ module Gamefic
|
|
|
5
5
|
# methods for creating entities, actions, scenes, and hooks.
|
|
6
6
|
#
|
|
7
7
|
class Plot < Narrative
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
chapters.each(&:seed)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def script
|
|
14
|
-
super
|
|
15
|
-
chapters.each(&:script)
|
|
16
|
-
rulebook.scenes.with_defaults self
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def post_script
|
|
20
|
-
super
|
|
21
|
-
chapters.freeze
|
|
22
|
-
end
|
|
8
|
+
# @return [Array<Chapter>]
|
|
9
|
+
attr_reader :chapters
|
|
23
10
|
|
|
24
|
-
def
|
|
25
|
-
@chapters ||= self.class.appended_chapters.map { |klass| klass.new(self) }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def ready
|
|
29
|
-
super
|
|
30
|
-
subplots.each(&:ready)
|
|
31
|
-
players.each(&:start_take)
|
|
32
|
-
subplots.each(&:conclude) if concluding?
|
|
33
|
-
players.select(&:concluding?).each { |plyr| rulebook.run_player_conclude_blocks plyr }
|
|
34
|
-
subplots.delete_if(&:concluding?)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def update
|
|
38
|
-
players.each(&:finish_take)
|
|
11
|
+
def initialize
|
|
39
12
|
super
|
|
40
|
-
|
|
13
|
+
@chapters = self.class.appended_chapter_map.map { |chap, config| chap.new(self, **unproxy(config)) }
|
|
41
14
|
end
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
# Calling `uncast` on the plot will also remove the actor from its
|
|
46
|
-
# subplots.
|
|
47
|
-
#
|
|
48
|
-
# @param actor [Actor]
|
|
49
|
-
# @return [Actor]
|
|
50
|
-
def uncast actor
|
|
16
|
+
def uncast(actor)
|
|
51
17
|
subplots.each { |sp| sp.uncast actor }
|
|
52
18
|
super
|
|
53
19
|
end
|
|
@@ -70,41 +36,57 @@ module Gamefic
|
|
|
70
36
|
.tap { |sub| subplots.push sub }
|
|
71
37
|
end
|
|
72
38
|
|
|
73
|
-
def save
|
|
74
|
-
Snapshot.save self
|
|
75
|
-
end
|
|
76
|
-
|
|
77
39
|
def inspect
|
|
78
40
|
"#<#{self.class}>"
|
|
79
41
|
end
|
|
80
42
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
@rulebook = nil
|
|
84
|
-
cache.concat subplots.map(&:detach)
|
|
85
|
-
cache
|
|
43
|
+
def self.append(chapter, **config)
|
|
44
|
+
appended_chapter_map[chapter] = config
|
|
86
45
|
end
|
|
87
46
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
subplots.each { |subplot| subplot.attach cache.shift }
|
|
47
|
+
def self.appended_chapter_map
|
|
48
|
+
@appended_chapter_map ||= {}
|
|
91
49
|
end
|
|
92
50
|
|
|
93
|
-
|
|
51
|
+
# Complete a game turn.
|
|
52
|
+
#
|
|
53
|
+
# In addition to running its own applicable conclude blocks, the Plot class
|
|
54
|
+
# will also handle conclude blocks for its chapters and subplots.
|
|
55
|
+
#
|
|
56
|
+
# @return [void]
|
|
57
|
+
def turn
|
|
94
58
|
super
|
|
95
|
-
subplots.each(&:
|
|
59
|
+
subplots.each(&:conclude) if concluding?
|
|
60
|
+
chapters.delete_if(&:concluding?)
|
|
61
|
+
subplots.delete_if(&:concluding?)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ready_blocks
|
|
65
|
+
super + subplots.flat_map(&:ready_blocks)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update_blocks
|
|
69
|
+
super + subplots.flat_map(&:update_blocks)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def player_output_blocks
|
|
73
|
+
super + subplots.flat_map(&:player_output_blocks)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def responses
|
|
77
|
+
super + chapters.flat_map(&:responses)
|
|
96
78
|
end
|
|
97
79
|
|
|
98
|
-
def
|
|
99
|
-
|
|
80
|
+
def responses_for(*verbs)
|
|
81
|
+
super + chapters.flat_map { |chap| chap.responses_for(*verbs) }
|
|
100
82
|
end
|
|
101
83
|
|
|
102
|
-
def
|
|
103
|
-
|
|
84
|
+
def syntaxes
|
|
85
|
+
super + chapters.flat_map(&:syntaxes)
|
|
104
86
|
end
|
|
105
87
|
|
|
106
|
-
def
|
|
107
|
-
|
|
88
|
+
def find_and_bind(symbol)
|
|
89
|
+
super + chapters.flat_map { |chap| chap.find_and_bind(symbol) }
|
|
108
90
|
end
|
|
109
91
|
end
|
|
110
92
|
end
|
|
@@ -18,23 +18,6 @@ module Gamefic
|
|
|
18
18
|
# @return [String]
|
|
19
19
|
attr_accessor :input
|
|
20
20
|
|
|
21
|
-
# A freeform dictionary of objects related to the scene. Plots can pass
|
|
22
|
-
# opts to be included in the context when they cue scenes.
|
|
23
|
-
#
|
|
24
|
-
# @return [Hash]
|
|
25
|
-
attr_reader :context
|
|
26
|
-
alias data context
|
|
27
|
-
|
|
28
|
-
# @return [Hash]
|
|
29
|
-
attr_reader :scene
|
|
30
|
-
|
|
31
|
-
# @param scene [Scene]
|
|
32
|
-
# @param context [Hash]
|
|
33
|
-
def initialize scene, **context
|
|
34
|
-
@scene = { name: scene.name, type: scene.type }
|
|
35
|
-
@context = context
|
|
36
|
-
end
|
|
37
|
-
|
|
38
21
|
def prompt
|
|
39
22
|
@prompt ||= '>'
|
|
40
23
|
end
|
|
@@ -42,6 +25,11 @@ module Gamefic
|
|
|
42
25
|
def output
|
|
43
26
|
@output ||= Props::Output.new
|
|
44
27
|
end
|
|
28
|
+
|
|
29
|
+
# @param text [String]
|
|
30
|
+
def enter(text)
|
|
31
|
+
@input = text
|
|
32
|
+
end
|
|
45
33
|
end
|
|
46
34
|
end
|
|
47
35
|
end
|
|
@@ -49,6 +49,10 @@ module Gamefic
|
|
|
49
49
|
options[index]
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
def selected?
|
|
53
|
+
!!index
|
|
54
|
+
end
|
|
55
|
+
|
|
52
56
|
private
|
|
53
57
|
|
|
54
58
|
def index_by_number
|
|
@@ -58,8 +62,7 @@ module Gamefic
|
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
def index_by_text
|
|
61
|
-
|
|
62
|
-
matches.first if matches.one?
|
|
65
|
+
options.find_index { |opt| opt.casecmp?(input) }
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
68
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gamefic
|
|
4
|
+
module Props
|
|
5
|
+
# A subclass of MultipleChoice props that matches partial input.
|
|
6
|
+
#
|
|
7
|
+
class MultiplePartial < MultipleChoice
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def index_by_text
|
|
11
|
+
matches = options.map.with_index { |text, idx| next idx if text.downcase.start_with?(input.downcase) }.compact
|
|
12
|
+
matches.first if matches.one?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|