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
@@ -2,29 +2,32 @@
2
2
 
3
3
  module Gamefic
4
4
  module Scene
5
- # A scene that presents a list of choices and processes the player's input.
6
- # If the input is not a valid choice, the scene gets recued.
5
+ # A scene that presents a list of choices. If the input does not match any
6
+ # of the choices, the scene gets recued.
7
7
  #
8
- class MultipleChoice < Default
8
+ class MultipleChoice < Base
9
9
  use_props_class Props::MultipleChoice
10
10
 
11
- def start actor, props
11
+ def initialize(...)
12
12
  super
13
- props.output[:options] = props.options
13
+ props.options.concat(context[:options] || [])
14
14
  end
15
15
 
16
- def finish actor, props
16
+ def start
17
17
  super
18
- return if props.index
18
+ props.output[:options] = props.options
19
+ props
20
+ end
21
+
22
+ def finish
23
+ return super if props.selected?
19
24
 
20
25
  actor.tell format(props.invalid_message, input: props.input)
21
26
  actor.recue
22
27
  end
23
28
 
24
- def run_finish_blocks actor, props
25
- return unless props.index
26
-
27
- super
29
+ def self.type
30
+ 'MultipleChoice'
28
31
  end
29
32
  end
30
33
  end
@@ -6,8 +6,12 @@ module Gamefic
6
6
  # before proceeding to on_finish. The user input itself is ignored by
7
7
  # default.
8
8
  #
9
- class Pause < Default
9
+ class Pause < Base
10
10
  use_props_class Props::Pause
11
+
12
+ def self.type
13
+ 'Pause'
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -6,6 +6,15 @@ module Gamefic
6
6
  #
7
7
  class YesOrNo < MultipleChoice
8
8
  use_props_class Props::YesOrNo
9
+
10
+ def initialize(...)
11
+ super
12
+ props.options
13
+ end
14
+
15
+ def self.type
16
+ 'YesOrNo'
17
+ end
9
18
  end
10
19
  end
11
20
  end
data/lib/gamefic/scene.rb CHANGED
@@ -5,9 +5,10 @@ module Gamefic
5
5
  # the output to be sent to the player. The finish processes player input.
6
6
  #
7
7
  module Scene
8
- require 'gamefic/scene/default'
8
+ require 'gamefic/scene/base'
9
9
  require 'gamefic/scene/activity'
10
10
  require 'gamefic/scene/multiple_choice'
11
+ require 'gamefic/scene/active_choice'
11
12
  require 'gamefic/scene/pause'
12
13
  require 'gamefic/scene/yes_or_no'
