gamefic 1.7.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +16 -0
  5. data/.solargraph.yml +5 -0
  6. data/CHANGELOG.md +10 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +20 -0
  9. data/README.md +28 -0
  10. data/Rakefile +10 -0
  11. data/gamefic.gemspec +27 -0
  12. data/lib/gamefic.rb +7 -7
  13. data/lib/gamefic/action.rb +66 -60
  14. data/lib/gamefic/active.rb +331 -280
  15. data/lib/gamefic/actor.rb +8 -5
  16. data/lib/gamefic/command.rb +9 -7
  17. data/lib/gamefic/core_ext/array.rb +27 -49
  18. data/lib/gamefic/core_ext/string.rb +25 -16
  19. data/lib/gamefic/describable.rb +21 -23
  20. data/lib/gamefic/element.rb +47 -31
  21. data/lib/gamefic/entity.rb +6 -12
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -44
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +69 -91
  26. data/lib/gamefic/plot/darkroom.rb +80 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +14 -19
  29. data/lib/gamefic/query.rb +15 -18
  30. data/lib/gamefic/query/base.rb +50 -37
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/descendants.rb +2 -2
  33. data/lib/gamefic/query/external.rb +18 -14
  34. data/lib/gamefic/query/family.rb +3 -7
  35. data/lib/gamefic/query/matches.rb +75 -67
  36. data/lib/gamefic/query/parent.rb +0 -0
  37. data/lib/gamefic/query/siblings.rb +0 -0
  38. data/lib/gamefic/query/text.rb +12 -12
  39. data/lib/gamefic/query/tree.rb +17 -0
  40. data/lib/gamefic/scene.rb +0 -2
  41. data/lib/gamefic/scene/activity.rb +24 -26
  42. data/lib/gamefic/scene/base.rb +71 -10
  43. data/lib/gamefic/scene/conclusion.rb +1 -3
  44. data/lib/gamefic/scene/multiple_choice.rb +19 -14
  45. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  46. data/lib/gamefic/scene/pause.rb +8 -3
  47. data/lib/gamefic/scene/yes_or_no.rb +22 -10
  48. data/lib/gamefic/scriptable.rb +88 -0
  49. data/lib/gamefic/serialize.rb +223 -0
  50. data/lib/gamefic/subplot.rb +38 -35
  51. data/lib/gamefic/syntax.rb +15 -13
  52. data/lib/gamefic/version.rb +3 -3
  53. data/lib/gamefic/world.rb +18 -0
  54. data/lib/gamefic/world/callbacks.rb +135 -0
  55. data/lib/gamefic/world/commands.rb +184 -0
  56. data/lib/gamefic/{plot → world}/entities.rb +33 -35
  57. data/lib/gamefic/{plot → world}/playbook.rb +245 -240
  58. data/lib/gamefic/world/players.rb +37 -0
  59. data/lib/gamefic/world/scenes.rb +226 -0
  60. metadata +37 -88
  61. data/bin/gamefic +0 -9
  62. data/lib/gamefic/engine.rb +0 -7
  63. data/lib/gamefic/engine/base.rb +0 -59
  64. data/lib/gamefic/engine/tty.rb +0 -24
  65. data/lib/gamefic/grammar.rb +0 -13
  66. data/lib/gamefic/grammar/conjugator.rb +0 -20
  67. data/lib/gamefic/grammar/gender.rb +0 -11
  68. data/lib/gamefic/grammar/person.rb +0 -10
  69. data/lib/gamefic/grammar/plural.rb +0 -13
  70. data/lib/gamefic/grammar/pronouns.rb +0 -106
  71. data/lib/gamefic/grammar/tense.rb +0 -6
  72. data/lib/gamefic/grammar/verb_set.rb +0 -43
  73. data/lib/gamefic/grammar/verbs.rb +0 -26
  74. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  75. data/lib/gamefic/plot/articles.rb +0 -22
  76. data/lib/gamefic/plot/callbacks.rb +0 -126
  77. data/lib/gamefic/plot/commands.rb +0 -120
  78. data/lib/gamefic/plot/players.rb +0 -15
  79. data/lib/gamefic/plot/scenes.rb +0 -187
  80. data/lib/gamefic/plot/theater.rb +0 -73
  81. data/lib/gamefic/plot/you_mount.rb +0 -22
  82. data/lib/gamefic/scene/custom.rb +0 -9
  83. data/lib/gamefic/script.rb +0 -13
  84. data/lib/gamefic/script/base.rb +0 -42
  85. data/lib/gamefic/script/file.rb +0 -14
  86. data/lib/gamefic/script/text.rb +0 -14
  87. data/lib/gamefic/shell.rb +0 -76
  88. data/lib/gamefic/source.rb +0 -14
  89. data/lib/gamefic/source/base.rb +0 -12
  90. data/lib/gamefic/source/file.rb +0 -23
  91. data/lib/gamefic/source/text.rb +0 -16
  92. data/lib/gamefic/tester.rb +0 -19
  93. data/lib/gamefic/text.rb +0 -8
  94. data/lib/gamefic/text/ansi.rb +0 -53
  95. data/lib/gamefic/text/html.rb +0 -68
  96. data/lib/gamefic/text/html/conversions.rb +0 -250
  97. data/lib/gamefic/text/html/entities.rb +0 -9
  98. data/lib/gamefic/tty.rb +0 -10
  99. data/lib/gamefic/user.rb +0 -7
  100. data/lib/gamefic/user/base.rb +0 -29
  101. data/lib/gamefic/user/tty.rb +0 -38
