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
@@ -1,4 +1,15 @@
|
|
1
1
|
class Array
|
2
|
+
# Get a subset of the array that matches the argument.
|
3
|
+
# If the argument is a Class or Module, the elements must be of the type.
|
4
|
+
# If the argument is a Symbol, it should be a method for which the elements must return true.
|
5
|
+
# If the argument is an Object, the elements must equal the object.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# animals = ['dog', 'cat', nil]
|
9
|
+
# animals.that_are(String) #=> ['dog', 'cat']
|
10
|
+
# animals.that_are('dog') #=> ['dog']
|
11
|
+
# animals.that_are(:nil?) #=> [nil]
|
12
|
+
#
|
2
13
|
# @return [Array]
|
3
14
|
def that_are(cls)
|
4
15
|
if (cls.kind_of?(Class) or cls.kind_of?(Module))
|
@@ -12,6 +23,10 @@ class Array
|
|
12
23
|
return Array.new
|
13
24
|
end
|
14
25
|
end
|
26
|
+
|
27
|
+
# Get a subset of the array that does not match the argument.
|
28
|
+
# See Array#that_are for information about how arguments are evaluated.
|
29
|
+
#
|
15
30
|
# @return [Array]
|
16
31
|
def that_are_not(cls)
|
17
32
|
if (cls.kind_of?(Class) or cls.kind_of?(Module))
|
@@ -22,26 +37,50 @@ class Array
|
|
22
37
|
return self.clone - [cls]
|
23
38
|
end
|
24
39
|
end
|
40
|
+
|
41
|
+
# Get a random element from the array.
|
42
|
+
# @deprecated Use Array#sample instead.
|
43
|
+
#
|
25
44
|
def random
|
26
45
|
return self[rand(self.length)]
|
27
46
|
end
|
47
|
+
|
48
|
+
# Pop a random element from the array.
|
49
|
+
# @deprecated Use Array#pop_sample instead.
|
50
|
+
#
|
28
51
|
def pop_random
|
52
|
+
pop_sample
|
53
|
+
end
|
54
|
+
|
55
|
+
# Pop a random element from the array.
|
56
|
+
#
|
57
|
+
def pop_sample
|
29
58
|
delete_at(rand(self.length))
|
30
59
|
end
|
60
|
+
|
61
|
+
# @todo Candidate for deprecation
|
31
62
|
# @return [Array]
|
32
|
-
def shuffle
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
63
|
+
#def shuffle
|
64
|
+
# self.sort { |a, b|
|
65
|
+
# rand(3) <=> rand(3)
|
66
|
+
# }
|
67
|
+
#end
|
68
|
+
|
69
|
+
# @todo Candidate for deprecation
|
37
70
|
# @return [Array]
|
38
|
-
def shuffle!
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
71
|
+
#def shuffle!
|
72
|
+
# self.sort! { |a, b|
|
73
|
+
# rand(3) <=> rand(3)
|
74
|
+
# }
|
75
|
+
#end
|
76
|
+
|
77
|
+
# Get a string representation of the array that separates elements with
|
44
78
|
# commas and adds a conjunction before the last element.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# animals = ['a dog', 'a cat', 'a mouse']
|
82
|
+
# animals.join_and #=> 'a dog, a cat, and a mouse'
|
83
|
+
#
|
45
84
|
# @return [String]
|
46
85
|
def join_and(sep = ', ', andSep = ' and ', serial = true)
|
47
86
|
if self.length < 3
|
@@ -51,6 +90,9 @@ class Array
|
|
51
90
|
start.join(sep) + "#{serial ? sep.strip : ''}#{andSep}#{self.last}"
|
52
91
|
end
|
53
92
|
end
|
93
|
+
|
94
|
+
# @see Array#join_and
|
95
|
+
#
|
54
96
|
# @return [String]
|
55
97
|
def join_or(sep = ', ', orSep = ' or ', serial = true)
|
56
98
|
join_and(sep, orSep, serial)
|
data/lib/gamefic/describable.rb
CHANGED
@@ -1,38 +1,48 @@
|
|
1
|
-
require 'gamefic/keywords'
|
1
|
+
#require 'gamefic/keywords'
|
2
2
|
require 'gamefic/grammar'
|
3
3
|
|
4
4
|
module Gamefic
|
5
5
|
|
6
|
-
# Add a variety of text properties for naming, describing, and
|
7
|
-
#
|
6
|
+
# Add a variety of text properties for naming, describing, and referencing
|
7
|
+
# objects.
|
8
8
|
module Describable
|
9
9
|
include Grammar::Person, Grammar::Plural
|
10
|
+
include Matchable
|
11
|
+
|
12
|
+
# @return [String]
|
10
13
|
attr_reader :name
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :synonyms
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :indefinite_article
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :definite_article
|
23
|
+
|
14
24
|
# Get a set of Keywords associated with the object.
|
15
25
|
# Keywords are typically the words in the object's name plus its synonyms.
|
16
26
|
#
|
17
27
|
# @return [Keywords]
|
18
28
|
def keywords
|
19
|
-
|
29
|
+
@keywords ||= "#{definite_article} #{indefinite_article} #{name} #{synonyms}".downcase.split(Matchable::SPLIT_REGEXP).uniq
|
20
30
|
end
|
21
|
-
|
31
|
+
|
22
32
|
# Get the name of the object with an indefinite article.
|
23
33
|
# Note: proper-named objects never append an article, though an article
|
24
34
|
# may be included in its proper name.
|
25
35
|
#
|
26
36
|
# @return [String]
|
27
37
|
def indefinitely
|
28
|
-
((proper_named? or indefinite_article == '') ? '' : "#{indefinite_article} ") + name
|
38
|
+
((proper_named? or indefinite_article == '') ? '' : "#{indefinite_article} ") + name.to_s
|
29
39
|
end
|
30
40
|
|
31
41
|
# Get the name of the object with a definite article.
|
32
42
|
# Note: proper-named objects never append an article, though an article
|
33
43
|
# may be included in its proper name.
|
34
44
|
def definitely
|
35
|
-
((proper_named? or definite_article == '') ? '' : "#{definite_article} ") + name
|
45
|
+
((proper_named? or definite_article == '') ? '' : "#{definite_article} ") + name.to_s
|
36
46
|
end
|
37
47
|
|
38
48
|
# Get the definite article for this object.
|
@@ -42,6 +52,16 @@ module Gamefic
|
|
42
52
|
@definite_article || "the"
|
43
53
|
end
|
44
54
|
|
55
|
+
def definite_article= article
|
56
|
+
@keywords = nil
|
57
|
+
@definite_article = article
|
58
|
+
end
|
59
|
+
|
60
|
+
def indefinite_article= article
|
61
|
+
@keywords = nil
|
62
|
+
@indefinite_article = article
|
63
|
+
end
|
64
|
+
|
45
65
|
# Is the object proper-named?
|
46
66
|
# Proper-named objects typically do not add articles to their names when
|
47
67
|
# referenced #definitely or #indefinitely, e.g., "Jane Doe" instead of
|
@@ -54,7 +74,7 @@ module Gamefic
|
|
54
74
|
|
55
75
|
# Set whether the object has a proper name.
|
56
76
|
#
|
57
|
-
# @param [Boolean]
|
77
|
+
# @param bool [Boolean]
|
58
78
|
def proper_named=(bool)
|
59
79
|
if bool == true
|
60
80
|
if @definite_article != nil
|
@@ -71,6 +91,7 @@ module Gamefic
|
|
71
91
|
#
|
72
92
|
# @param value [String]
|
73
93
|
def name=(value)
|
94
|
+
@keywords = nil
|
74
95
|
words = value.split_words
|
75
96
|
if ['a','an'].include?(words[0].downcase)
|
76
97
|
@indefinite_article = words[0].downcase
|
@@ -120,6 +141,11 @@ module Gamefic
|
|
120
141
|
end
|
121
142
|
end
|
122
143
|
|
144
|
+
def synonyms= text
|
145
|
+
@keywords = nil
|
146
|
+
@synonyms = text
|
147
|
+
end
|
148
|
+
|
123
149
|
# Set the object's default description.
|
124
150
|
# The default description is typically set in an object's initialization
|
125
151
|
# to ensure that a non-empty string is available when a instance-specific
|
data/lib/gamefic/engine/base.rb
CHANGED
@@ -29,22 +29,35 @@ module Gamefic
|
|
29
29
|
def run
|
30
30
|
connect
|
31
31
|
@plot.introduce @character
|
32
|
+
@user.update @character.state
|
32
33
|
turn until @character.concluded?
|
33
|
-
print @user.flush
|
34
|
+
#print @user.flush
|
34
35
|
end
|
35
36
|
|
36
37
|
def turn
|
37
38
|
@plot.ready
|
38
|
-
|
39
|
+
unless @character.state[:options].nil?
|
40
|
+
list = '<ol class="multiple_choice">'
|
41
|
+
@character.state[:options].each { |o|
|
42
|
+
list += "<li><a href=\"#\" rel=\"gamefic\" data-command=\"#{o}\">#{o}</a></li>"
|
43
|
+
}
|
44
|
+
list += "</ol>"
|
45
|
+
@character.tell list
|
46
|
+
end
|
47
|
+
#print @user.flush
|
48
|
+
@user.update @character.state
|
49
|
+
#@character.flush
|
39
50
|
if @character.queue.empty?
|
40
51
|
receive
|
41
52
|
end
|
42
53
|
@plot.update
|
43
|
-
print @user.flush
|
54
|
+
#print @user.flush
|
55
|
+
@user.update @character.state
|
56
|
+
#@character.flush
|
44
57
|
end
|
45
58
|
|
46
59
|
def receive
|
47
|
-
print @character.scene.
|
60
|
+
print @character.scene.prompt + ' '
|
48
61
|
input = STDIN.gets
|
49
62
|
@character.queue.push input unless input.nil?
|
50
63
|
end
|
data/lib/gamefic/engine/tty.rb
CHANGED
@@ -5,6 +5,10 @@ module Gamefic
|
|
5
5
|
# Extend Engine::Base to connect with User::Tty, which provides ANSI
|
6
6
|
# formatting for HTML.
|
7
7
|
#
|
8
|
+
# @note Due to their dependency on io/console, User::Tty and Engine::Tty are
|
9
|
+
# not included in the core Gamefic library. `require gamefic/tty` if you
|
10
|
+
# need them.
|
11
|
+
#
|
8
12
|
class Engine::Tty < Engine::Base
|
9
13
|
def post_initialize
|
10
14
|
self.user_class = Gamefic::User::Tty
|
data/lib/gamefic/entity.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
require "gamefic/node"
|
2
2
|
require "gamefic/describable"
|
3
|
-
require
|
3
|
+
require 'gamefic/messaging'
|
4
4
|
|
5
5
|
module Gamefic
|
6
6
|
|
7
7
|
class Entity
|
8
8
|
include Node
|
9
9
|
include Describable
|
10
|
-
include
|
11
|
-
extend Serialized::ClassMethods
|
10
|
+
include Messaging
|
12
11
|
include Grammar::WordAdapter
|
13
|
-
|
12
|
+
|
14
13
|
attr_reader :session
|
15
|
-
|
16
|
-
|
14
|
+
|
17
15
|
def initialize(args = {})
|
18
16
|
pre_initialize
|
19
17
|
args.each { |key, value|
|
@@ -39,15 +37,6 @@ module Gamefic
|
|
39
37
|
# raise NotImplementedError, "#{self.class} must implement post_initialize"
|
40
38
|
end
|
41
39
|
|
42
|
-
def tell(message)
|
43
|
-
#TODO: Should this even be here? In all likelihood, only Characters receive tells, right?
|
44
|
-
#TODO: On second thought, it might be interesting to see logs from an npc point of view.
|
45
|
-
end
|
46
|
-
|
47
|
-
def stream(message)
|
48
|
-
# Unlike tell, this method sends raw data without formatting.
|
49
|
-
end
|
50
|
-
|
51
40
|
# Execute the entity's on_update blocks.
|
52
41
|
# This method is typically called by the Engine that manages game execution.
|
53
42
|
# The base method does nothing. Subclasses can override it.
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Gamefic
|
2
|
+
module Matchable
|
3
|
+
SPLIT_REGEXP = /[\s]+/
|
4
|
+
|
5
|
+
# Get an array of keywords associated with this object.
|
6
|
+
# The default implementation splits the value of self.to_s into an array.
|
7
|
+
#
|
8
|
+
# @return [Array<String>]
|
9
|
+
def keywords
|
10
|
+
self.to_s.downcase.split(SPLIT_REGEXP).uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
# Determine if this object matches the provided description.
|
14
|
+
# In a regular match, every word in the description must be a keyword.
|
15
|
+
# Fuzzy matches accept words if a keyword starts with it, e.g., "red"
|
16
|
+
# would be a fuzzy match for "reddish."
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# dog = "big red dog"
|
20
|
+
# dog.extend Gamefic::Matchable
|
21
|
+
#
|
22
|
+
# dog.match?("red dog") #=> true
|
23
|
+
# dog.match?("gray dog") #=> false
|
24
|
+
# dog.match?("red do") #=> false
|
25
|
+
#
|
26
|
+
# dog.match?("re do", fuzzy: true) #=> true
|
27
|
+
# dog.match?("red og", fuzzy: true) #=> false
|
28
|
+
#
|
29
|
+
# @return [Boolean]
|
30
|
+
def match? description, fuzzy: false
|
31
|
+
words = description.split(SPLIT_REGEXP)
|
32
|
+
return false if words.empty?
|
33
|
+
matches = 0
|
34
|
+
available = keywords
|
35
|
+
words.each { |w|
|
36
|
+
if fuzzy
|
37
|
+
available.each { |k|
|
38
|
+
if k.gsub(/[^a-z0-9]/, '').start_with?(w.downcase.gsub(/[^a-z0-9]/, ''))
|
39
|
+
matches +=1
|
40
|
+
break
|
41
|
+
end
|
42
|
+
}
|
43
|
+
else
|
44
|
+
matches +=1 if available.include?(w.downcase)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
matches == words.length
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gamefic
|
2
|
+
module Messaging
|
3
|
+
# Send a message to the entity.
|
4
|
+
# This method will automatically wrap the message in HTML paragraphs.
|
5
|
+
# To send a message without paragraph formatting, use #stream instead.
|
6
|
+
#
|
7
|
+
# @param message [String]
|
8
|
+
def tell(message)
|
9
|
+
message = "<p>#{message.strip}</p>"
|
10
|
+
# This method uses String#gsub instead of String#gsub! for
|
11
|
+
# compatibility with Opal.
|
12
|
+
message = message.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, '</p><p>')
|
13
|
+
message = message.gsub(/[ \t]*\n[ \t]*/, ' ')
|
14
|
+
#user.send message
|
15
|
+
p_set_messages messages + message
|
16
|
+
end
|
17
|
+
|
18
|
+
# Send a message to the Character as raw text.
|
19
|
+
# Unlike #tell, this method will not wrap the message in HTML paragraphs.
|
20
|
+
#
|
21
|
+
# @param message [String]
|
22
|
+
def stream(message)
|
23
|
+
p_set_messages messages + message.strip
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
def messages
|
28
|
+
@messages ||= ''
|
29
|
+
end
|
30
|
+
|
31
|
+
def output
|
32
|
+
messages
|
33
|
+
end
|
34
|
+
|
35
|
+
def flush
|
36
|
+
p_set_messages '' unless messages.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def p_set_messages str
|
42
|
+
@messages = str
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/gamefic/node.rb
CHANGED
@@ -11,6 +11,8 @@ module Gamefic
|
|
11
11
|
@children ||= []
|
12
12
|
@children.clone
|
13
13
|
end
|
14
|
+
|
15
|
+
# @return [Array]
|
14
16
|
def flatten
|
15
17
|
array = Array.new
|
16
18
|
children.each { |child|
|
@@ -18,9 +20,12 @@ module Gamefic
|
|
18
20
|
}
|
19
21
|
return array
|
20
22
|
end
|
23
|
+
|
24
|
+
# @return [Object]
|
21
25
|
def parent
|
22
26
|
@parent
|
23
27
|
end
|
28
|
+
|
24
29
|
def parent=(node)
|
25
30
|
return if node == @parent
|
26
31
|
if node == self
|
@@ -43,19 +48,30 @@ module Gamefic
|
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
51
|
+
|
52
|
+
# @return [Boolean]
|
53
|
+
def accessible?
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
46
57
|
protected
|
58
|
+
|
47
59
|
def add_child(node)
|
48
60
|
children
|
49
61
|
@children.push(node)
|
50
62
|
end
|
63
|
+
|
51
64
|
def rem_child(node)
|
52
65
|
children
|
53
66
|
@children.delete(node)
|
54
67
|
end
|
68
|
+
|
55
69
|
def concat_children(children)
|
56
70
|
children.concat(children)
|
57
71
|
end
|
72
|
+
|
58
73
|
private
|
74
|
+
|
59
75
|
def recurse_flatten(node)
|
60
76
|
array = Array.new
|
61
77
|
array.push(node)
|