gamefic 2.4.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) 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 +18 -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 +58 -181
  11. data/lib/gamefic/active/cue.rb +25 -0
  12. data/lib/gamefic/active/epic.rb +73 -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 +111 -191
  16. data/lib/gamefic/actor.rb +2 -0
  17. data/lib/gamefic/block.rb +28 -0
  18. data/lib/gamefic/command.rb +6 -7
  19. data/lib/gamefic/composer.rb +68 -0
  20. data/lib/gamefic/core_ext/array.rb +4 -4
  21. data/lib/gamefic/core_ext/string.rb +10 -5
  22. data/lib/gamefic/describable.rb +39 -65
  23. data/lib/gamefic/dispatcher.rb +79 -41
  24. data/lib/gamefic/entity.rb +42 -19
  25. data/lib/gamefic/expression.rb +31 -0
  26. data/lib/gamefic/logging.rb +32 -0
  27. data/lib/gamefic/messenger.rb +66 -0
  28. data/lib/gamefic/narrative.rb +104 -0
  29. data/lib/gamefic/node.rb +44 -53
  30. data/lib/gamefic/plot.rb +60 -93
  31. data/lib/gamefic/props/default.rb +49 -0
  32. data/lib/gamefic/props/multiple_choice.rb +65 -0
  33. data/lib/gamefic/props/output.rb +82 -0
  34. data/lib/gamefic/props/pause.rb +11 -0
  35. data/lib/gamefic/props/yes_or_no.rb +21 -0
  36. data/lib/gamefic/props.rb +11 -0
  37. data/lib/gamefic/query/base.rb +64 -121
  38. data/lib/gamefic/query/general.rb +50 -0
  39. data/lib/gamefic/query/result.rb +20 -0
  40. data/lib/gamefic/query/scoped.rb +46 -0
  41. data/lib/gamefic/query/text.rb +43 -33
  42. data/lib/gamefic/query.rb +7 -15
  43. data/lib/gamefic/response.rb +112 -0
  44. data/lib/gamefic/rulebook/calls.rb +90 -0
  45. data/lib/gamefic/rulebook/events.rb +79 -0
  46. data/lib/gamefic/rulebook/hooks.rb +57 -0
  47. data/lib/gamefic/rulebook/scenes.rb +68 -0
  48. data/lib/gamefic/rulebook.rb +139 -0
  49. data/lib/gamefic/scanner.rb +130 -0
  50. data/lib/gamefic/scene/activity.rb +9 -17
  51. data/lib/gamefic/scene/conclusion.rb +6 -5
  52. data/lib/gamefic/scene/default.rb +88 -0
  53. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  54. data/lib/gamefic/scene/pause.rb +9 -13
  55. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  56. data/lib/gamefic/scene.rb +11 -7
  57. data/lib/gamefic/scope/base.rb +44 -0
  58. data/lib/gamefic/scope/children.rb +16 -0
  59. data/lib/gamefic/scope/family.rb +20 -0
  60. data/lib/gamefic/scope/myself.rb +13 -0
  61. data/lib/gamefic/scope/parent.rb +13 -0
  62. data/lib/gamefic/scope/siblings.rb +14 -0
  63. data/lib/gamefic/scope.rb +8 -0
  64. data/lib/gamefic/scriptable/actions.rb +156 -0
  65. data/lib/gamefic/scriptable/entities.rb +79 -0
  66. data/lib/gamefic/scriptable/events.rb +65 -0
  67. data/lib/gamefic/scriptable/proxy.rb +66 -0
  68. data/lib/gamefic/scriptable/queries.rb +73 -0
  69. data/lib/gamefic/scriptable/scenes.rb +162 -0
  70. data/lib/gamefic/scriptable.rb +167 -73
  71. data/lib/gamefic/snapshot.rb +44 -0
  72. data/lib/gamefic/stage.rb +51 -0
  73. data/lib/gamefic/subplot.rb +51 -79
  74. data/lib/gamefic/syntax/template.rb +67 -0
  75. data/lib/gamefic/syntax.rb +102 -83
  76. data/lib/gamefic/vault.rb +50 -0
  77. data/lib/gamefic/version.rb +1 -1
  78. data/lib/gamefic.rb +28 -15
  79. data/spec-opal/spec_helper.rb +24 -0
  80. metadata +94 -29
  81. data/lib/gamefic/element.rb +0 -46
  82. data/lib/gamefic/keywords.rb +0 -52
  83. data/lib/gamefic/messaging.rb +0 -43
  84. data/lib/gamefic/plot/darkroom.rb +0 -120
  85. data/lib/gamefic/plot/host.rb +0 -42
  86. data/lib/gamefic/plot/snapshot.rb +0 -27
  87. data/lib/gamefic/query/children.rb +0 -9
  88. data/lib/gamefic/query/descendants.rb +0 -15
  89. data/lib/gamefic/query/external.rb +0 -39
  90. data/lib/gamefic/query/family.rb +0 -18
  91. data/lib/gamefic/query/itself.rb +0 -13
  92. data/lib/gamefic/query/matches.rb +0 -75
  93. data/lib/gamefic/query/parent.rb +0 -9
  94. data/lib/gamefic/query/siblings.rb +0 -13
  95. data/lib/gamefic/query/tree.rb +0 -17
  96. data/lib/gamefic/scene/base.rb +0 -142
  97. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  98. data/lib/gamefic/serialize.rb +0 -196
  99. data/lib/gamefic/world/callbacks.rb +0 -135
  100. data/lib/gamefic/world/commands.rb +0 -181
  101. data/lib/gamefic/world/entities.rb +0 -98
  102. data/lib/gamefic/world/playbook.rb +0 -233
  103. data/lib/gamefic/world/players.rb +0 -37
  104. data/lib/gamefic/world/scenes.rb +0 -228
  105. 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