gamefic 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -3
  3. data/lib/gamefic/action.rb +140 -79
  4. data/lib/gamefic/character.rb +120 -53
  5. data/lib/gamefic/character/state.rb +12 -0
  6. data/lib/gamefic/core_ext/array.rb +53 -11
  7. data/lib/gamefic/core_ext/string.rb +1 -0
  8. data/lib/gamefic/describable.rb +37 -11
  9. data/lib/gamefic/engine/base.rb +17 -4
  10. data/lib/gamefic/engine/tty.rb +4 -0
  11. data/lib/gamefic/entity.rb +4 -15
  12. data/lib/gamefic/matchable.rb +50 -0
  13. data/lib/gamefic/messaging.rb +45 -0
  14. data/lib/gamefic/node.rb +16 -0
  15. data/lib/gamefic/plot.rb +27 -33
  16. data/lib/gamefic/plot/{article_mount.rb → articles.rb} +22 -22
  17. data/lib/gamefic/plot/callbacks.rb +30 -4
  18. data/lib/gamefic/plot/{command_mount.rb → commands.rb} +121 -121
  19. data/lib/gamefic/plot/entities.rb +3 -3
  20. data/lib/gamefic/plot/host.rb +3 -3
  21. data/lib/gamefic/plot/playbook.rb +74 -30
  22. data/lib/gamefic/plot/scenes.rb +149 -0
  23. data/lib/gamefic/plot/snapshot.rb +14 -39
  24. data/lib/gamefic/plot/theater.rb +73 -0
  25. data/lib/gamefic/query.rb +6 -19
  26. data/lib/gamefic/query/base.rb +127 -246
  27. data/lib/gamefic/query/children.rb +6 -7
  28. data/lib/gamefic/query/descendants.rb +15 -0
  29. data/lib/gamefic/query/family.rb +19 -7
  30. data/lib/gamefic/query/itself.rb +13 -0
  31. data/lib/gamefic/query/matches.rb +67 -11
  32. data/lib/gamefic/query/parent.rb +6 -7
  33. data/lib/gamefic/query/siblings.rb +10 -7
  34. data/lib/gamefic/query/text.rb +39 -35
  35. data/lib/gamefic/scene.rb +1 -1
  36. data/lib/gamefic/scene/active.rb +12 -6
  37. data/lib/gamefic/scene/base.rb +56 -5
  38. data/lib/gamefic/scene/conclusion.rb +3 -0
  39. data/lib/gamefic/scene/custom.rb +0 -83
  40. data/lib/gamefic/scene/multiple_choice.rb +54 -32
  41. data/lib/gamefic/scene/multiple_scene.rb +11 -6
  42. data/lib/gamefic/scene/pause.rb +3 -4
  43. data/lib/gamefic/scene/yes_or_no.rb +23 -9
  44. data/lib/gamefic/script/base.rb +4 -0
  45. data/lib/gamefic/subplot.rb +22 -19
  46. data/lib/gamefic/syntax.rb +7 -15
  47. data/lib/gamefic/user/base.rb +7 -13
  48. data/lib/gamefic/user/buffer.rb +7 -0
  49. data/lib/gamefic/user/tty.rb +13 -12
  50. data/lib/gamefic/version.rb +1 -1
  51. metadata +11 -37
  52. data/lib/gamefic/director.rb +0 -23
  53. data/lib/gamefic/director/delegate.rb +0 -126
  54. data/lib/gamefic/director/order.rb +0 -17
  55. data/lib/gamefic/director/parser.rb +0 -137
  56. data/lib/gamefic/keywords.rb +0 -67
  57. data/lib/gamefic/plot/query_mount.rb +0 -9
  58. data/lib/gamefic/plot/scene_mount.rb +0 -182
  59. data/lib/gamefic/query/ambiguous_children.rb +0 -5
  60. data/lib/gamefic/query/expression.rb +0 -47
  61. data/lib/gamefic/query/many_children.rb +0 -7
  62. data/lib/gamefic/query/plural_children.rb +0 -14
  63. data/lib/gamefic/query/self.rb +0 -10
  64. data/lib/gamefic/scene_data.rb +0 -10
  65. data/lib/gamefic/scene_data/base.rb +0 -12
  66. data/lib/gamefic/scene_data/multiple_choice.rb +0 -19
  67. data/lib/gamefic/scene_data/multiple_scene.rb +0 -21
  68. data/lib/gamefic/scene_data/yes_or_no.rb +0 -18
  69. data/lib/gamefic/serialized.rb +0 -24
  70. data/lib/gamefic/stage.rb +0 -106
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63846578f9f4d58fb35a150a601b8b5d16b197c8
4
- data.tar.gz: 360676b4be22f3eb71661bf8ee9c63edd629207b
3
+ metadata.gz: ce1dd2ec32bb2872b9c18a40cfdf92ac745506a4
4
+ data.tar.gz: 264aa06dc79054f22d07fe4552b6341c05a4c82e
5
5
  SHA512:
