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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -40
  3. data/.rspec-opal +2 -0
  4. data/.solargraph.yml +20 -3
  5. data/CHANGELOG.md +9 -0
  6. data/Rakefile +11 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/gamefic.gemspec +5 -2
  10. data/lib/gamefic/action.rb +52 -183
  11. data/lib/gamefic/active/cue.rb +25 -0
  12. data/lib/gamefic/active/epic.rb +68 -0
  13. data/lib/gamefic/active/messaging.rb +43 -0
  14. data/lib/gamefic/active/take.rb +69 -0
  15. data/lib/gamefic/active.rb +95 -192
  16. data/lib/gamefic/actor.rb +2 -0
  17. data/lib/gamefic/block.rb +28 -0
  18. data/lib/gamefic/command.rb +16 -6
  19. data/lib/gamefic/core_ext/array.rb +4 -4
  20. data/lib/gamefic/core_ext/string.rb +10 -5
  21. data/lib/gamefic/describable.rb +39 -65
  22. data/lib/gamefic/dispatcher.rb +63 -32
  23. data/lib/gamefic/entity.rb +44 -19
  24. data/lib/gamefic/logging.rb +32 -0
  25. data/lib/gamefic/messenger.rb +66 -0
  26. data/lib/gamefic/narrative.rb +104 -0
  27. data/lib/gamefic/node.rb +44 -53
  28. data/lib/gamefic/plot.rb +60 -93
  29. data/lib/gamefic/props/default.rb +41 -0
  30. data/lib/gamefic/props/multiple_choice.rb +65 -0
  31. data/lib/gamefic/props/pause.rb +11 -0
  32. data/lib/gamefic/props/yes_or_no.rb +21 -0
  33. data/lib/gamefic/props.rb +10 -0
  34. data/lib/gamefic/query/base.rb +45 -126
  35. data/lib/gamefic/query/general.rb +46 -0
  36. data/lib/gamefic/query/result.rb +20 -0
  37. data/lib/gamefic/query/scoped.rb +41 -0
  38. data/lib/gamefic/query/text.rb +30 -31
  39. data/lib/gamefic/query.rb +7 -15
  40. data/lib/gamefic/response.rb +118 -0
  41. data/lib/gamefic/rulebook/calls.rb +90 -0
  42. data/lib/gamefic/rulebook/events.rb +79 -0
  43. data/lib/gamefic/rulebook/hooks.rb +57 -0
  44. data/lib/gamefic/rulebook/scenes.rb +68 -0
  45. data/lib/gamefic/rulebook.rb +139 -0
  46. data/lib/gamefic/scanner.rb +103 -0
  47. data/lib/gamefic/scene/activity.rb +9 -17
  48. data/lib/gamefic/scene/conclusion.rb +6 -5
  49. data/lib/gamefic/scene/default.rb +88 -0
  50. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  51. data/lib/gamefic/scene/pause.rb +9 -13
  52. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  53. data/lib/gamefic/scene.rb +11 -7
  54. data/lib/gamefic/scope/base.rb +44 -0
  55. data/lib/gamefic/scope/children.rb +16 -0
  56. data/lib/gamefic/scope/family.rb +20 -0
  57. data/lib/gamefic/scope/myself.rb +13 -0
  58. data/lib/gamefic/scope/parent.rb +13 -0
  59. data/lib/gamefic/scope/siblings.rb +14 -0
  60. data/lib/gamefic/scope.rb +8 -0
  61. data/lib/gamefic/scriptable/actions.rb +156 -0
  62. data/lib/gamefic/scriptable/entities.rb +76 -0
  63. data/lib/gamefic/scriptable/events.rb +65 -0
  64. data/lib/gamefic/scriptable/proxy.rb +55 -0
  65. data/lib/gamefic/scriptable/queries.rb +73 -0
  66. data/lib/gamefic/scriptable/scenes.rb +162 -0
  67. data/lib/gamefic/scriptable.rb +167 -73
  68. data/lib/gamefic/snapshot.rb +36 -0
  69. data/lib/gamefic/stage.rb +51 -0
  70. data/lib/gamefic/subplot.rb +51 -79
  71. data/lib/gamefic/syntax/template.rb +67 -0
  72. data/lib/gamefic/syntax.rb +102 -83
  73. data/lib/gamefic/vault.rb +50 -0
  74. data/lib/gamefic/version.rb +1 -1
  75. data/lib/gamefic.rb +26 -15
  76. data/spec-opal/spec_helper.rb +24 -0
  77. metadata +91 -29
  78. data/lib/gamefic/element.rb +0 -46
  79. data/lib/gamefic/keywords.rb +0 -52
  80. data/lib/gamefic/messaging.rb +0 -43
  81. data/lib/gamefic/plot/darkroom.rb +0 -120
  82. data/lib/gamefic/plot/host.rb +0 -42
  83. data/lib/gamefic/plot/snapshot.rb +0 -27
  84. data/lib/gamefic/query/children.rb +0 -9
  85. data/lib/gamefic/query/descendants.rb +0 -15
  86. data/lib/gamefic/query/external.rb +0 -39
  87. data/lib/gamefic/query/family.rb +0 -18
  88. data/lib/gamefic/query/itself.rb +0 -13
  89. data/lib/gamefic/query/matches.rb +0 -75
  90. data/lib/gamefic/query/parent.rb +0 -9
  91. data/lib/gamefic/query/siblings.rb +0 -13
  92. data/lib/gamefic/query/tree.rb +0 -17
  93. data/lib/gamefic/scene/base.rb +0 -142
  94. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  95. data/lib/gamefic/serialize.rb +0 -196
  96. data/lib/gamefic/world/callbacks.rb +0 -135
  97. data/lib/gamefic/world/commands.rb +0 -181
  98. data/lib/gamefic/world/entities.rb +0 -98
  99. data/lib/gamefic/world/playbook.rb +0 -233
  100. data/lib/gamefic/world/players.rb +0 -37
  101. data/lib/gamefic/world/scenes.rb +0 -228
  102. data/lib/gamefic/world.rb +0 -18
