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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/CHANGELOG.md +19 -0
  4. data/Rakefile +1 -0
  5. data/gamefic.gemspec +1 -1
  6. data/lib/gamefic/action.rb +68 -54
  7. data/lib/gamefic/active/cue.rb +84 -6
  8. data/lib/gamefic/active/messaging.rb +8 -0
  9. data/lib/gamefic/active/narratives.rb +101 -0
  10. data/lib/gamefic/active.rb +80 -92
  11. data/lib/gamefic/binding.rb +44 -0
  12. data/lib/gamefic/chapter.rb +30 -46
  13. data/lib/gamefic/command.rb +22 -40
  14. data/lib/gamefic/core_ext/array.rb +7 -7
  15. data/lib/gamefic/core_ext/string.rb +2 -2
  16. data/lib/gamefic/describable.rb +13 -0
  17. data/lib/gamefic/dispatcher.rb +35 -55
  18. data/lib/gamefic/entity.rb +6 -5
  19. data/lib/gamefic/expression.rb +1 -11
  20. data/lib/gamefic/logging.rb +3 -10
  21. data/lib/gamefic/match.rb +23 -0
  22. data/lib/gamefic/messenger.rb +1 -1
  23. data/lib/gamefic/narrative.rb +38 -74
  24. data/lib/gamefic/narrator.rb +77 -0
  25. data/lib/gamefic/node.rb +40 -8
  26. data/lib/gamefic/order.rb +53 -0
  27. data/lib/gamefic/plot.rb +41 -59
  28. data/lib/gamefic/props/default.rb +5 -17
  29. data/lib/gamefic/props/multiple_choice.rb +5 -2
  30. data/lib/gamefic/props/multiple_partial.rb +16 -0
  31. data/lib/gamefic/props/output.rb +7 -5
  32. data/lib/gamefic/props/yes_or_no.rb +2 -2
  33. data/lib/gamefic/props.rb +1 -0
  34. data/lib/gamefic/proxy/attr.rb +11 -0
  35. data/lib/gamefic/proxy/base.rb +3 -15
  36. data/lib/gamefic/proxy/config.rb +2 -2
  37. data/lib/gamefic/proxy/pick.rb +3 -3
  38. data/lib/gamefic/proxy/pick_ex.rb +11 -0
  39. data/lib/gamefic/proxy.rb +3 -71
  40. data/lib/gamefic/query/ascendants.rb +16 -0
  41. data/lib/gamefic/query/base.rb +47 -73
  42. data/lib/gamefic/query/children.rb +15 -0
  43. data/lib/gamefic/query/descendants.rb +17 -0
  44. data/lib/gamefic/query/extended.rb +20 -0
  45. data/lib/gamefic/query/family.rb +27 -0
  46. data/lib/gamefic/query/global.rb +22 -0
  47. data/lib/gamefic/query/integer.rb +32 -0
  48. data/lib/gamefic/query/myself.rb +13 -0
  49. data/lib/gamefic/query/parent.rb +13 -0
  50. data/lib/gamefic/query/result.rb +1 -1
  51. data/lib/gamefic/query/siblings.rb +12 -0
  52. data/lib/gamefic/query/subqueries.rb +17 -0
  53. data/lib/gamefic/query/text.rb +8 -9
  54. data/lib/gamefic/query.rb +11 -3
  55. data/lib/gamefic/request.rb +60 -0
  56. data/lib/gamefic/response.rb +46 -72
  57. data/lib/gamefic/scanner/nesting.rb +6 -6
  58. data/lib/gamefic/scanner/result.rb +3 -0
  59. data/lib/gamefic/scanner/strict.rb +14 -4
  60. data/lib/gamefic/scanner.rb +11 -6
  61. data/lib/gamefic/scene/active_choice.rb +75 -0
  62. data/lib/gamefic/scene/activity.rb +7 -3
  63. data/lib/gamefic/scene/base.rb +123 -0
  64. data/lib/gamefic/scene/conclusion.rb +4 -1
  65. data/lib/gamefic/scene/multiple_choice.rb +14 -11
  66. data/lib/gamefic/scene/pause.rb +5 -1
  67. data/lib/gamefic/scene/yes_or_no.rb +9 -0
  68. data/lib/gamefic/scene.rb +2 -1
  69. data/lib/gamefic/scriptable/hooks.rb +161 -0
  70. data/lib/gamefic/scriptable/queries.rb +38 -29
  71. data/lib/gamefic/scriptable/responses.rb +70 -0
  72. data/lib/gamefic/scriptable/scenes.rb +88 -115
  73. data/lib/gamefic/scriptable/seeds.rb +69 -0
  74. data/lib/gamefic/scriptable/syntaxes.rb +29 -0
  75. data/lib/gamefic/scriptable.rb +14 -199
  76. data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
  77. data/lib/gamefic/scripting/hooks.rb +45 -0
  78. data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
  79. data/lib/gamefic/scripting/responses.rb +21 -0
  80. data/lib/gamefic/scripting/scenes.rb +57 -0
  81. data/lib/gamefic/scripting/seeds.rb +10 -0
  82. data/lib/gamefic/scripting/syntaxes.rb +13 -0
  83. data/lib/gamefic/scripting.rb +43 -0
  84. data/lib/gamefic/subplot.rb +11 -22
  85. data/lib/gamefic/syntax.rb +39 -24
  86. data/lib/gamefic/version.rb +1 -1
  87. data/lib/gamefic.rb +6 -7
  88. metadata +38 -41
  89. data/lib/gamefic/active/epic.rb +0 -74
  90. data/lib/gamefic/active/take.rb +0 -67
  91. data/lib/gamefic/block.rb +0 -28
  92. data/lib/gamefic/callback.rb +0 -16
  93. data/lib/gamefic/proxy/plot_pick.rb +0 -11
  94. data/lib/gamefic/query/abstract.rb +0 -12
  95. data/lib/gamefic/query/general.rb +0 -41
  96. data/lib/gamefic/query/scoped.rb +0 -27
  97. data/lib/gamefic/rulebook/calls.rb +0 -86
  98. data/lib/gamefic/rulebook/events.rb +0 -65
  99. data/lib/gamefic/rulebook/hooks.rb +0 -57
  100. data/lib/gamefic/rulebook/scenes.rb +0 -68
  101. data/lib/gamefic/rulebook.rb +0 -125
  102. data/lib/gamefic/scene/default.rb +0 -88
  103. data/lib/gamefic/scope/base.rb +0 -44
  104. data/lib/gamefic/scope/children.rb +0 -16
  105. data/lib/gamefic/scope/descendants.rb +0 -16
  106. data/lib/gamefic/scope/family.rb +0 -43
  107. data/lib/gamefic/scope/myself.rb +0 -13
  108. data/lib/gamefic/scope/parent.rb +0 -13
  109. data/lib/gamefic/scope/siblings.rb +0 -14
  110. data/lib/gamefic/scope.rb +0 -9
  111. data/lib/gamefic/scriptable/actions.rb +0 -137
  112. data/lib/gamefic/scriptable/events.rb +0 -71
  113. data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
  114. data/lib/gamefic/snapshot.rb +0 -44
  115. data/lib/gamefic/stage.rb +0 -51
  116. data/lib/gamefic/syntax/template.rb +0 -67
  117. data/lib/gamefic/vault.rb +0 -52
