gamefic 0.0.1
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 +7 -0
- data/lib/gamefic/action.rb +61 -0
- data/lib/gamefic/action_ext/container.rb +36 -0
- data/lib/gamefic/action_ext/inventory.rb +37 -0
- data/lib/gamefic/action_ext/look.rb +52 -0
- data/lib/gamefic/action_ext/meta.rb +10 -0
- data/lib/gamefic/action_ext/traversal.rb +39 -0
- data/lib/gamefic/base.rb +10 -0
- data/lib/gamefic/character.rb +72 -0
- data/lib/gamefic/core_ext/array.rb +40 -0
- data/lib/gamefic/core_ext/string.rb +42 -0
- data/lib/gamefic/describable.rb +27 -0
- data/lib/gamefic/director.rb +133 -0
- data/lib/gamefic/engine.rb +18 -0
- data/lib/gamefic/entity.rb +76 -0
- data/lib/gamefic/entity_ext/container.rb +9 -0
- data/lib/gamefic/entity_ext/fixture.rb +9 -0
- data/lib/gamefic/entity_ext/item.rb +11 -0
- data/lib/gamefic/entity_ext/itemized.rb +7 -0
- data/lib/gamefic/entity_ext/portable.rb +33 -0
- data/lib/gamefic/entity_ext/portal.rb +47 -0
- data/lib/gamefic/entity_ext/room.rb +31 -0
- data/lib/gamefic/entity_ext/scenery.rb +7 -0
- data/lib/gamefic/entity_ext/zone.rb +8 -0
- data/lib/gamefic/keywords.rb +39 -0
- data/lib/gamefic/node.rb +62 -0
- data/lib/gamefic/plot.rb +188 -0
- data/lib/gamefic/query.rb +187 -0
- data/lib/gamefic/syntax.rb +117 -0
- data/lib/gamefic/user.rb +64 -0
- data/lib/gamefic.rb +12 -0
- metadata +73 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
module Portable
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
#Action.new("take", Context::ENVIRONMENT) { |actor, object|
|
8
|
+
# if object.parent == actor
|
9
|
+
# actor.tell "You're already carrying #{object.longname}."
|
10
|
+
# else
|
11
|
+
# if object.kind_of? Portable
|
12
|
+
# object.parent = actor
|
13
|
+
# actor.tell "You take #{object.longname}."
|
14
|
+
# else
|
15
|
+
# actor.tell "You can't carry #{object.longname}."
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#}
|
19
|
+
#Parser.translate("get [object]", "take [object]")
|
20
|
+
|
21
|
+
#Action.new("drop", Context::INVENTORY) { |actor, object|
|
22
|
+
# if object.parent != actor
|
23
|
+
# actor.tell "You're not carrying it."
|
24
|
+
# else
|
25
|
+
# object.parent = actor.parent
|
26
|
+
# actor.tell "You drop #{object.longname}."
|
27
|
+
# end
|
28
|
+
#}
|
29
|
+
|
30
|
+
#Parser.translate "drop [object] floor", "drop [object]"
|
31
|
+
#Parser.translate "drop [object] ground", "drop [object]"
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
class Portal < Entity
|
4
|
+
attr_accessor :destination
|
5
|
+
#def initialize
|
6
|
+
# super
|
7
|
+
# @destination = nil
|
8
|
+
#end
|
9
|
+
def find_reverse
|
10
|
+
rev = Portal.reverse(self.name)
|
11
|
+
if rev != nil
|
12
|
+
destination.children.that_are(Portal).each { |c|
|
13
|
+
if c.name == rev
|
14
|
+
return c
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def self.reverse(direction)
|
20
|
+
case direction.downcase
|
21
|
+
when "north"
|
22
|
+
"south"
|
23
|
+
when "south"
|
24
|
+
"north"
|
25
|
+
when "west"
|
26
|
+
"east"
|
27
|
+
when "east"
|
28
|
+
"west"
|
29
|
+
when "northwest"
|
30
|
+
"southeast"
|
31
|
+
when "southeast"
|
32
|
+
"northwest"
|
33
|
+
when "northeast"
|
34
|
+
"southwest"
|
35
|
+
when "southwest"
|
36
|
+
"northeast"
|
37
|
+
when "up"
|
38
|
+
"down"
|
39
|
+
when "down"
|
40
|
+
"up"
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "gamefic/entity_ext/portal"
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
|
5
|
+
class Room < Entity
|
6
|
+
def post_initialize
|
7
|
+
|
8
|
+
end
|
9
|
+
def connect(destination, direction, type = Portal, two_way = true)
|
10
|
+
portal = type.new self.plot, :name => direction, :parent => self, :destination => destination
|
11
|
+
if two_way == true
|
12
|
+
reverse = Portal.reverse(direction)
|
13
|
+
if reverse == nil
|
14
|
+
raise "\"#{direction.cap_first}\" does not have an opposite direction"
|
15
|
+
end
|
16
|
+
portal2 = type.new(self.plot, {
|
17
|
+
:name => reverse,
|
18
|
+
:parent => destination,
|
19
|
+
:destination => self
|
20
|
+
})
|
21
|
+
end
|
22
|
+
portal
|
23
|
+
end
|
24
|
+
def tell(message, refresh = false)
|
25
|
+
children.each { |c|
|
26
|
+
c.tell message, refresh
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Arrays of keywords that can be compared for matches.
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
|
5
|
+
class Keywords < Array
|
6
|
+
def initialize(statement = '')
|
7
|
+
if statement.kind_of?(Keywords)
|
8
|
+
self.concat statement
|
9
|
+
else
|
10
|
+
self.concat statement.to_s.gsub(/[^a-z0-9]/i, ' ').strip.downcase.split(' ')
|
11
|
+
end
|
12
|
+
self.delete_if { |w|
|
13
|
+
w.length < 2 or w == 'an' or w == 'the'
|
14
|
+
}
|
15
|
+
self.uniq!
|
16
|
+
end
|
17
|
+
def found_in(other)
|
18
|
+
matches = 0.0
|
19
|
+
self.each { |my_word|
|
20
|
+
if (other.include?(my_word))
|
21
|
+
matches = matches + 1.0
|
22
|
+
else
|
23
|
+
other.each { |other_word|
|
24
|
+
if my_word.length < other_word.length
|
25
|
+
if other_word[0, my_word.length] == my_word
|
26
|
+
matches = matches + (my_word.length.to_f / other_word.length.to_f)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
}
|
32
|
+
return matches
|
33
|
+
end
|
34
|
+
def to_s
|
35
|
+
self.join(' ')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/gamefic/node.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
module Node
|
4
|
+
def children
|
5
|
+
if @children == nil
|
6
|
+
@children = Array.new
|
7
|
+
end
|
8
|
+
@children.clone
|
9
|
+
end
|
10
|
+
def flatten
|
11
|
+
array = Array.new
|
12
|
+
children.each { |child|
|
13
|
+
array = array + recurse_flatten(child)
|
14
|
+
}
|
15
|
+
return array
|
16
|
+
end
|
17
|
+
protected
|
18
|
+
def add_child(node)
|
19
|
+
children
|
20
|
+
@children.push(node)
|
21
|
+
end
|
22
|
+
def rem_child(node)
|
23
|
+
children
|
24
|
+
@children.delete(node)
|
25
|
+
end
|
26
|
+
def concat_children(children)
|
27
|
+
children
|
28
|
+
@children.concat(children)
|
29
|
+
end
|
30
|
+
private
|
31
|
+
def recurse_flatten(node)
|
32
|
+
array = Array.new
|
33
|
+
array.push(node)
|
34
|
+
node.children.each { |child|
|
35
|
+
array = array + recurse_flatten(child)
|
36
|
+
}
|
37
|
+
return array
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Branch
|
42
|
+
include Node
|
43
|
+
def parent
|
44
|
+
@parent
|
45
|
+
end
|
46
|
+
def parent=(node)
|
47
|
+
if node == self
|
48
|
+
raise "Entity cannot be its own parent"
|
49
|
+
end
|
50
|
+
if @parent != node
|
51
|
+
if @parent != nil
|
52
|
+
@parent.send(:rem_child, self)
|
53
|
+
end
|
54
|
+
@parent = node
|
55
|
+
if @parent != nil
|
56
|
+
@parent.send(:add_child, self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/gamefic/plot.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
module Gamefic
|
2
|
+
|
3
|
+
class Plot
|
4
|
+
attr_reader :scenes, :commands, :conclusions, :declared_scripts
|
5
|
+
attr_accessor :story
|
6
|
+
def commandwords
|
7
|
+
words = Array.new
|
8
|
+
@syntaxes.each { |s|
|
9
|
+
words.push(s.template.split_words[0])
|
10
|
+
}
|
11
|
+
words.uniq
|
12
|
+
end
|
13
|
+
def initialize
|
14
|
+
@scenes = Hash.new
|
15
|
+
@commands = Hash.new
|
16
|
+
@syntaxes = Array.new
|
17
|
+
@conclusions = Hash.new
|
18
|
+
@update_procs = Array.new
|
19
|
+
@declared_scripts = Array.new
|
20
|
+
@entities = Array.new
|
21
|
+
post_initialize
|
22
|
+
end
|
23
|
+
def post_initialize
|
24
|
+
Action.defaults.each {|a|
|
25
|
+
add_action a
|
26
|
+
}
|
27
|
+
Syntax.defaults.each {|s|
|
28
|
+
add_syntax s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
#def require_defaults
|
32
|
+
#Dir[File.dirname(__FILE__) + '/entity_ext/*.rb'].each do |file|
|
33
|
+
# require file
|
34
|
+
#end
|
35
|
+
#Dir[File.dirname(__FILE__) + '/action_ext/*.rb'].each do |file|
|
36
|
+
# require_script file
|
37
|
+
#end
|
38
|
+
#end
|
39
|
+
def action(command, *queries, &proc)
|
40
|
+
act = Action.new(self, command, *queries, &proc)
|
41
|
+
end
|
42
|
+
def respond(command, *queries, &proc)
|
43
|
+
self.action(command, *queries, &proc)
|
44
|
+
end
|
45
|
+
def entity(cls, args)
|
46
|
+
ent = cls.new(self, args)
|
47
|
+
if ent.kind_of?(Entity) == false
|
48
|
+
raise "Invalid entity class"
|
49
|
+
end
|
50
|
+
#@entities.push ent
|
51
|
+
ent
|
52
|
+
end
|
53
|
+
def make(cls, args = {})
|
54
|
+
self.entity(cls, args)
|
55
|
+
end
|
56
|
+
def syntax(*args)
|
57
|
+
syn = Syntax.new(self, *args)
|
58
|
+
@syntaxes.push syn
|
59
|
+
syn
|
60
|
+
end
|
61
|
+
def xlate(*args)
|
62
|
+
syntax(*args)
|
63
|
+
end
|
64
|
+
#def add_entity(entity)
|
65
|
+
# if @entities.include?(entity) == false
|
66
|
+
# @entities.push entity
|
67
|
+
# end
|
68
|
+
#end
|
69
|
+
def rem_entity(entity)
|
70
|
+
@entities.delete(entity)
|
71
|
+
end
|
72
|
+
def entities
|
73
|
+
@entities.clone
|
74
|
+
end
|
75
|
+
def syntaxes
|
76
|
+
@syntaxes.clone
|
77
|
+
end
|
78
|
+
def on_update(&block)
|
79
|
+
@update_procs.push block
|
80
|
+
end
|
81
|
+
def introduction (&proc)
|
82
|
+
@introduction = proc
|
83
|
+
end
|
84
|
+
def conclusion(key, &proc)
|
85
|
+
@conclusions[key] = proc
|
86
|
+
end
|
87
|
+
def scene(key, &proc)
|
88
|
+
@scenes[key] = proc
|
89
|
+
end
|
90
|
+
def introduce(player)
|
91
|
+
if @introduction != nil
|
92
|
+
@introduction.call(player)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
def conclude(player, key = nil)
|
96
|
+
if key != nil and @conclusions[key]
|
97
|
+
@conclusions[key].call(player)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
def cue scene
|
101
|
+
@scenes[scene].call
|
102
|
+
end
|
103
|
+
def passthru
|
104
|
+
Director::Delegate.passthru
|
105
|
+
end
|
106
|
+
def update
|
107
|
+
@update_procs.each { |p|
|
108
|
+
p.call
|
109
|
+
}
|
110
|
+
#@entities.flatten.each { |e|
|
111
|
+
@entities.each { |e|
|
112
|
+
#recursive_update e
|
113
|
+
e.update
|
114
|
+
}
|
115
|
+
end
|
116
|
+
def tell entities, message, refresh = false
|
117
|
+
entities.each { |entity|
|
118
|
+
entity.tell message, refresh
|
119
|
+
}
|
120
|
+
end
|
121
|
+
def load_script filename
|
122
|
+
plot = self
|
123
|
+
eval File.read(filename), nil, filename, 1
|
124
|
+
end
|
125
|
+
def require_script filename
|
126
|
+
if @declared_scripts.include?(filename) == false
|
127
|
+
@declared_scripts.push(filename)
|
128
|
+
load_script filename
|
129
|
+
end
|
130
|
+
end
|
131
|
+
private
|
132
|
+
def recursive_update(entity)
|
133
|
+
entity.update
|
134
|
+
entity.children.each { |e|
|
135
|
+
recursive_update e
|
136
|
+
}
|
137
|
+
end
|
138
|
+
def add_syntax syntax
|
139
|
+
if @commands[syntax.command] == nil
|
140
|
+
raise "Action \"#{syntax.command}\" does not exist"
|
141
|
+
end
|
142
|
+
@syntaxes.unshift syntax
|
143
|
+
@syntaxes.sort! { |a, b|
|
144
|
+
al = a.template.split_words.length
|
145
|
+
bl = b.template.split_words.length
|
146
|
+
if al == bl
|
147
|
+
# For syntaxes of the same length, creation order takes precedence
|
148
|
+
0
|
149
|
+
else
|
150
|
+
bl <=> al
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
154
|
+
def add_action(action)
|
155
|
+
if (@commands[action.command] == nil)
|
156
|
+
@commands[action.command] = Array.new
|
157
|
+
end
|
158
|
+
@commands[action.command].unshift action
|
159
|
+
@commands[action.command].sort! { |a, b|
|
160
|
+
if a.specificity == b.specificity
|
161
|
+
# For actions of the same length, creation order takes precedence
|
162
|
+
0
|
163
|
+
else
|
164
|
+
b.specificity <=> a.specificity
|
165
|
+
end
|
166
|
+
}
|
167
|
+
user_friendly = action.command.to_s.sub(/_/, ' ')
|
168
|
+
args = Array.new
|
169
|
+
used_names = Array.new
|
170
|
+
action.queries.each { |c|
|
171
|
+
num = 1
|
172
|
+
new_name = "var"
|
173
|
+
while used_names.include? new_name
|
174
|
+
num = num + 1
|
175
|
+
new_name = "var#{num}"
|
176
|
+
end
|
177
|
+
used_names.push new_name
|
178
|
+
user_friendly += " :#{new_name}"
|
179
|
+
args.push new_name.to_sym
|
180
|
+
}
|
181
|
+
Syntax.new self, *[user_friendly, action.command] + args
|
182
|
+
end
|
183
|
+
def add_entity(entity)
|
184
|
+
@entities.push entity
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require "gamefic/keywords"
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
|
5
|
+
class Query
|
6
|
+
FAMILY = :family
|
7
|
+
CHILDREN = :children
|
8
|
+
SIBLINGS = :siblings
|
9
|
+
PARENT = :parent
|
10
|
+
SELF = :self
|
11
|
+
PLOT = :plot
|
12
|
+
STRING = :string
|
13
|
+
def initialize(context, *arguments)
|
14
|
+
if context != :family and context != :children and context != :siblings and context != :parent and context != :self and context != :plot and context != :string
|
15
|
+
raise "Invalid Query context '#{context}'"
|
16
|
+
end
|
17
|
+
#if context == :string and arguments.length > 0
|
18
|
+
# raise "Query with :string context cannot take additional arguments."
|
19
|
+
#end
|
20
|
+
@context = context
|
21
|
+
@arguments = arguments
|
22
|
+
end
|
23
|
+
def execute(subject, description)
|
24
|
+
case context
|
25
|
+
when :self
|
26
|
+
array = [subject]
|
27
|
+
when :parent
|
28
|
+
array = [subject.parent]
|
29
|
+
when :plot
|
30
|
+
array = subject.plot.entities
|
31
|
+
when :children
|
32
|
+
array = subject.children
|
33
|
+
when :siblings
|
34
|
+
array = subject.parent.children
|
35
|
+
when :family
|
36
|
+
array = subject.children + subject.parent.children
|
37
|
+
when :string
|
38
|
+
if @arguments.length == 0
|
39
|
+
return Matches.new([description], description, '')
|
40
|
+
end
|
41
|
+
keywords = Keywords.new(description)
|
42
|
+
args = Keywords.new(@arguments)
|
43
|
+
found = Array.new
|
44
|
+
remainder = Array.new
|
45
|
+
keywords.each { |key|
|
46
|
+
if args.include?(key)
|
47
|
+
found.push key
|
48
|
+
else
|
49
|
+
remainder.push key
|
50
|
+
end
|
51
|
+
}
|
52
|
+
if found.length > 0
|
53
|
+
return Matches.new([description], found.join(' '), remainder.join(' '))
|
54
|
+
else
|
55
|
+
return Matches.new([], '', description)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise "Unrecognized: #{context}"
|
59
|
+
end
|
60
|
+
@arguments.each { |arg|
|
61
|
+
if arg.kind_of?(Class) or arg.kind_of?(Module)
|
62
|
+
array.delete_if { |entity| entity.kind_of?(arg) == false }
|
63
|
+
else
|
64
|
+
if array.include?(arg)
|
65
|
+
array = Array.new
|
66
|
+
array.push(arg)
|
67
|
+
else
|
68
|
+
array.clear
|
69
|
+
end
|
70
|
+
end
|
71
|
+
}
|
72
|
+
return Query.match(description, array)
|
73
|
+
end
|
74
|
+
def context
|
75
|
+
@context
|
76
|
+
end
|
77
|
+
def Query.match(description, array)
|
78
|
+
array.each {|e|
|
79
|
+
if e.uid == description
|
80
|
+
return Matches.new([e], description, '')
|
81
|
+
end
|
82
|
+
}
|
83
|
+
keywords = description.split_words
|
84
|
+
results = array
|
85
|
+
used = Array.new
|
86
|
+
if results.length > 0
|
87
|
+
previous_match = false
|
88
|
+
while keywords.length > 0
|
89
|
+
used.push keywords.shift
|
90
|
+
new_results = Array.new
|
91
|
+
mostMatches = 0.0
|
92
|
+
results.each { |r|
|
93
|
+
words = Keywords.new(used.join(' '))
|
94
|
+
if words.length > 0
|
95
|
+
matches = words.found_in r.keywords
|
96
|
+
if matches >= mostMatches and matches > 0
|
97
|
+
if matches - mostMatches > 0.5
|
98
|
+
new_results = Array.new
|
99
|
+
end
|
100
|
+
new_results.push r
|
101
|
+
mostMatches = matches
|
102
|
+
end
|
103
|
+
end
|
104
|
+
}
|
105
|
+
if new_results.length == 0
|
106
|
+
if previous_match == true
|
107
|
+
keywords.unshift used.pop
|
108
|
+
if used.length == 0
|
109
|
+
results = new_results
|
110
|
+
end
|
111
|
+
break
|
112
|
+
end
|
113
|
+
else
|
114
|
+
previous_match = true
|
115
|
+
results = new_results
|
116
|
+
if results.length == 1
|
117
|
+
break
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
if previous_match == false
|
122
|
+
# Scrolled through every word and not a single thing matched
|
123
|
+
results = Array.new
|
124
|
+
end
|
125
|
+
end
|
126
|
+
return Matches.new(results, used.join(' '), keywords.join(' '))
|
127
|
+
end
|
128
|
+
def Query.match_one(description, array)
|
129
|
+
result = self.match(description, array)
|
130
|
+
if result.objects.length == 1
|
131
|
+
return result.objects[0]
|
132
|
+
end
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
def specificity
|
136
|
+
if @specificity == nil
|
137
|
+
@specificity = 0
|
138
|
+
case @context
|
139
|
+
when :children
|
140
|
+
@specificity += 50
|
141
|
+
when :siblings
|
142
|
+
@specificity += 40
|
143
|
+
when :family
|
144
|
+
@specificity += 30
|
145
|
+
when :parent
|
146
|
+
@specificity += 20
|
147
|
+
when :self
|
148
|
+
@specificity += 10
|
149
|
+
when :string
|
150
|
+
@specificity = 1
|
151
|
+
#return @specificity
|
152
|
+
end
|
153
|
+
magnitude = 1
|
154
|
+
@arguments.each { |item|
|
155
|
+
if item.kind_of?(Entity)
|
156
|
+
@specificity += (magnitude * 10)
|
157
|
+
item = item.class
|
158
|
+
end
|
159
|
+
if item.kind_of?(Class)
|
160
|
+
s = item
|
161
|
+
while s != nil
|
162
|
+
@specificity += magnitude
|
163
|
+
s = s.superclass
|
164
|
+
end
|
165
|
+
else
|
166
|
+
@specificity += magnitude
|
167
|
+
end
|
168
|
+
magnitude = magnitude * 10
|
169
|
+
}
|
170
|
+
end
|
171
|
+
@specificity
|
172
|
+
end
|
173
|
+
class Matches
|
174
|
+
attr_reader :objects, :matching_text, :remainder
|
175
|
+
def initialize(objects, matching_text, remainder)
|
176
|
+
@objects = objects
|
177
|
+
@matching_text = matching_text
|
178
|
+
@remainder = remainder
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class Subquery < Query
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|