13
14
  require 'gamefic/scene/conclusion'
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Scriptable hook methods are class methods that define procs to be
6
+ # executed in response to various game events.
7
+ #
8
+ module Hooks
9
+ # Define a callback to be executed before a command is processed. The
10
+ # callback accepts two parameters, the Actor and the Command.
11
+ #
12
+ # The optional verbs parameter can be used to restrict the callback to
13
+ # commands that use one of the specified verbs. If no verbs are provided,
14
+ # the callback will be executed before every command.
15
+ #
16
+ # Calling Command#cancel will prevent the command from being performed.
17
+ #
18
+ # @example Cancel non-meta commands when the actor does not have a parent
19
+ # before_command do |actor, command|
20
+ # next if actor.parent || command.meta?
21
+ #
22
+ # actor.tell "You can't do anything while you're in limbo."
23
+ # command.cancel
24
+ # end
25
+ #
26
+ # @param verbs [Array<Symbol>]
27
+ # @yieldparam [Actor]
28
+ # @yieldparam [Command]
29
+ # @yieldself [Object<self>]
30
+ def before_command(*verbs, &block)
31
+ before_commands.push(proc do |actor, command|
32
+ instance_exec(actor, command, &block) if verbs.empty? || verbs.include?(command.verb)
33
+ end)
34
+ end
35
+
36
+ # Define a callback to be executed after a command is processed. The
37
+ # callback accepts two parameters, the Actor and the Command.
38
+ #
39
+ # The optional verbs parameter can be used to restrict the callback to
40
+ # commands that use one of the specified verbs. If no verbs are provided,
41
+ # the callback will be executed after every command.
42
+ #
43
+ # @param verbs [Array<Symbol>]
44
+ # @yieldparam [Actor]
45
+ # @yieldparam [Command]
46
+ # @yieldself [Object<self>]
47
+ def after_command(*verbs, &block)
48
+ after_commands.push(proc do |actor, command|
49
+ instance_exec(actor, command, &block) if verbs.empty? || verbs.include?(command.verb)
50
+ end)
51
+ end
52
+
53
+ # Define a callback to be executed after a scene starts.
54
+ #
55
+ # @yieldself [Object<self>]
56
+ def on_ready(&block)
57
+ ready_blocks.push block
58
+ end
59
+
60
+ # Define a callback to be executed for each participating player after
61
+ # a scene starts.
62
+ #
63
+ # @yieldparam [Actor]
64
+ # @yieldself [Object<self>]
65
+ def on_player_ready(&block)
66
+ ready_blocks.push(proc { players.each { |player| instance_exec(player, &block) } })
67
+ end
68
+
69
+ # Define a callback to be executed after a scene finishes.
70
+ #
71
+ # @yieldself [Object<self>]
72
+ def on_update(&block)
73
+ update_blocks.push block
74
+ end
75
+
76
+ # Define a callback to be executed for each participating player after
77
+ # a scene finishes.
78
+ #
79
+ # @yieldparam [Actor]
80
+ # @yieldself [Object<self>]
81
+ def on_player_update(&block)
82
+ update_blocks.push(proc { players.each { |player| instance_exec(player, &block) } })
83
+ end
84
+
85
+ # Define a callback that modifies the output sent to the player at the
86
+ # beginning of a game turn. The callback accepts two parameters, the
87
+ # Actor and the Props::Output.
88
+ #
89
+ # Narrators execute player_output blocks after starting a scene and
90
+ # executing ready blocks. The output gets sent to players' game clients
91
+ # as JSON objects to be rendered before prompting the player for input.
92
+ #
93
+ # @example Add a player's parent to the output as a custom hash value.
94
+ # on_player_output do |actor, output|
95
+ # output[:parent_name] = actor.parent&.name
96
+ # end
97
+ #
98
+ # @yieldparam [Actor]
99
+ # @yieldparam [Props::Output]
100
+ # @yieldself [Object<self>]
101
+ def on_player_output(&block)
102
+ player_output_blocks.push(block)
103
+ end
104
+
105
+ # Define a callback that gets executed when a narrative reaches a
106
+ # conclusion.
107
+ #
108
+ # @yieldself [Object<self>]
109
+ def on_conclude(&block)
110
+ conclude_blocks.push(block)
111
+ end
112
+
113
+ # Define a callback that gets executed when a player reaches a
114
+ # conclusion.
115
+ #
116
+ # @note A player can conclude participation in a narrative without the
117
+ # narrative itself concluding.
118
+ #
119
+ # @yieldparam [Actor]
120
+ # @yieldself [Object<self>]
121
+ def on_player_conclude(&block)
122
+ player_conclude_blocks.push(block)
123
+ end
124
+
125
+ # @return [Array<Proc>]
126
+ def before_commands
127
+ @before_commands ||= []
128
+ end
129
+
130
+ # @return [Array<Proc>]
131
+ def after_commands
132
+ @after_commands ||= []
133
+ end
134
+
135
+ # @return [Array<Proc>]
136
+ def ready_blocks
137
+ @ready_blocks ||= []
138
+ end
139
+
140
+ # @return [Array<Proc>]
141
+ def update_blocks
142
+ @update_blocks ||= []
143
+ end
144
+
145
+ # @return [Array<Proc>]
146
+ def player_output_blocks
147
+ @player_output_blocks ||= []
148
+ end
149
+
150
+ # @return [Array<Proc>]
151
+ def conclude_blocks
152
+ @conclude_blocks ||= []
153
+ end
154
+
155
+ # @return [Array<Proc>]
156
+ def player_conclude_blocks
157
+ @player_conclude_blocks ||= []
158
+ end
159
+ end
160
+ end
161
+ end
@@ -5,26 +5,16 @@ module Gamefic
5
5
  # Scriptable methods related to creating action queries.
