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.
- checksums.yaml +4 -4
- data/lib/gamefic.rb +1 -3
- data/lib/gamefic/action.rb +140 -79
- data/lib/gamefic/character.rb +120 -53
- data/lib/gamefic/character/state.rb +12 -0
- data/lib/gamefic/core_ext/array.rb +53 -11
- data/lib/gamefic/core_ext/string.rb +1 -0
- data/lib/gamefic/describable.rb +37 -11
- data/lib/gamefic/engine/base.rb +17 -4
- data/lib/gamefic/engine/tty.rb +4 -0
- data/lib/gamefic/entity.rb +4 -15
- data/lib/gamefic/matchable.rb +50 -0
- data/lib/gamefic/messaging.rb +45 -0
- data/lib/gamefic/node.rb +16 -0
- data/lib/gamefic/plot.rb +27 -33
- data/lib/gamefic/plot/{article_mount.rb → articles.rb} +22 -22
- data/lib/gamefic/plot/callbacks.rb +30 -4
- data/lib/gamefic/plot/{command_mount.rb → commands.rb} +121 -121
- data/lib/gamefic/plot/entities.rb +3 -3
- data/lib/gamefic/plot/host.rb +3 -3
- data/lib/gamefic/plot/playbook.rb +74 -30
- data/lib/gamefic/plot/scenes.rb +149 -0
- data/lib/gamefic/plot/snapshot.rb +14 -39
- data/lib/gamefic/plot/theater.rb +73 -0
- data/lib/gamefic/query.rb +6 -19
- data/lib/gamefic/query/base.rb +127 -246
- data/lib/gamefic/query/children.rb +6 -7
- data/lib/gamefic/query/descendants.rb +15 -0
- data/lib/gamefic/query/family.rb +19 -7
- data/lib/gamefic/query/itself.rb +13 -0
- data/lib/gamefic/query/matches.rb +67 -11
- data/lib/gamefic/query/parent.rb +6 -7
- data/lib/gamefic/query/siblings.rb +10 -7
- data/lib/gamefic/query/text.rb +39 -35
- data/lib/gamefic/scene.rb +1 -1
- data/lib/gamefic/scene/active.rb +12 -6
- data/lib/gamefic/scene/base.rb +56 -5
- data/lib/gamefic/scene/conclusion.rb +3 -0
- data/lib/gamefic/scene/custom.rb +0 -83
- data/lib/gamefic/scene/multiple_choice.rb +54 -32
- data/lib/gamefic/scene/multiple_scene.rb +11 -6
- data/lib/gamefic/scene/pause.rb +3 -4
- data/lib/gamefic/scene/yes_or_no.rb +23 -9
- data/lib/gamefic/script/base.rb +4 -0
- data/lib/gamefic/subplot.rb +22 -19
- data/lib/gamefic/syntax.rb +7 -15
- data/lib/gamefic/user/base.rb +7 -13
- data/lib/gamefic/user/buffer.rb +7 -0
- data/lib/gamefic/user/tty.rb +13 -12
- data/lib/gamefic/version.rb +1 -1
- metadata +11 -37
- data/lib/gamefic/director.rb +0 -23
- data/lib/gamefic/director/delegate.rb +0 -126
- data/lib/gamefic/director/order.rb +0 -17
- data/lib/gamefic/director/parser.rb +0 -137
- data/lib/gamefic/keywords.rb +0 -67
- data/lib/gamefic/plot/query_mount.rb +0 -9
- data/lib/gamefic/plot/scene_mount.rb +0 -182
- data/lib/gamefic/query/ambiguous_children.rb +0 -5
- data/lib/gamefic/query/expression.rb +0 -47
- data/lib/gamefic/query/many_children.rb +0 -7
- data/lib/gamefic/query/plural_children.rb +0 -14
- data/lib/gamefic/query/self.rb +0 -10
- data/lib/gamefic/scene_data.rb +0 -10
- data/lib/gamefic/scene_data/base.rb +0 -12
- data/lib/gamefic/scene_data/multiple_choice.rb +0 -19
- data/lib/gamefic/scene_data/multiple_scene.rb +0 -21
- data/lib/gamefic/scene_data/yes_or_no.rb +0 -18
- data/lib/gamefic/serialized.rb +0 -24
- data/lib/gamefic/stage.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce1dd2ec32bb2872b9c18a40cfdf92ac745506a4
|
4
|
+
data.tar.gz: 264aa06dc79054f22d07fe4552b6341c05a4c82e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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"
|
data/lib/gamefic/action.rb
CHANGED
@@ -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 :
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
#
|
36
|
-
|
37
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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
|
data/lib/gamefic/character.rb
CHANGED
@@ -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::
|
10
|
-
attr_reader :
|
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
|
-
|
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
|
-
|
126
|
-
|
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
|
-
|
130
|
-
|
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
|
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
|
-
|
202
|
+
order.freeze
|
203
|
+
@last_action = order
|
144
204
|
end
|
145
205
|
|
146
|
-
|
147
|
-
|
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
|
157
|
-
|
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
|
|