6
- metadata.gz: 6a1f09d1c998ddeca48c721c0d8336479b0f1762b98f6a57f1f440f08309b33f1993585ac386d9443a4b5d616012d85c285f5d4298bd64437e025d235d52cb1f
7
- data.tar.gz: 3fd2d90bb464dc6c2ed83d9a72fba6d3177b7cdd65853b740fdc77a7414b8113a8b4a5e79466fc3e1e964f01d1427d0daf30f90abe93fca7b461459ff867b76b
6
+ metadata.gz: ad1365b02676307134c0a141dbe9003989093b2e78f590affd2c5bbaa237758ae1357e3c156551e36f65b22c44bd0f81a06b940e655075bf529dcea59b75fc6a
7
+ data.tar.gz: fc741d054f2d4e187d6e51fc96ef4a59354dfc6bea21c39adfcf7a45a5e48195fcae2017c3543bd56d9e4dfbd89bfb584e4a3e8349145bb869878cbd178eb12b
data/lib/gamefic.rb CHANGED
@@ -1,16 +1,14 @@
1
+ require 'gamefic/matchable'
1
2
  require 'gamefic/core_ext/array'
2
3
  require 'gamefic/core_ext/string'
3
4
 
4
5
  require 'gamefic/grammar'
5
- require 'gamefic/keywords'
6
- require 'gamefic/serialized'
7
6
  require 'gamefic/entity'
8
7
  require 'gamefic/character'
9
8
  require "gamefic/scene"
10
9
  require "gamefic/query"
11
10
  require "gamefic/action"
12
11
  require "gamefic/syntax"
13
- require "gamefic/director"
14
12
  require "gamefic/plot"
15
13
  require 'gamefic/subplot'
16
14
  require "gamefic/engine"
@@ -1,95 +1,156 @@
1
1
  module Gamefic
2
-
3
2
  # Exception raised when the Action's proc arity is not compatible with the
4
3
  # number of queries
5
4
  class ActionArgumentError < ArgumentError
6
5
  end
7
-
8
- # Actions manage the execution of commands that Characters can perform.
9
- #
6
+
10
7
  class Action
11
- attr_reader :order_key, :queries
12
- attr_writer :meta
13
- @@order_key_seed = 0
14
-
15
- def initialize(verb, *queries, &proc)
16
- if !verb.kind_of?(Symbol)
17
- verb = verb.to_s
18
- verb = nil if verb == ''
19
- end
20
- @order_key = @@order_key_seed
21
- @@order_key_seed += 1
22
- @proc = proc
23
- if (verb.kind_of?(Symbol) == false and !verb.nil?)
24
- raise "Action verbs must be symbols #{verb}"
25
- end
26
- if !@proc.nil?
27
- if (queries.length + 1 != @proc.arity) and (@proc.arity > 0)
28
- raise ActionArgumentError.new("Number of queries is not compatible with proc arguments")
29
- end
30
- end
31
- @verb = verb
32
- @queries = queries
8
+ attr_reader :parameters
9
+
10
+ def initialize actor, parameters
11
+ @actor = actor
12
+ @parameters = parameters
13
+ @executed = false
33
14
  end
