gamefic 2.0.2 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,8 +11,9 @@ module Gamefic
11
11
  attr_reader :plot
12
12
 
13
13
  # @param plot [Gamefic::Plot]
14
- # @param introduce [Gamefic::Actor]
15
- # @param next_cue [Class<Gamefic::Scene::Base>]
14
+ # @param introduce [Gamefic::Actor, nil]
15
+ # @param next_cue [Class<Gamefic::Scene::Base>, nil]
16
+ # @param more [Hash]
16
17
  def initialize plot, introduce: nil, next_cue: nil, **more
17
18
  @plot = plot
18
19
  @next_cue = next_cue
@@ -98,15 +99,5 @@ module Gamefic
98
99
  #
99
100
  def configure more
100
101
  end
101
-
102
- # def to_serial(index)
103
- # puts "Serializing #{self}"
104
- # super
105
- # end
106
-
107
- # def from_serial index = []
108
- # # @todo Customize subplot unserialization
109
- # super
110
- # end
111
102
  end
112
103
  end
@@ -1,5 +1,3 @@
1
- require 'gamefic/command'
2
-
3
1
  module Gamefic
4
2
  class Syntax
5
3
  attr_reader :token_count, :first_word, :verb, :template, :command
@@ -57,17 +55,18 @@ module Gamefic
57
55
  m = text.match(@regexp)
58
56
  return nil if m.nil?
59
57
  arguments = []
60
- # HACK: Skip the first word if the verb is not nil.
61
- # This is ugly.
62
58
  b = @verb.nil? ? 0 : 1
59
+ xverb = @verb
63
60
  @replace.to_s.split_words[b..-1].each { |r|
64
61
  if r.match(/^\{\$[0-9]+\}$/)
65
62
  arguments.push m[r[2..-2].to_i]
63
+ elsif arguments.empty? && xverb.nil?
64
+ xverb = r.to_sym
66
65
  else
67
66
  arguments.push r
68
67
  end
69
68
  }
70
- Command.new @verb, arguments
69
+ Command.new xverb, arguments
71
70
  end
72
71
 
73
72
  # Determine if the specified text matches the syntax's expected pattern.
@@ -86,28 +85,36 @@ module Gamefic
86
85
  end
87
86
 
88
87
  def ==(other)
88
+ return false unless other.is_a?(Syntax)
89
89
  signature == other.signature
90
90
  end
91
91
 
92
- # Tokenize an Array of Commands from the specified text.
92
+ def eql?(other)
93
+ self == other
94
+ end
95
+
96
+ def hash
97
+ signature.hash
98
+ end
99
+
100
+ # Tokenize an Array of Commands from the specified text. The resulting
101
+ # array is in descending order of specificity, i.e., most to least matched
102
+ # tokens.
93
103
  #
94
104
  # @param text [String] The text to tokenize.
95
105
  # @param syntaxes [Array<Gamefic::Syntax>] The Syntaxes to use.
96
106
  # @return [Array<Gamefic::Command>] The tokenized commands.
97
107
  def self.tokenize text, syntaxes
98
- matches = []
99
- syntaxes.each { |syntax|
100
- result = syntax.tokenize text
101
- matches.push(result) if !result.nil?
102
- }
103
- matches.sort! { |a,b|
104
- if a.arguments.length == b.arguments.length
105
- b.verb.to_s <=> a.verb.to_s
106
- else
107
- b.arguments.length <=> a.arguments.length
108
+ syntaxes
109
+ .map { |syn| syn.tokenize(text) }
110
+ .reject(&:nil?)
111
+ .sort do |a, b|
112
+ if a.arguments.length == b.arguments.length
113
+ b.verb.to_s <=> a.verb.to_s
114
+ else
115
+ b.arguments.length <=> a.arguments.length
116
+ end
108
117
  end
109
- }
110
- matches
111
118
  end
112
119
  end
113
120
  end
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '2.0.2'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -95,25 +95,6 @@ module Gamefic
95
95
  playbook.meta command, *queries, &proc
96
96
  end
97
97
 
