gamefic 1.5.1 → 1.6.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 (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