@@ -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 message
79
- Query::Scoped.new(Scope::Descendants).select(self)
80
- .that_are(Active, proc(&:acting?))
81
- .each { |actor| actor.tell message }
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
@@ -12,7 +12,7 @@ module Gamefic
12
12
 
13
13
  # @param verb [Symbol, nil]
14
14
  # @param tokens [Array<String>]
15
- def initialize verb, tokens
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
@@ -16,17 +16,10 @@ module Gamefic
16
16
 
17
17
  class << self
18
18
  def logger
19
- @logger ||= select_logger.tap do |l|
20
- l.formatter = proc { |sev, _dt, _prog, msg| "[#{sev}] #{msg}\n" }
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
@@ -48,7 +48,7 @@ module Gamefic
48
48
  @buffers.last
49
49
  end
50
50
 
51
- # Clear the buffered messages.
51
+ # Clear the current buffer.
52
52
  #
53
53
  # @return [String] The flushed message
54
54
  def flush
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
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
- extend Scriptable
11
+ include Scripting
12
+ # @!parse extend Gamefic::Scriptable
12
13
 
13
- include Logging
14
- include Scriptable::Actions
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
- attr_reader :rulebook
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
- rulebook.scenes.introductions.each do |scene|
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 active
80
- active.epic.add self
81
- player_vault.add active
82
- entity_vault.add active
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 active
91
- active.epic.delete self
92
- player_vault.delete active
93
- entity_vault.delete active
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
- def ready
98
- rulebook.run_ready_blocks
99
- end
100
-
101
- def update
102
- rulebook.run_update_blocks
103
- end
104
-
105
- # @return [Object]
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
- def attach cache
113
- @rulebook = cache
72
+ # @return [String]
73
+ def save
74
+ Marshal.dump(self)
114
75
  end
115
76
 
116
- def hydrate
117
- script
118
- post_script
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 klass
83
+ def self.inherited(klass)
122
84
  super
123
- klass.blocks.concat blocks
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.parent = self }
68
+ def take *children, relation: nil
69
+ children.flatten.each { |child| child.put self, relation }
51
70
  children
52
71
  end
53
72
 
54
- # Determine if external objects can interact with this object's children.
55
- # For example, a game can designate that the contents of a bowl are
56
- # accessible, while the contents of a locked safe are not.
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 [Boolean]
59
- def accessible?
60
- true
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
- def seed
9
- super
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 chapters
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
- subplots.each(&:update)
13
+ @chapters = self.class.appended_chapter_map.map { |chap, config| chap.new(self, **unproxy(config)) }
41
14
  end
42
15
 
43
- # Remove an actor from the game.
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 detach
82
- cache = [@rulebook]
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 attach(cache)
89
- super(cache.shift)
90
- subplots.each { |subplot| subplot.attach cache.shift }
47
+ def self.appended_chapter_map
48
+ @appended_chapter_map ||= {}
91
49
  end
92
50
 
93
- def hydrate
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(&:hydrate)
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 self.append chapter
99
- appended_chapters.add chapter
80
+ def responses_for(*verbs)
81
+ super + chapters.flat_map { |chap| chap.responses_for(*verbs) }
100
82
  end
101
83
 
102
- def self.appended_chapters
103
- @appended_chapters ||= Set.new
84
+ def syntaxes
85
+ super + chapters.flat_map(&:syntaxes)
104
86
  end
105
87
 
106
- def self.restore data
107
- Snapshot.restore data
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
- matches = options.map.with_index { |text, idx| next idx if text.downcase.start_with?(input.downcase) }.compact
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