98
- # Declare a dismabiguation response for actions.
99
- # The disambigurator 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
98
  # Validate an order before a character can execute its command.
118
99
  #
119
100
  # @yieldparam [Gamefic::Director::Order]
@@ -144,7 +125,7 @@ module Gamefic
144
125
  #
145
126
  # @return [Array<String>]
146
127
  def verbs
147
- playbook.verbs.map { |v| v.to_s }.reject{ |v| v.start_with?('_') }
128
+ playbook.verbs.map(&:to_s).reject { |v| v.start_with?('_') }
148
129
  end
149
130
 
150
131
  # Get an Array of all Actions defined in the Plot.
@@ -164,20 +145,20 @@ module Gamefic
164
145
 
165
146
  private
166
147
 
148
+ # @param queries [Array]
149
+ # @return [Array<Query::Base>]
167
150
  def map_response_args queries
168
- result = []
169
- queries.each do |q|
151
+ queries.map do |q|
170
152
  if q.is_a?(Regexp)
171
- result.push Gamefic::Query::Text.new(q)
153
+ Gamefic::Query::Text.new(q)
172
154
  elsif q.is_a?(Gamefic::Query::Base)
173
- result.push q
155
+ q
174
156
  elsif q.is_a?(Gamefic::Element) || (q.is_a?(Class) && q <= Gamefic::Element)
175
- result.push get_default_query.new(q)
157
+ get_default_query.new(q)
176
158
  else
177
159
  raise ArgumentError.new("Invalid argument for response: #{q}")
178
160
  end
179
161
  end
180
- result
181
162
  end
182
163
  end
183
164
  end
@@ -93,16 +93,6 @@ module Gamefic
93
93
  def players
94
94
  @players ||= []
95
95
  end
96
-
97
- # private
98
-
99
- # def mark_static_entities
100
- # @static_entity_length ||= entities.length
101
- # end
102
-
103
- # def static_entity_length
104
- # @static_entity_length || 0
105
- # end
106
96
  end
107
97
  end
108
98
  end
@@ -1,20 +1,28 @@
1
+ require 'set'
2
+
1
3
  module Gamefic
2
4
  module World
3
5
  # A collection of rules for performing commands.
4
6
  #
5
7
  class Playbook
6
- def initialize commands: {}, syntaxes: [], validators: [], disambiguator: nil
7
- @commands = commands
8
- @syntaxes = syntaxes
9
- @validators = validators
10
- @disambiguator = disambiguator
11
- end
12
-
13
8
  # An array of available syntaxes.
14
9
  #
15
10
  # @return [Array<Gamefic::Syntax>]
16
- def syntaxes
17
- @syntaxes
11
+ attr_reader :syntaxes
12
+
13
+ # An array of defined validators.
14
+ #
15
+ # @return [Array<Proc>]
16
+ attr_reader :validators
17
+
18
+ # @param commands [Hash]
19
+ # @param syntaxes [Array<Syntax>, Set<Syntax>]
20
+ # @param validators [Array]
21
+ def initialize commands: {}, syntaxes: [], validators: []
22
+ @commands = commands
23
+ @syntax_set = syntaxes.to_set
24
+ sort_syntaxes
25
+ @validators = validators
18
26
  end
19
27
 
20
28
  # An array of available actions.
@@ -31,32 +39,6 @@ module Gamefic
31
39
  @commands.keys
32
40
  end
33
41
 
34
- # An array of defined validators.
35
- #
36
- # @return [Array<Proc>]
37
- def validators
38
- @validators
39
- end
40
-
41
- # Get the action for handling ambiguous entity references.
42
- #
43
- def disambiguator
44
- @disambiguator ||= Action.subclass(nil, Query::Base.new) do |actor, entities|
45
- definites = []
46
- entities.each { |entity|
47
- definites.push entity.definitely
48
- }
49
- actor.tell "I don't know which you mean: #{definites.join_or}."
50
- end
51
- end
52
-
53
- # Set the action for handling ambiguous entity references.
54
- #
55
- def disambiguate &block
56
- @disambiguator = Action.subclass(nil, Query::Base.new, meta: true, &block)
57
- @disambiguator
58
- end
59
-
60
42
  # Add a block that determines whether an action can be executed.