@@ -1,3 +1,3 @@
1
- module Gamefic
2
- VERSION = '1.7.0'
3
- end
1
+ module Gamefic
2
+ VERSION = '2.1.0'
3
+ end
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,135 @@
1
+ module Gamefic
2
+ module World
3
+ module Callbacks
4
+ # Add a block to be executed on preparation of every turn.
5
+ #
6
+ # @example Increment a turn counter
7
+ # turn = 0
8
+ # on_ready do
9
+ # turn += 1
10
+ # end
11
+ #
12
+ def on_ready &block
13
+ ready_procs.push block
14
+ end
15
+
16
+ # Add a block to be executed after the Plot is finished updating a turn.
17
+ #
18
+ def on_update &block
19
+ update_procs.push block
20
+ end
21
+
22
+ # Add a block to be executed for each player at the beginning of a turn.
23
+ #
24
+ # @example Tell the player how many turns they've played.
25
+ # on_player_ready do |player|
26
+ # player[:turns] ||= 0
27
+ # if player[:turns] > 0
28
+ # player.tell "Turn #{player[:turns]}"
29
+ # end
30
+ # player[:turns] += 1
31
+ # end
32
+ #
33
+ # @yieldparam [Gamefic::Actor]
34
+ def on_player_ready &block
35
+ player_ready_procs.push block
36
+ end
37
+
38
+ # Add a block to be executed for each player before an update.
39
+ #
40
+ # @yieldparam[Gamefic::Actor]
41
+ def before_player_update &block
42
+ before_player_update_procs.push block
43
+ end
44
+
45
+ # Add a block to be executed for each player at the end of a turn.
46
+ #
47
+ # @yieldparam [Gamefic::Actor]
48
+ def on_player_update &block
49
+ player_update_procs.push block
50
+ end
51
+
52
+ # Add a block to be executed at the conclusion of the plot.
53
+ #
54
+ # @yieldparam [Gamefic::Actor]
55
+ def on_player_conclude &block
56
+ player_conclude_procs.push block
57
+ end
58
+
59
+ def player_conclude_procs
60
+ @player_conclude_procs ||= []
61
+ end
62
+
63
+ def ready_procs
64
+ @ready_procs ||= []
65
+ end
66
+
67
+ def update_procs
68
+ @update_procs ||= []
69
+ end
70
+
71
+ def player_ready_procs
72
+ @player_ready_procs ||= []
73
+ end
74
+
75
+ def before_player_update_procs
76
+ @before_player_update_procs ||= []
77
+ end
78
+
79
+ def player_update_procs
80
+ @player_update_procs ||= []
81
+ end
82
+
83
+ private
84
+
85
+ # Execute the on_ready blocks. This method is typically called by the
86
+ # Plot while beginning a turn.
87
+ #
88
+ def call_ready
89
+ ready_procs.each { |p| p.call }
90
+ end
91
+
92
+ # Execute the on_update blocks. This method is typically called by the
93
+ # Plot while ending a turn.
94
+ #
95
+ def call_update
96
+ update_procs.each { |p| p.call }
97
+ end
98
+
99
+ # Execute the before_player_update blocks for each player. This method is
100
+ # typically called by the Plot while updating a turn, immediately before
101
+ # processing player input.
102
+ #
103
+ def call_before_player_update
104
+ players.each { |player|
105
+ player.flush
106
+ before_player_update_procs.each { |block| block.call player }
107
+ }
108
+ end
109
+
110
+ # Execute the on_player_ready blocks for each player. This method is
111
+ # typically called by the Plot while beginning a turn, immediately after
112
+ # the on_ready blocks.
113
+ #
114
+ def call_player_ready
115
+ players.each { |player|
116
+ unless player.next_scene.nil? || !player.scene.finished?
117
+ player.cue player.next_scene, **player.next_options
118
+ end
119
+ player.cue default_scene if player.scene.nil?
120
+ player_ready_procs.each { |block| block.call player }
121
+ }
122
+ end
123
+
124
+ # Execute the on_player_update blocks for each player. This method is
125
+ # typically called by the Plot while ending a turn, immediately before the
126
+ # on_ready blocks.
127
+ #
128
+ def call_player_update
129
+ players.each { |player|
130
+ player_update_procs.each { |block| block.call player }
131
+ }
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,184 @@
1
+ require 'gamefic/action'
2
+
3
+ module Gamefic
4
+ module World
5
+ module Commands
6
+ include Gamefic::World::Entities
7
+
8
+ # @return [Gamefic::World::Playbook]
9
+ def playbook
10
+ @playbook ||= Gamefic::World::Playbook.new
11
+ end
12
+
13
+ # Create an Action that responds to a command.
14
+ # An Action uses the command argument to identify the imperative verb that
15
+ # triggers the action.
16
+ # It can also accept queries to tokenize the remainder of the input and
17
+ # filter for particular entities or properties.
18
+ # The block argument contains the code to be executed when the input
19
+ # matches all of the Action's criteria (i.e., verb and queries).
20
+ #
21
+ # @example A simple Action.
22
+ # respond :salute do |actor|
23
+ # actor.tell "Hello, sir!"
24
+ # end
25
+ # # The command "salute" will respond "Hello, sir!"
26
+ #
27
+ # @example An Action that accepts a Character
28
+ # respond :salute, Use.visible(Character) do |actor, character|
29
+ # actor.tell "#{The character} returns your salute."
30
+ # end
31
+ #
32
+ # @param command [Symbol] An imperative verb for the command
33
+ # @param queries [Array<Query::Base>] Filters for the command's tokens
34
+ # @yieldparam [Gamefic::Actor]
35
+ # @return [Class] The resulting Action subclass
36
+ def respond(command, *queries, &proc)
37
+ playbook.respond(command, *map_response_args(queries), &proc)
38
+ end
39
+ alias action respond
40
+
41
+ # Parse a verb and a list of arguments into an action.
42
+ # This method serves as a shortcut to creating an action with one or more
43
+ # arguments that identify specific entities.
44
+ #
45
+ # @example
46
+ # @thing = make Entity, name: 'a thing'
47
+ # parse "use", "the thing" do |actor, thing|
48
+ # actor.tell "You use it."
49
+ # end
50
+ #
51
+ # @raise [ArgumentError] if tokens are unrecognized or ambiguous
52
+ #
53
+ # @param verb [String, Symbol] The command's verb
54
+ # @param tokens [Array<String>] The arguments passed to the action
55
+ # @return [Class] The resulting Action subclass
56
+ def parse verb, *tokens, &proc
57
+ query = Query::External.new(entities)
58
+ params = []
59
+ tokens.each do |arg|
60
+ matches = query.resolve(nil, arg)
61
+ raise ArgumentError, "Unable to resolve token '#{arg}'" if matches.objects.empty?
62
+ raise ArgumentError, "Ambiguous results for '#{arg}'" if matches.objects.length > 1
63
+ params.push Query::Family.new(matches.objects[0])
64
+ end
65
+ respond(verb.to_sym, *params, &proc)
66
+ end
67
+
68
+ # Tokenize and parse a command to create a new Action subclass.
69
+ #
70
+ # @param command [String] The command
71
+ # @yieldparam [Gamefic::Actor]
72
+ # @return [Class] the resulting Action subclass
73
+ def override(command, &proc)
74
+ cmd = Syntax.tokenize(command, playbook.syntaxes).first
75
+ raise "Unable to tokenize command '#{command}'" if cmd.nil?
76
+ parse cmd.verb, *cmd.arguments, &proc
77
+ end
78
+
79
+ # Create a Meta Action that responds to a command.
80
+ # Meta Actions are very similar to standard Actions, except the Plot
81
+ # understands them to be commands that operate above and/or outside of the
82
+ # actual game world. Examples of Meta Actions are commands that report the
83
+ # player's current score, save and restore saved games, or list the game's
84
+ # credits.
85
+ #
86
+ # @example A simple Meta Action
87
+ # meta :credits do |actor|
88
+ # actor.tell "This game was written by John Smith."
89
+ # end
90
+ #
91
+ # @param command [Symbol] An imperative verb for the command
92
+ # @param queries [Array<Query::Base>] Filters for the command's tokens
93
+ # @yieldparam [Gamefic::Actor]
94
+ def meta(command, *queries, &proc)
95
+ playbook.meta command, *queries, &proc
96
+ end
97
+
98
+ # Declare a dismabiguation response for actions.
99
+ # The disambiguator is executed when an action expects an argument to
100
+ # reference a specific entity but its query matched more than one. For
101
+ # example, "red" might refer to either a red key or a red book.
102
+ #
103
+ # If a disambiguator is not defined, the playbook will use its default
104
+ # implementation.
105
+ #
106
+ # @example Tell the player the list of ambiguous entities.
107
+ # disambiguate do |actor, entities|
108
+ # actor.tell "I don't know which you mean: #{entities.join_or}."
109
+ # end
110
+ #
111
+ # @yieldparam [Gamefic::Actor]
112
+ # @yieldparam [Array<Gamefic::Entity>]
113
+ def disambiguate &block
114
+ playbook.disambiguate &block
115
+ end
116
+
117
+ # Validate an order before a character can execute its command.
118
+ #
119
+ # @yieldparam [Gamefic::Director::Order]
120
+ def validate &block
121
+ playbook.validate &block
122
+ end
123
+
124
+ # Create an alternate Syntax for an Action.
125
+ # The command and its translation can be parameterized.
126
+ #
127
+ # @example Create a synonym for the Inventory Action.
128
+ # interpret "catalogue", "inventory"
129
+ # # The command "catalogue" will be translated to "inventory"
130
+ #
131
+ # @example Create a parameterized synonym for the Look Action.
132
+ # interpret "scrutinize :entity", "look :entity"
133
+ # # The command "scrutinize chair" will be translated to "look chair"
134
+ #
135
+ # @param command [String] The format of the original command
136
+ # @param translation [String] The format of the translated command
137
+ # @return [Syntax] the Syntax object
138
+ def interpret command, translation
139
+ playbook.interpret command, translation
140
+ end
141
+ alias xlate interpret
142
+
143
+ # Get an Array of available verbs.
144
+ #
145
+ # @return [Array<String>]
146
+ def verbs
147
+ playbook.verbs.map(&:to_s).reject { |v| v.start_with?('_') }
148
+ end
149
+
150
+ # Get an Array of all Actions defined in the Plot.
151
+ #
152
+ # @return [Array<Action>]
153
+ def actions
154
+ playbook.actions
155
+ end
156
+
157
+ def get_default_query
158
+ @default_query_class ||= Gamefic::Query::Family
159
+ end
160
+
161
+ def set_default_query cls
162
+ @default_query_class = cls
163
+ end
164
+
165
+ private
166
+
167
+ # @param queries [Array]
168
+ # @return [Array<Query::Base>]
169
+ def map_response_args queries
170
+ queries.map do |q|
171
+ if q.is_a?(Regexp)
172
+ Gamefic::Query::Text.new(q)
173
+ elsif q.is_a?(Gamefic::Query::Base)
174
+ q
175
+ elsif q.is_a?(Gamefic::Element) || (q.is_a?(Class) && q <= Gamefic::Element)
176
+ get_default_query.new(q)
177
+ else
178
+ raise ArgumentError.new("Invalid argument for response: #{q}")
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -1,6 +1,5 @@
1
1
  module Gamefic
