gamefic 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|