6
6
  #
7
7
  module Queries
8
- include Proxies
9
-
10
- # Define a query that searches the entire plot's entities.
8
+ # Define a query that searches all entities in the subject's epic.
11
9
  #
12
- # @param args [Array<Object>] Query arguments
13
- # @return [Query::General]
14
- def anywhere *args, ambiguous: false
15
- Query::General.new -> { entities }, *args, ambiguous: ambiguous, name: 'anywhere'
16
- end
17
-
18
- # Define a query that searches for abstract entities.
19
- #
20
- # An abstract entity is a pseudo-entity that is describable but does
21
- # not have a parent or children.
10
+ # If the subject is not an actor, the result will always be empty.
22
11
  #
23
12
  # @param args [Array<Object>] Query arguments
24
- # @return [Query::Abstract]
25
- def abstract *args, ambiguous: false
26
- Query::Abstract.new -> { entities }, *args, ambiguous: ambiguous
13
+ # @return [Query::Global]
14
+ def global *args
15
+ Query::Global.new(*args, name: 'global')
27
16
  end
17
+ alias anywhere global
28
18
 
29
19
  # Define a query that searches an actor's family of entities. The
30
20
  # results include the parent, siblings, children, and accessible
@@ -32,49 +22,58 @@ module Gamefic
32
22
  #
33
23
  # @param args [Array<Object>] Query arguments
34
24
  # @return [Query::Scoped]
35
- def available *args, ambiguous: false
36
- Query::Scoped.new Scope::Family, *args, ambiguous: ambiguous, name: 'available'
25
+ def available *args
26
+ Query::Family.new(*args, name: 'available')
37
27
  end
38
28
  alias family available
29
+ alias avail available
39
30
 
40
31
  # Define a query that returns the actor's parent.
41
32
  #
42
33
  # @param args [Array<Object>] Query arguments
43
34
  # @return [Query::Scoped]
44
- def parent *args, ambiguous: false
45
- Query::Scoped.new Scope::Parent, *args, ambiguous: ambiguous, name: 'parent'
35
+ def parent *args
36
+ Query::Parent.new(*args, name: 'parent')
46
37
  end
47
38
 
48
39
  # Define a query that searches an actor's children.
49
40
  #
50
41
  # @param args [Array<Object>] Query arguments
51
42
  # @return [Query::Scoped]
52
- def children *args, ambiguous: false
53
- Query::Scoped.new Scope::Children, *args, ambiguous: ambiguous, name: 'children'
43
+ def children *args
44
+ Query::Children.new(*args, name: 'children')
54
45
  end
55
46
 
56
47
  # Define a query that searches an actor's descendants.
57
48
  #
58
49
  # @param args [Array<Object>] Query arguments
59
50
  # @return [Query::Scoped]
60
- def descendants *args, ambiguous: false
61
- Query::Scoped.new Scope::Descendants, *args, ambiguous: ambiguous
51
+ def descendants *args
52
+ Query::Descendants.new(*args)
62
53
  end
63
54
 
64
55
  # Define a query that searches an actor's siblings.
65
56
  #
66
57
  # @param args [Array<Object>] Query arguments
67
58
  # @return [Query::Scoped]
68
- def siblings *args, ambiguous: false
69
- Query::Scoped.new Scope::Siblings, *args, ambiguous: ambiguous, name: 'siblings'
59
+ def siblings *args
60
+ Query::Siblings.new(*args, name: 'siblings')
61
+ end
62
+
63
+ # Define a query that searches an actor's siblings and their descendants.
64
+ #
65
+ # @param args [Array<Object>] Query arguments
66
+ # @return [Query::Scoped]
67
+ def extended *args
68
+ Query::Extended.new(*args, name: 'extended')
70
69
  end