61
43
  #
62
44
  def validate &block
@@ -66,7 +48,7 @@ module Gamefic
66
48
  # Get an Array of all Actions associated with the specified verb.
67
49
  #
68
50
  # @param verb [Symbol] The Symbol for the verb (e.g., :go or :look)
69
- # @return [Array<Action>] The verb's associated Actions
51
+ # @return [Array<Class<Action>>] The verb's associated Actions
70
52
  def actions_for verb
71
53
  @commands[verb] || []
72
54
  end
@@ -142,40 +124,16 @@ module Gamefic
142
124
  syn
143
125
  end
144
126
 
145
- # Get an array of actions, derived from the specified command, that the
146
- # actor can potentially execute.
147
- # The command can either be a single string (e.g., "examine book") or a
148
- # list of tokens (e.g., :examine, @book).
127
+ # Get a Dispatcher to select actions that can potentially be executed
128
+ # from the specified command string.
149
129
  #
150
- # @return [Array<Gamefic::Action>]
151
- def dispatch(actor, *command)
152
- result = []
153
- if command.length > 1
154
- result.concat dispatch_from_params(actor, command[0], command[1..-1])
155
- end
156
- if result.empty?
157
- result.concat dispatch_from_string(actor, command.join(' '))
158
- end
159
- result
160
- end
161
-
162
- # Get an array of actions, derived from the specified command, that the
163
- # actor can potentially execute.
164
- # The command should be a plain-text string, e.g., "examine the book."
165
- #
166
- # @return [Array<Gamefic::Action>]
167
- def dispatch_from_string actor, text
168
- result = []
130
+ # @param actor [Actor]
131
+ # @param text [String]
132
+ # @return [Dispatcher]
133
+ def dispatch(actor, text)
169
134
  commands = Syntax.tokenize(text, actor.syntaxes)
170
- commands.each { |c|
171
- available = actions_for(c.verb)
172
- available.each { |a|
173
- next if a.hidden?
174
- o = a.attempt(actor, c.arguments)
175
- result.unshift o unless o.nil?
176
- }
177
- }
178
- sort_and_reduce_actions result
135
+ actions = commands.flat_map { |cmd| actions_for(cmd.verb).reject(&:hidden?) }
136
+ Dispatcher.new(actor, commands, sort_and_reduce_actions(actions))
179
137
  end
180
138
 
181
139
  # Get an array of actions, derived from the specified verb and params,
@@ -183,12 +141,8 @@ module Gamefic
183
141
  #
184
142
  # @return [Array<Gamefic::Action>]
185
143
  def dispatch_from_params actor, verb, params
186
- result = []
187
144
  available = actions_for(verb)
188
- available.each { |a|
189
- result.unshift a.new(actor, params) if a.valid?(actor, params)
190
- }
191
- sort_and_reduce_actions result
145
+ Dispatcher.new(actor, [Command.new(verb, params)], sort_and_reduce_actions(available))
192
146
  end
193
147
 
194
148
  # Duplicate the playbook.
@@ -217,38 +171,38 @@ module Gamefic
217
171
  user_friendly = action.verb.to_s.gsub(/_/, ' ')
218
172
  args = []
219
173
  used_names = []
220
- action.queries.each { |c|
174
+ action.queries.each do |_c|
221
175
  num = 1
222
176
  new_name = ":var"
223
177
  while used_names.include? new_name
224
- num = num + 1
178
+ num += 1
225
179
  new_name = ":var#{num}"
226
180
  end
227
181
  used_names.push new_name
228
182
  user_friendly += " #{new_name}"
229
183
  args.push new_name
230
- }
184
+ end
231
185
  add_syntax Syntax.new(user_friendly.strip, "#{action.verb} #{args.join(' ')}") unless action.verb.to_s.start_with?('_')
232
186
  end
233
187
 
234
188
  def add_syntax syntax