34
-
35
- # Get the specificity of the Action.
36
- # Specificity indicates how narrowly the Action's queries filter matches.
37
- # Actions with higher specificity are given higher priority when searching
38
- # for the Action that matches a character command. For example, an Action
39
- # with a Query that filters for a specific class of Entity has a higher
40
- # specificity than an Action with a Query that accepts arbitrary text.
41
- #
42
- # @return [Fixnum]
43
- def specificity
44
- spec = 0
45
- if verb.nil?
46
- spec = -100
47
- end
48
- @queries.each { |q|
49
- if q.kind_of?(Query::Base)
50
- spec += q.specificity
51
- else
52
- spec += 1
53
- end
54
- }
55
- return spec
15
+
16
+ # @todo Determine whether to call them parameters, arguments, or both.
17
+ def arguments
18
+ parameters
56
19
  end
57
-
58
- # Get the verb associated with this Action.
59
- # The verb is represented by a Symbol in the imperative form, such as
60
- # :take or :look_under.
61
- #
62
- # @return [Symbol] The Symbol representing the verb.
63
- def verb
64
- @verb
20
+
21
+ def execute
22
+ @executed = true
23
+ self.class.executor.call(@actor, *@parameters) unless self.class.executor.nil?
65
24
  end
66
-
67
- # Execute this Action. This method is typically called by the Plot when
68
- # a Character performs a command.
69
- def execute *args
70
- @proc.call(*args)
25
+
26
+ def executed?
27
+ @executed
71
28
  end
72
-
29
+
30
+ def verb
31
+ self.class.verb
32
+ end
33
+
73
34
  def signature
74
- sig = ["#{@verb}"]
75
- @queries.each { |q|
76
- sig.push q.signature
77
- }
78
- "#{sig.join(', ').gsub(/Gamefic::(Query::)?/, '')}(#{specificity})"
35
+ self.class.signature
79
36
  end
80
-
81
- # Is this a meta Action?
82
- # If an Action is flagged meta, it usually means that it provides
83
- # information about the game or manages some aspect of the user interface.
84
- # It shouldn't represent an Action that the player's character performs in
85
- # the game world. Examples include Actions to display credits or
86
- # instructions.
87
- #
88
- # @return [Boolean]
37
+
38
+ def rank
39
+ self.class.rank
40
+ end
41
+
89
42
  def meta?
90
- @meta ||= false
43
+ self.class.meta?
91
44
  end
92
-
93
- end
94
45
 
46
+ def order_key
47
+ self.class.order_key
48
+ end
49
+
50
+ def self.subclass verb, *q, meta: false, order_key: 0, &block
51
+ act = Class.new(self) do
52
+ self.verb = verb
53
+ self.meta = meta
54
+ self.order_key = order_key
55
+ q.each { |q|
56
+ add_query q
57
+ }
58
+ on_execute &block
59
+ end
60
+ if !block.nil? and act.queries.length + 1 != block.arity and block.arity > 0
61
+ raise ActionArgumentError.new("Number of parameters is not compatible with proc arguments")
62
+ end
63
+ act
64
+ end
65
+
66
+ class << self
67
+ def verb
68
+ @verb
69
+ end
70
+
71
+ def meta?
72
+ @meta ||= false
73
+ end
74
+
75
+ def add_query q
76
+ @specificity = nil
77
+ queries.push q
78
+ end
79
+
80
+ def queries
81
+ @queries ||= []
82
+ end
83
+
84
+ def on_execute &block
85
+ @executor = block
86
+ end
87
+
88
+ def signature
89
+ # @todo This is clearly unfinished
90
+ "#{verb} #{queries.map{|m| m.signature}.join(',')}"
91
+ end
92
+
93
+ def executor
94
+ @executor
95
+ end
96
+
97
+ def order_key
98
+ @order_key ||= 0
99
+ end
100
+
101
+ def rank
102
+ if @rank.nil?
103
+ @rank = 0
104
+ queries.each { |q|
105
+ @rank += (q.rank + 1)
106
+ }
107
+ @rank -= 1000 if verb.nil?
108
+ end
109
+ @rank
110
+ end
111
+
112
+ def valid? actor, objects
113
+ return false if objects.length != queries.length
114
+ i = 0
115
+ queries.each { |p|
116
+ return false unless p.include?(actor, objects[i])
117
+ i += 1
118
+ }
119
+ true
120
+ end
121
+
122
+ def attempt actor, tokens
123
+ i = 0
124
+ result = []
125
+ matches = Gamefic::Query::Matches.new([], '', '')
126
+ queries.each { |p|
127
+ return nil if tokens[i].nil? and matches.remaining == ''
128
+ matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
129
+ return nil if matches.objects.empty?
130
+ if p.ambiguous?
131
+ result.push matches.objects
132
+ else
133
+ return nil if matches.objects.length > 1
134
+ result.push matches.objects[0]
135
+ end
136
+ i += 1
137
+ }
138
+ self.new(actor, result)
139
+ end
140
+
141
+ protected
142
+
143
+ def verb= sym
144
+ @verb = sym
145
+ end
146
+
147
+ def meta= bool
148
+ @meta = bool
149
+ end
150
+
151
+ def order_key= num
152
+ @order_key = num
153
+ end
154
+ end
155
+ end
95
156
  end