2
-
3
- class Plot
2
+ module World
4
3
  module Entities
5
4
  # Make a new Entity with the provided properties.
6
5
  #
@@ -8,16 +7,16 @@ module Gamefic
8
7
  # chair = make Entity, name: 'red chair'
9
8
  # chair.name #=> 'red chair'
10
9
  #
10
+ # @raise [ArgumentError] if class is not an Entity
11
+ #
11
12
  # @param cls [Class] The Class of the Entity to be created.
12
13
  # @param args [Hash] The entity's properties.
13
- # @return [Gamefic::Entity]
14
+ # @!macro [attach] make_entity
15
+ # @return [$1]
14
16
  def make cls, args = {}, &block
17
+ raise ArgumentError, "Invalid Entity class" unless cls.is_a?(Class) && cls <= Entity
15
18
  ent = cls.new args, &block
16
- if ent.kind_of?(Entity) == false
17
- raise "Invalid entity class"
18
- end
19
- p_entities.push ent
20
- p_dynamic.push ent if running?
19
+ entities.push ent
21
20
  ent
22
21
  end
23
22
 
@@ -34,13 +33,28 @@ module Gamefic
34
33
  ent
35
34
  end
36
35
 
36
+ # Safely remove an entity from a plot.
37
+ #
38
+ # If the entity is dynamic (e.g., created after a plot is already
39
+ # running), it is safe to delete it completely. Otherwise the entity
40
+ # will still be referenced in the entities array, but its parent will be
41
+ # set to nil.
42
+ #
43
+ # @param [Gamefic::Entity] The entity to remove
37
44
  def destroy entity