@@ -1,233 +0,0 @@
1
- require 'set'
2
-
3
- module Gamefic
4
- module World
5
- # A collection of rules for performing commands.
6
- #
7
- class Playbook
8
- ActionHook = Struct.new(:verb, :block)
9
-
10
- # An array of available syntaxes.
11
- #
12
- # @return [Array<Gamefic::Syntax>]
13
- attr_reader :syntaxes
14
-
15
- # An array of blocks to execute before actions.
16
- #
17
- # @return [Array<Proc>]
18
- attr_reader :before_actions
19
-
20
- # An array of blocks to execute after actions.
21
- #
22
- # @return [Array<Proc>]
23
- attr_reader :after_actions
24
-
25
- # @param commands [Hash]
26
- # @param syntaxes [Array<Syntax>, Set<Syntax>]
27
- # @param before_actions [Array]
28
- # @param after_actions [Array]
29
- def initialize commands: {}, syntaxes: [], before_actions: [], after_actions: []
30
- @commands = commands
31
- @syntax_set = syntaxes.to_set
32
- sort_syntaxes
33
- @before_actions = before_actions
34
- @after_actions = after_actions
35
- end
36
-
37
- # An array of available actions.
38
- #
39
- # @return [Array<Gamefic::Action>]
40
- def actions
41
- @commands.values.flatten
42
- end
43
-
44
- # An array of recognized verbs.
45
- #
46
- # @return [Array<Symbol>]
47
- def verbs
48
- @commands.keys
49
- end
50
-
51
- # Add a proc to be evaluated before a character executes an action.
52
- # When a verb is specified, the proc will only be evaluated if the
53
- # action's verb matches it.
54
- #
55
- # @param verb [Symbol, nil]
56
- # @yieldparam [Gamefic::Action]
57
- def before_action verb = nil, &block
58
- @before_actions.push ActionHook.new(verb, block)
59
- end
60
- alias validate before_action
61
-
62
- # Add a proc to be evaluated after a character executes an action.
63
- # When a verb is specified, the proc will only be evaluated if the
64
- # action's verb matches it.
65
- #
66
- # @param verb [Symbol, nil]
67
- # @yieldparam [Gamefic::Action]
68
- def after_action verb = nil, &block
69
- @after_actions.push ActionHook.new(verb, block)
70
- end
71
-
72
- # Get an Array of all Actions associated with the specified verb.
73
- #
74
- # @param verb [Symbol] The Symbol for the verb (e.g., :go or :look)
75
- # @return [Array<Class<Action>>] The verb's associated Actions
76
- def actions_for verb
77
- @commands[verb] || []
78
- end
79
-
80
- # Create an Action that responds to a command.
81
- # An Action uses the command argument to identify the imperative verb that
82
- # triggers the action.
83
- # It can also accept queries to tokenize the remainder of the input and
84
- # filter for particular entities or properties.
85
- # The block argument contains the code to be executed when the input
86
- # matches all of the Action's criteria (i.e., verb and queries).
87
- #
88
- # @example A simple Action.
89
- # respond :salute do |actor|
90
- # actor.tell "Hello, sir!"
91
- # end
92
- # # The command "salute" will respond "Hello, sir!"
93
- #
94
- # @example An Action that accepts a Character
95
- # respond :salute, Use.visible(Character) do |actor, character|
96
- # actor.tell "#{The character} returns your salute."
97
- # end
98
- #
99
- # @param verb [Symbol] An imperative verb for the command
100
- # @param queries [Array<Query::Base>] Filters for the command's tokens
101
- # @yieldparam [Gamefic::Actor]
102
- # @return [Class<Gamefic::Action>]
103
- def respond(verb, *queries, &proc)
104
- act = Action.subclass verb, *queries, &proc
105
- add_action act
106
- act
107
- end
108
-
109
- # Create a Meta Action that responds to a command.
110
- # Meta Actions are very similar to standard Actions, except the Plot
111
- # understands them to be commands that operate above and/or outside of the
112
- # actual game world. Examples of Meta Actions are commands that report the
113
- # player's current score, save and restore saved games, or list the game's
114
- # credits.
115
- #
116
- # @example A simple Meta Action
117
- # meta :credits do |actor|
118
- # actor.tell "This game was written by John Smith."
119
- # end
120
- #
121
- # @param verb [Symbol] An imperative verb for the command
122
- # @param queries [Array<Query::Base>] Filters for the command's tokens
123
- # @yieldparam [Gamefic::Actor]
124
- # @return [Class<Gamefic::Action>]
125
- def meta(verb, *queries, &proc)
126
- act = Action.subclass verb, *queries, meta: true, &proc
127
- add_action act
128
- act
129
- end
130
-
131
- # Create an alternate Syntax for an Action.
132
- # The command and its translation can be parameterized.
133
- #
134
- # @example Create a synonym for the Inventory Action.
135
- # interpret "catalogue", "inventory"
136
- # # The command "catalogue" will be translated to "inventory"
137
- #
138
- # @example Create a parameterized synonym for the Look Action.
139
- # interpret "scrutinize :entity", "look :entity"
140
- # # The command "scrutinize chair" will be translated to "look chair"
141
- #
142
- # @param input [String] The format of the original command
143
- # @param translation [String] The format of the translated command
144
- # @return [Syntax] the Syntax object
145
- def interpret(input, translation)
146
- syn = Syntax.new(input, translation)
147
- add_syntax syn
148
- syn
149
- end
150
-
151
- # Get a Dispatcher to select actions that can potentially be executed
152
- # from the specified command string.
153
- #
154
- # @param actor [Actor]
155
- # @param text [String]
156
- # @return [Dispatcher]
157
- def dispatch(actor, text)
158
- commands = Syntax.tokenize(text, actor.syntaxes)
159
- actions = commands.flat_map { |cmd| actions_for(cmd.verb).reject(&:hidden?) }
160
- Dispatcher.new(actor, commands, sort_and_reduce_actions(actions))
161
- end
162
-
163
- # Get an array of actions, derived from the specified verb and params,
164
- # that the actor can potentially execute.
165
- #
166
- # @return [Array<Gamefic::Action>]
167
- def dispatch_from_params actor, verb, params
168
- available = actions_for(verb)
169
- Dispatcher.new(actor, [Command.new(verb, params)], sort_and_reduce_actions(available))
170
- end
171
-
172
- # Duplicate the playbook.
173
- # This method will duplicate the commands hash and the syntax array so
174
- # the new playbook can be modified without affecting the original.
175
- #
176
- # @return [Playbook]
177
- def dup
178
- Playbook.new commands: @commands.dup, syntaxes: @syntaxes.dup
179
- end
180
-
181
- def freeze
182
- @commands.freeze
183
- @syntaxes.freeze
184
- end
185
-
186
- private
187
-
188
- def add_action(action)
189
- @commands[action.verb] ||= []
190
- @commands[action.verb].push action
191
- generate_default_syntax action
192
- end
193
-
194
- def generate_default_syntax action
195
- user_friendly = action.verb.to_s.gsub(/_/, ' ')
196
- args = []
197
- used_names = []
198
- action.queries.each do |_c|
199
- num = 1
200
- new_name = ":var"
201
- while used_names.include? new_name
202
- num += 1
203
- new_name = ":var#{num}"
204
- end
205
- used_names.push new_name
206
- user_friendly += " #{new_name}"
207
- args.push new_name
208
- end
209
- add_syntax Syntax.new(user_friendly.strip, "#{action.verb} #{args.join(' ')}") unless action.verb.to_s.start_with?('_')
210
- end
211
-
212
- def add_syntax syntax
213
- raise "No actions exist for \"#{syntax.verb}\"" if @commands[syntax.verb].nil?
214
- sort_syntaxes if @syntax_set.add?(syntax)
215
- end
216
-
217
- def sort_and_reduce_actions arr
218
- arr.sort_by.with_index { |a, i| [a.rank, i] }.reverse.uniq
219
- end
220
-
221
- def sort_syntaxes
222
- @syntaxes = @syntax_set.sort do |a, b|
223
- if a.token_count == b.token_count
224
- # For syntaxes of the same length, sort first word
225
- b.first_word <=> a.first_word
226
- else
227
- b.token_count <=> a.token_count
228
- end
229
- end
230
- end
231
- end
232
- end
233
- end
@@ -1,37 +0,0 @@
1
- module Gamefic
2
- module World
3
- module Players
4
- include Gamefic::World::Entities
5
- include Gamefic::World::Commands
6
-
7
- # An array of entities that are currently connected to users.
8
- #
9
- # @return [Array<Gamefic::Actor>]
10
- def players
11
- @players ||= []
12
- end
13
-
14
- def player_class cls = nil
15
- STDERR.puts "Modifying player_class this way is deprecated. Use set_player_class instead" unless cls.nil?
16
- @player_class = cls unless cls.nil?
17
- @player_class ||= Gamefic::Actor
18
- end
19
-
20
- # @param cls [Class]
21
- def set_player_class cls
22
- unless cls < Gamefic::Active && cls <= Gamefic::Entity
23
- raise ArgumentError, "Player class must be an active entity"
24
- end
25
- @player_class = cls
26
- end
27
-
28
- # Make a character that a player will control on introduction.
29
- #
30
- # @return [Gamefic::Actor]
31
- def make_player_character
32
- cast player_class, name: 'yourself', synonyms: 'self myself you me', proper_named: true
33
- end
34
- alias get_player_character make_player_character
35
- end
36
- end
37
- end
@@ -1,228 +0,0 @@
1
- module Gamefic
2
- module World
3
- module Scenes
4
- include Commands
5
- include Players
6
-
7
- # @return [Class<Gamefic::Scene::Activity>]
8
- def default_scene
9
- @default_scene ||= Scene::Activity
10
- end
11
-
12
- # @return [Class<Gamefic::Scene::Conclusion>]
13
- def default_conclusion
14
- @default_conclusion ||= Scene::Conclusion
15
- end
16
-
17
- # Add a block to be executed when a player is added to the game.
18
- # Each Plot can only have one introduction. Subsequent calls will
19
- # overwrite the existing one.
20
- #
21
- # @example Welcome the player to the game
22
- # introduction do |actor|
23
- # actor.tell "Welcome to the game!"
24
- # end
25
- #
26
- # @yieldparam [Gamefic::Actor]
27
- def introduction(&proc)
28
- @introduction = proc
29
- end
30
-
31
- # Introduce a player to the game.
32
- # This method is typically called by the Engine that manages game execution.
33
- #
34
- # @param [Gamefic::Actor]
35
- # @return [void]
36
- def introduce(player)
37
- player.playbooks.push playbook unless player.playbooks.include?(playbook)
38
- player.cue default_scene
39
- players.push player
40
- @introduction&.call(player)
41
- end
42
-
43
- # Create a multiple-choice scene.
44
- # The user will be required to make a valid choice to continue.
45
- #
46
- # @example
47
- # @scene = multiple_choice 'Go to work', 'Go to school' do |actor, scene|
48
- # # Assuming user selected the first choice:
49
- # scene.selection #=> 'Go to work'
50
- # scene.index #=> 0
51
- # scene.number #=> 1
52
- # end
53
- #
54
- # @yieldparam [Gamefic::Actor]
55
- # @yieldparam [Gamefic::Scene::MultipleChoice]
56
- # @return [Class<Gamefic::Scene::MultipleChoice>]
57
- def multiple_choice *choices, &block
58
- s = Scene::MultipleChoice.subclass do |_actor, scene|
59
- scene.options.concat choices
60
- scene.on_finish &block
61
- end
62
- scene_classes.push s
63
- s
64
- end
65
-
66
- # Create a yes-or-no scene.
67
- # The user will be required to answer Yes or No to continue.
68
- #
69
- # @example
70
- # @scene = yes_or_no 'What is your answer?' do |actor, scene|
71
- # if scene.yes?
72
- # actor.tell "You said yes."
73
- # else
74
- # actor.tell "You said no."
75
- # end
76
- # end
77
- #
78
- # @param prompt [String, nil]
79
- # @yieldparam [Gamefic::Actor]
80
- # @yieldparam [Gamefic::Scene::YesOrNo]
81
- # @return [Class<Gamefic::Scene::YesOrNo>]
82
- def yes_or_no prompt = nil, &block
83
- s = Scene::YesOrNo.subclass do |_actor, scene|
84
- scene.prompt = prompt
85
- scene.on_finish &block
86
- end
87
- scene_classes.push s
88
- s
89
- end
90
-
91
- # Create a scene with custom processing on user input.
92
- #
93
- # @example Echo the user's response
94
- # @scene = question 'What do you say?' do |actor, scene|
95
- # actor.tell "You said #{scene.input}"
96
- # end
97
- #
98
- # @param prompt [String]
99
- # @yieldparam [Gamefic::Actor]
100
- # @yieldparam [Gamefic::Scene::Base]
101
- # @return [Class<Gamefic::Scene::Base>]
102
- def question prompt = 'What is your answer?', &block
103
- s = Scene::Base.subclass do |_actor, scene|
104
- scene.prompt = prompt
105
- scene.on_finish &block
106
- end
107
- scene_classes.push s
108
- s
109
- end
110
-
111
- # Create a scene that pauses the game.
112
- # This scene will execute the specified block and wait for input from the
113
- # the user (e.g., pressing Enter) to continue.
114
- #
115
- # @example
116
- # @scene = pause 'Continue' do |actor|
117
- # actor.tell "After you continue, you will be prompted for a command."
118
- # actor.prepare default_scene
119
- # end
120
- #
121
- # @param prompt [String, nil] The text to display when prompting the user to continue
122
- # @yieldparam [Gamefic::Actor]
123
- # @return [Class<Gamefic::Scene::Pause>]
124
- def pause prompt = nil, &block
125
- s = Scene::Pause.subclass do |actor, scene|
126
- scene.prompt = prompt unless prompt.nil?
127
- block&.call(actor, scene)
128
- end
129
- scene_classes.push s
130
- s
131
- end
132
-
133
- # Create a conclusion.
134
- # The game (or the character's participation in it) will end after this
135
- # scene is complete.
136
- #
137
- # @example
138
- # @scene = conclusion do |actor|
139
- # actor.tell 'Game over'
140
- # end
141
- #
142
- # @yieldparam [Gamefic::Actor]
143
- # @return [Class<Gamefic::Scene::Conclusion>]
144
- def conclusion &block
145
- s = Scene::Conclusion.subclass &block
146
- scene_classes.push s
147
- s
148
- end
149
-
150
- # Create a custom scene.
151
- #
152
- # Custom scenes should always specify the next scene to be cued or
153
- # prepared. If not, the scene will get repeated on the next turn.
154
- #
155
- # This method creates a Scene::Base by default. You can customize other
156
- # scene types by specifying the class to create.
157
- #
158
- # @example Ask the user for a name
159
- # @scene = custom do |actor, scene|
160
- # scene.prompt = "What's your name?"
161
- # scene.on_finish do
162
- # actor.name = scene.input
163
- # actor.tell "Hello, #{actor.name}!"
164
- # actor.cue default_scene
165
- # end
166
- # end
167
- #
168
- # @param cls [Class<Scene::Base>] The class of scene to be instantiated.
169
- # @yieldparam [Gamefic::Actor]
170
- # @return [Class<Gamefic::Scene::Base>]
171
- def custom cls = Scene::Base, &block
172
- s = cls.subclass &block
173
- scene_classes.push s
174
- s
175
- end
176
-
177
- # Choose a new scene based on a list of options.
178
- # This is a specialized type of multiple-choice scene that determines
179
- # which scene to cue based on a Hash of choices and scene keys.
180
- #
181
- # @example Select a scene
182
- # scene_one = pause do |actor|
183
- # actor.tell "You went to scene one"
184
- # end
185
- #
186
- # scene_two = pause do |actor|
187
- # actor.tell "You went to scene two"
188
- # end
189
- #
190
- # select_one_or_two = multiple_scene "One" => scene_one, "Two" => scene_two
191
- #
192
- # introduction do |actor|
193
- # actor.cue select_one_or_two # The actor will be prompted to select "one" or "two" and get sent to the corresponding scene
194
- # end
195
- #
196
- # @example Customize options
197
- # scene_one = pause # do...
198
- # scene_two = pause # do...
199
- #
200
- # # Some event in the game sets actor[:can_go_to_scene_two] to true
201
- #
202
- # select_one_or_two = multiple_scene do |actor, scene|
203
- # scene.map "Go to scene one", scene_one
204
- # scene.map "Go to scene two", scene_two if actor[:can_go_to_scene_two]
205
- # end
206
- #
207
- # @param map [Hash] A Hash of options and associated scenes.
208
- # @yieldparam [Gamefic::Actor]
209
- # @yieldparam [Gamefic::Scene::MultipleScene]
210
- # @return [Class<Gamefic::Scene::MultipleScene>]
211
- def multiple_scene map = {}, &block
212
- s = Scene::MultipleScene.subclass do |actor, scene|
213
- map.each_pair do |k, v|
214
- scene.map k, v
215
- end
216
- block&.call actor, scene
217
- end
218
- scene_classes.push s
219
- s
220
- end
221
-
222
- # @return [Array<Class<Gamefic::Scene::Base>>]
223
- def scene_classes
224
- @scene_classes ||= []
225
- end
226
- end
227
- end
228
- end
data/lib/gamefic/world.rb DELETED
@@ -1,18 +0,0 @@
1
- module Gamefic
2
- # A collection of classes and modules related to generating a world model.
3
- #
4
- module World
5
- autoload :Playbook, 'gamefic/world/playbook'
6
- autoload :Entities, 'gamefic/world/entities'
7
- autoload :Commands, 'gamefic/world/commands'
8
- autoload :Callbacks, 'gamefic/world/callbacks'
9
- autoload :Scenes, 'gamefic/world/scenes'
10
- autoload :Players, 'gamefic/world/players'
11
-
12
- include Entities
13
- include Commands
14
- include Callbacks
15
- include Scenes
16
- include Players
17
- end
18
- end