71
70
 
72
71
  # Define a query that returns the actor itself.
73
72
  #
74
73
  # @param args [Array<Object>] Query arguments
75
74
  # @return [Query::Scoped]
76
- def myself *args, ambiguous: false
77
- Query::Scoped.new Scope::Myself, *args, ambiguous: ambiguous, name: 'myself'
75
+ def myself *args
76
+ Query::Myself.new(*args, name: 'myself')
78
77
  end
79
78
 
80
79
  # Define a query that performs a plaintext search. It can take a String
@@ -84,9 +83,19 @@ module Gamefic
84
83
  #
85
84
  # @param arg [String, Regexp] The string or regular expression to match
86
85
  # @return [Query::Text]
87
- def plaintext arg = /.*/
86
+ def plaintext(arg = /.*/)
88
87
  Query::Text.new arg, name: 'plaintext'
89
88
  end
89
+
90
+ # Define a query that matches integers. Unlike other queries, #integer
91
+ # does not take arguments. It will match and return an integer if the
92
+ # corresponding command token is an integer or the corresponding input is
93
+ # a string representation of an integer. A successful query returns the
94
+ # integer instead of an entity.
95
+ #
96
+ def integer
97
+ Query::Integer.new name: 'integer'
98
+ end
90
99
  end
91
100
  end
92
101
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ module Responses
6
+ include Queries
7
+ include Syntaxes
8
+
9
+ # Create a response to a command.
10
+ # A Response uses the `verb` argument to identify the imperative verb
11
+ # that triggers the action. It can also accept queries to tokenize the
12
+ # remainder of the input and filter for particular entities or
13
+ # properties. The `block`` argument is the proc to execute when the input
14
+ # matches all of the Response's criteria (i.e., verb and queries).
15
+ #
16
+ # @example A simple Response.
17
+ # respond :wave do |actor|
18
+ # actor.tell "Hello!"
19
+ # end
20
+ # # The command "wave" will respond "Hello!"
21
+ #
22
+ # @example A Response that accepts a Character
23
+ # respond :salute, available(Character) do |actor, character|
24
+ # actor.tell "#{The character} returns your salute."
25
+ # end
26
+ #
27
+ # @param verb [Symbol, String, nil] An imperative verb for the command
28
+ # @param args [Array<Object>] Filters for the command's tokens
29
+ # @yieldparam [Gamefic::Actor]
30
+ # @yieldself [Object<self>]
31
+ # @return [Response]
32
+ def respond verb, *args, &proc
33
+ response = Response.new(verb&.to_sym, *args, &proc)
34
+ responses.push response
35
+ syntaxes.push response.syntax
36
+ response
37
+ end
38
+
39
+ # Create a meta response to a command.
40
+ #
41
+ # @param verb [Symbol, String, nil] An imperative verb for the command
42
+ # @param args [Array<Object>] Filters for the command's tokens
43
+ # @yieldparam [Gamefic::Actor]
44
+ # @yieldself [Object<self>]
45
+ # @return [Response]
46
+ def meta verb, *args, &proc
47
+ response = Response.new(verb&.to_sym, *args, meta: true, &proc)
48
+ responses.push response
49
+ syntaxes.push response.syntax
50
+ response
51
+ end
52
+
53
+ # @return [Array<Response>]
54
+ def responses
55
+ @responses ||= []
56
+ end
57
+
58
+ # @return [Array<Response>]
59
+ def responses_for(*verbs)
60
+ symbols = verbs.map { |verb| verb&.to_sym }
61
+ responses.select { |response| symbols.include? response.verb }
62
+ end
63
+
64
+ # @return [Array<Symbol>]
65
+ def verbs
66
+ responses.select(&:verb).uniq(&:verb)
67
+ end
68
+ end
69
+ end
70
+ end