38
- if p_dynamic.include?(entity)
39
- p_entities.delete entity
40
- p_dynamic.delete entity
41
- p_players.delete entity
42
- end
43
45
  entity.parent = nil
46
+ # index = entities.index(entity)
47
+ # return if index.nil? || index < static_entity_length - 1
48
+ # entities.delete_at index
49
+ # players.delete entity
50
+ # entity.destroy
51
+
52
+ # @todo It might make sense to destroy the entity completely now. It
53
+ # will still have a reference in the index, but that shouldn't impact
54
+ # the current state of the plot.
55
+ return if static.include?(entity)
56
+ entities.delete entity
57
+ players.delete entity
44
58
  end
45
59
 
46
60
  # Pick an entity based on its description.
@@ -49,7 +63,7 @@ module Gamefic
49
63
  #
50
64
  # @example Select the Entity that matches the description
51
65
  # red_chair = make Entity, :name => 'red chair'
52
- # blue_chair make Entity, :name => 'blue chair'
66
+ # blue_chair = make Entity, :name => 'blue chair'
53
67
  # pick "red chair" #=> red_chair
54
68
  # pick "blue chair" #=> blue_chair
55
69
  # pick "chair" #=> IndexError: description is ambiguous
@@ -57,8 +71,7 @@ module Gamefic
57
71
  # @param description [String] The description of the entity