@@ -1,23 +1,29 @@
1
- require 'gamefic/director'
1
+ #require 'gamefic/director'
2
2
 
3
- class NotConclusionError < Exception
4
- end
5
3
 
6
4
  module Gamefic
5
+ class NotConclusionError < Exception
6
+ end
7
+
7
8
  class Character < Entity
9
+ autoload :State, 'gamefic/character/state'
10
+
8
11
  attr_reader :queue, :user
9
- # @return [Gamefic::Director::Order]
10
- attr_reader :last_order
12
+ # @return [Gamefic::Action]
13
+ attr_reader :last_action
11
14
  # @return [Entity,nil]
12
15
  attr_reader :last_object
13
16
  attr_accessor :object_of_pronoun
14
17
  attr_reader :scene
15
18
  attr_reader :next_scene
16
19
  attr_accessor :playbook
20
+
21
+ include Character::State
17
22
 
18
23
  def initialize(args = {})
19
- @queue = Array.new
20
24
  super
25
+ @queue = Array.new
26
+ @messages = ''
21
27
  @buffer_stack = 0
22
28
  @buffer = ""
23
29
  end
@@ -34,7 +40,32 @@ module Gamefic
34
40
  def disconnect
35
41
  @user = nil
36
42
  end
37
-
43
+
44
+ # Send a message to the entity.
45
+ # This method will automatically wrap the message in HTML paragraphs.
46
+ # To send a message without paragraph formatting, use #stream instead.
47
+ #
48
+ # @param message [String]
49
+ def tell(message)
50
+ if @buffer_stack > 0
51
+ @buffer += message
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ # Send a message to the Character as raw text.
58
+ # Unlike #tell, this method will not wrap the message in HTML paragraphs.
59
+ #
60
+ # @param message [String]
61
+ def stream(message)
62
+ if @buffer_stack > 0
63
+ @buffer += message
64
+ else
65
+ super
66
+ end
67
+ end
68
+
38
69
  # Perform a command.
39
70
  # The command can be specified as a String or a set of tokens. Either form
40
71
  # should yield the same result, but using tokens can yield better
@@ -49,7 +80,23 @@ module Gamefic
49
80
  # character.perform :take, @key
50
81
  #
51
82
  def perform(*command)
52
- Director.dispatch(self, *command)
83
+ #Director.dispatch(self, *command)
84
+ actions = playbook.dispatch(self, *command)
85
+ a = actions.first
86
+ okay = true
87
+ unless a.meta?
88
+ playbook.validators.each { |v|
89
+ result = v.call(self, a.verb, a.parameters)
90
+ okay = (result != false)
91
+ break if not okay
92
+ }
93
+ end
94
+ if okay
95
+ performance_stack.push actions
96
+ proceed
97
+ performance_stack.pop
98
+ end
99
+ a
53
100
  end
54
101
 
55
102
  # Quietly perform a command.
@@ -66,34 +113,6 @@ module Gamefic
66
113
  @buffer_stack -= 1
67
114
  @buffer
68
115
  end