235
- if @commands[syntax.verb] == nil
236
- raise "No actions exist for \"#{syntax.verb}\""
237
- end
238
- @syntaxes.unshift syntax
239
- @syntaxes.uniq! &:signature
240
- @syntaxes.sort! { |a, b|
189
+ raise "No actions exist for \"#{syntax.verb}\"" if @commands[syntax.verb].nil?
190
+ sort_syntaxes if @syntax_set.add?(syntax)
191
+ end
192
+
193
+ def sort_and_reduce_actions arr
194
+ arr.sort_by.with_index { |a, i| [a.rank, i] }.reverse.uniq
195
+ end
196
+
197
+ def sort_syntaxes
198
+ @syntaxes = @syntax_set.sort do |a, b|
241
199
  if a.token_count == b.token_count
242
- # For syntaxes of the same length, length of action takes precedence
200
+ # For syntaxes of the same length, sort first word
243
201
  b.first_word <=> a.first_word
244
202
  else
245
203
  b.token_count <=> a.token_count
246
204
  end
247
- }
248
- end
249
-
250
- def sort_and_reduce_actions arr
251
- arr.sort_by.with_index{|a, i| [a.rank, -i]}.reverse.uniq(&:class)
205
+ end
252
206
  end
253
207
  end
254
208
  end
@@ -2,6 +2,7 @@ module Gamefic
2
2
  module World
3
3
  module Players
4
4
  include Gamefic::World::Entities
5
+ include Gamefic::World::Commands
5
6
 
6
7
  # An array of entities that are currently connected to users.
7
8
  #
@@ -10,12 +11,27 @@ module Gamefic
10
11
  @players ||= []
11
12
  end
12
13
 
13
- # Get the character that the player will control on introduction.
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.
14
29
  #
15
30
  # @return [Gamefic::Actor]
16
- def get_player_character
31
+ def make_player_character
17
32
  cast player_class, name: 'yourself', synonyms: 'self myself you me', proper_named: true
18
33
  end
34
+ alias get_player_character make_player_character
19
35
  end
20
36
  end
21
37
  end
@@ -95,10 +95,10 @@ module Gamefic
95
95
  #
96
96
  # @param prompt [String]
97
97
  # @yieldparam [Gamefic::Actor]
98
- # @yieldparam [Gamefic::Scene::Custom]
99
- # @return [Class<Gamefic::Scene::Custom>]
98
+ # @yieldparam [Gamefic::Scene::Base]
99
+ # @return [Class<Gamefic::Scene::Base>]
100
100
  def question prompt = 'What is your answer?', &block
101
- s = Scene::Custom.subclass do |actor, scene|
101
+ s = Scene::Base.subclass do |actor, scene|
102
102
  scene.prompt = prompt
103
103
  scene.on_finish &block
104
104
  end
@@ -150,23 +150,23 @@ module Gamefic
150
150
  # Custom scenes should always specify the next scene to be cued or
151
151
  # prepared. If not, the scene will get repeated on the next turn.
152
152
  #
153
- # This method creates a Scene::Custom by default. You can customize other
153
+ # This method creates a Scene::Base by default. You can customize other
154
154
  # scene types by specifying the class to create.
155
155
  #
156
156
  # @example Ask the user for a name
157
- # @scene = custom do |scene|
158
- # data.prompt = "What's your name?"
159
- # scene.on_finish do |actor, data|
160
- # actor.name = data.input
157
+ # @scene = custom do |actor, scene|
158
+ # scene.prompt = "What's your name?"
159
+ # scene.on_finish do
160
+ # actor.name = scene.input
161
161
  # actor.tell "Hello, #{actor.name}!"
162
- # actor.cue :active
162
+ # actor.cue default_scene
163
163
  # end
164
164
  # end
165
165
  #
166
166
  # @param cls [Class] The class of scene to be instantiated.
167
167
  # @yieldparam [Gamefic::Actor]
168
- # @return [Class<Gamefic::Scene::Custom>]
169
- def custom cls = Scene::Custom, &block
168
+ # @return [Class<Gamefic::Scene::Base>]
169
+ def custom cls = Scene::Base, &block
170
170
  s = cls.subclass &block
171
171
  scene_classes.push s
172
172
  s