58
72
  # @return [Gamefic::Entity] The entity that matches the description
59
73
  def pick(description)
60
- query = Gamefic::Query::Base.new
61
- result = query.match(description, entities)
74
+ result = Query::Matches.execute(entities, description)
62
75
  if result.objects.length == 0
63
76
  raise IndexError.new("Unable to find entity from '#{description}'")
64
77
  elsif result.objects.length > 1
@@ -69,32 +82,17 @@ module Gamefic
69
82
 
70
83
  # Get an array of entities associated with this plot.
71
84
  #
72
- # @return [Array<Entity>]
85
+ # @return [Array<Gamefic::Entity>]
73
86
  def entities
74
- p_entities.clone
87
+ @entities ||= []
75
88
  end
76
89
 
77
90
  # Get an array of players associated with this plot.
78
91
  #
79
- # @return [Array<Character>]
92
+ # @return [Array<Gamefic::Actor>]
80
93
  def players
81
- p_players.clone
82
- end
83
-
84
- private
85
-
86
- def p_entities
87
- @p_entities ||= []
88
- end
89
-
90
- def p_players
91
- @p_players ||= []
92
- end
93
-
94
- def p_dynamic
95
- @p_dynamic ||= []
94
+ @players ||= []
96
95
  end
97
96
  end
98
97
  end
99
-
100
98
  end