69
-
70
- # Send a message to the Character.
71
- # This method will automatically wrap the message in HTML paragraphs.
72
- # To send a message without paragraph formatting, use #stream instead.
73
- #
74
- # @param message [String]
75
- def tell(message)
76
- if user != nil and message.to_s != ''
77
- if @buffer_stack > 0
78
- @buffer += message
79
- else
80
- message = "<p>#{message.strip}</p>"
81
- # This method uses String#gsub instead of String#gsub! for
82
- # compatibility with Opal.
83
- message = message.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, '</p><p>')
84
- message = message.gsub(/[ \t]*\n[ \t]*/, ' ')
85
- user.send message
86
- end
87
- end
88
- end
89
-
90
- # Send a message to the Character as raw text.
91
- # Unlike #tell, this method will not wrap the message in HTML paragraphs.
92
- #
93
- # @param message [String]
94
- def stream(message)
95
- user.send message.strip unless user.nil?
96
- end
97
116
 
98
117
  # Proceed to the next Action in the current stack.
99
118
  # This method is typically used in Action blocks to cascade through
@@ -116,35 +135,87 @@ module Gamefic
116
135
  # end
117
136
  # end
118
137
  #
119
- def proceed
120
- Director::Delegate.proceed_for self
138
+ def proceed quietly: false
139
+ #Director::Delegate.proceed_for self
140
+ return if performance_stack.empty?
141
+ a = performance_stack.last.shift
142
+ unless a.nil?
143
+ if quietly
144
+ if @buffer_stack == 0
145
+ @buffer = ""
146
+ end
147
+ @buffer_stack += 1
148
+ end
149
+ a.execute
150
+ if quietly
151
+ @buffer_stack -= 1
152
+ @buffer
153
+ end
154
+ end
121
155
  end
122
156
 
123
- def cue scene
157
+ # Immediately start a new scene for the character.
158
+ # Use #prepare if you want to declare a scene to be started at the
159
+ # beginning of the next turn.
160
+ #
161
+ def cue new_scene
124
162
  @next_scene = nil
125
- @scene = scene
126
- @scene.start self unless @scene.nil?
163
+ if new_scene.nil?
164
+ @scene = nil
165
+ else
166
+ @scene = new_scene.new(self)
167
+ @scene.start
168
+ end
127
169
  end
128
170
 
129
- def prepare scene
130
- @next_scene = scene
171
+ # Prepare a scene to be started for this character at the beginning of the
172
+ # next turn.
173
+ #
174
+ def prepare s
175
+ @next_scene = s
176
+ end
177
+
178
+ # Return true if the character is expected to be in the specified scene on
179
+ # the next turn.
180
+ #
181
+ # @return [Boolean]
182
+ def will_cue? scene
183
+ (@scene.class == scene and @next_scene.nil?) or @next_scene == scene
131
184
  end
132
185
 
186
+ # Cue a conclusion. This method works like #cue, except it will raise a
187
+ # NotConclusionError if the scene is not a Scene::Conclusion.
188
+ #
133
189
  def conclude scene
134
- raise NotConclusionError if !scene.kind_of?(Scene::Conclusion)
190
+ raise NotConclusionError unless scene <= Scene::Conclusion
135
191
  cue scene
136
192
  end
137
193
 
194
+ # True if the character is in a conclusion.
195
+ #
196
+ # @return [Boolean]
138
197
  def concluded?
139
198
  !scene.nil? and scene.kind_of?(Scene::Conclusion)
140
199
  end
141
200
 
142
201
  def performed order
143
- @last_order = order
202
+ order.freeze
203
+ @last_action = order
144
204
  end
145
205
 
146
- def prompt
147
- scene.nil? ? '>' : scene.prompt_for(self)
206
+ # Get the prompt that the user should see for the current scene.
207
+ #
208
+ # @return [String]
209
+ #def prompt
210
+ # scene.nil? ? '>' : scene.prompt
211
+ #end
212
+
213
+ def accessible?
214
+ false
215
+ end
216
+
217
+ def inspect
218
+ to_s
148
219
  end
149
220
 
150
221
  private
@@ -153,12 +224,8 @@ module Gamefic
153
224
  @delegate_stack ||= []
154
225
  end
155
226
 
156
- def last_order=(order)
157
- return if order.nil?
158
- @last_order = order
159
- if !order.action.meta? and !order.arguments[0].nil? and !order.arguments[0][0].nil? and order.arguments[0][0].kind_of?(Entity)
160
- @last_object = order.arguments[0][0]
161
- end
227
+ def performance_stack
228
+ @performance_stack ||= []
162
229
  end
163
230
  end
164
231