story-teller 1.1.3
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/LICENSE +623 -0
- data/README.md +188 -0
- data/Rakefile +58 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +90 -0
- data/game/example.rb +105 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/admin.inf.rb +185 -0
- data/game/grammar/builder.inf.rb +310 -0
- data/game/grammar/game_grammar.inf.rb +6 -0
- data/game/grammar/meta.inf.rb +41 -0
- data/game/languages/english.rb +571 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/modules/parser_extensions.rb +264 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +35 -0
- data/game/verbs/metaverbs.rb +2066 -0
- data/lib/story_teller/application.rb +82 -0
- data/lib/story_teller/cli.rb +35 -0
- data/lib/story_teller/color.rb +144 -0
- data/lib/story_teller/config.rb +61 -0
- data/lib/story_teller/curses_adapter.rb +30 -0
- data/lib/story_teller/database.rb +527 -0
- data/lib/story_teller/game/loader.rb +276 -0
- data/lib/story_teller/game.rb +22 -0
- data/lib/story_teller/inform/models.rb +42 -0
- data/lib/story_teller/inform/relational/link.rb +239 -0
- data/lib/story_teller/inform/relational/module.rb +203 -0
- data/lib/story_teller/inform/relational/object.rb +546 -0
- data/lib/story_teller/inform/relational/tag.rb +152 -0
- data/lib/story_teller/options.rb +151 -0
- data/lib/story_teller/persistence.rb +340 -0
- data/lib/story_teller/player_character.rb +99 -0
- data/lib/story_teller/privileges.rb +55 -0
- data/lib/story_teller/runtime.rb +381 -0
- data/lib/story_teller/snapshots.rb +412 -0
- data/lib/story_teller/terminal.rb +58 -0
- data/lib/story_teller/version.rb +24 -0
- data/lib/story_teller_cli.rb +34 -0
- metadata +158 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# game/modules/parser_extensions.rb
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: false
|
|
4
|
+
|
|
5
|
+
# Copyright Nels Nelson 2008-2025 but freely usable (see license)
|
|
6
|
+
#
|
|
7
|
+
# This file is part of the StoryTeller.
|
|
8
|
+
#
|
|
9
|
+
# The StoryTeller is free software: you can redistribute it and/or
|
|
10
|
+
# modify it under the terms of the GNU General Public License as published
|
|
11
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
# (at your option) any later version.
|
|
13
|
+
#
|
|
14
|
+
# The StoryTeller is distributed in the hope that it will be useful,
|
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
# GNU General Public License for more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License
|
|
20
|
+
# along with the StoryTeller. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
|
|
22
|
+
# The Inform module
|
|
23
|
+
module Inform
|
|
24
|
+
# The Parser module
|
|
25
|
+
module Parser
|
|
26
|
+
# §28 How nouns are parsed
|
|
27
|
+
#
|
|
28
|
+
# So: a parse_name routine, if provided, is expected to try to match as many
|
|
29
|
+
# words as possible starting from the current position of wn and reading them
|
|
30
|
+
# in one at a time using the NextWord() routine. Thus it must not stop just
|
|
31
|
+
# because the first word makes sense, but must keep reading and find out how
|
|
32
|
+
# many words in a row make sense. It should return:
|
|
33
|
+
#
|
|
34
|
+
# 0 if the text didn't make any sense at all,
|
|
35
|
+
# k if k words in a row of the text seem to refer to the object, or
|
|
36
|
+
# −1 to tell the parser it doesn't want to decide after all.
|
|
37
|
+
#
|
|
38
|
+
# §A5 Entry point ParseNoun routine
|
|
39
|
+
#
|
|
40
|
+
# To do the job of parsing the name property (if parse_name hasn't done it
|
|
41
|
+
# already). This takes one argument, the object in question, and returns a
|
|
42
|
+
# value as if it were a parse_name routine.
|
|
43
|
+
#
|
|
44
|
+
# The ParseNoun entry point routine returns the number of words matched, or 0
|
|
45
|
+
# if there is no match, or −1 to decline to make a decision and give the job
|
|
46
|
+
# back to the parser. Note that if −1 is returned, the word number variable
|
|
47
|
+
# wn must be left set to the first word the parser should look at — probably
|
|
48
|
+
# the same value it had when ParseNoun was called, but not necessarily.
|
|
49
|
+
def ParseNoun(obj)
|
|
50
|
+
i = @wn - 1; m = 0; p = 0
|
|
51
|
+
if obj.in? InformLibrary::Compass
|
|
52
|
+
while (word = NextWord())
|
|
53
|
+
break unless word.matches_exactly?(obj)
|
|
54
|
+
m += 1
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
while (word = NextWord())
|
|
58
|
+
word_is_plural = plural?(word)
|
|
59
|
+
# singular_word = singularize(word)
|
|
60
|
+
if word_is_plural && word.matches_last_exactly?(obj)
|
|
61
|
+
m += 1
|
|
62
|
+
elsif Prepositions.include?(word) && word.matches_any_exactly?(obj)
|
|
63
|
+
p += 1
|
|
64
|
+
m += 1
|
|
65
|
+
elsif !Prepositions.include?(word) && word.matches_any_exactly?(obj)
|
|
66
|
+
m += 1
|
|
67
|
+
elsif word.matches?(obj)
|
|
68
|
+
m += 1
|
|
69
|
+
else
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
@wn = i + 1
|
|
75
|
+
m <= 0 ? -1 : p + m
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ScoreMatchL(context, match_list, match_scores, number_matched, threshold = 0)
|
|
79
|
+
threshold += 1 if @indef_type.anybits?(OTHER_BIT)
|
|
80
|
+
threshold += 1 if @indef_type.anybits?(MY_BIT)
|
|
81
|
+
threshold += 1 if @indef_type.anybits?(THAT_BIT)
|
|
82
|
+
threshold += 1 if @indef_type.anybits?(LIT_BIT)
|
|
83
|
+
threshold += 1 if @indef_type.anybits?(UNLIT_BIT)
|
|
84
|
+
threshold += 1 if @indef_owner
|
|
85
|
+
|
|
86
|
+
if defined?(DEBUG)
|
|
87
|
+
if @parser_trace >= 4
|
|
88
|
+
print " Scoring match list: indef mode " + @indef_mode + ", type " +
|
|
89
|
+
@indef_type + ", satisfying " + threshold + " requirements:\n"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
# DEBUG
|
|
93
|
+
|
|
94
|
+
# if defined?(PREFER_HELD)
|
|
95
|
+
# a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC
|
|
96
|
+
# if @action_to_be == :Take || @action_to_be == :Remove
|
|
97
|
+
# a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC
|
|
98
|
+
# end
|
|
99
|
+
# # TODO: FIXME
|
|
100
|
+
# # Verify this is needed
|
|
101
|
+
# context = context # silence empty block warning
|
|
102
|
+
# else # not PREFER_HELD
|
|
103
|
+
a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC
|
|
104
|
+
if %i[held multiheld multiexcept].include?(context)
|
|
105
|
+
a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC
|
|
106
|
+
end
|
|
107
|
+
# end
|
|
108
|
+
# # PREFER_HELD
|
|
109
|
+
|
|
110
|
+
if number_matched > 20
|
|
111
|
+
log.warn "Matched more than 20 objects!"
|
|
112
|
+
# caller.each { |t| log.warn t }
|
|
113
|
+
end
|
|
114
|
+
# TODO: FIXME The @match_list traversal here is buggy and
|
|
115
|
+
# sometimes produces a situation where obj is nil.
|
|
116
|
+
for i in (0...number_matched)
|
|
117
|
+
# match_list.compact.each_with_index do |obj, i|
|
|
118
|
+
obj = match_list[i]; its_owner = parent(obj); its_score=0; met=0
|
|
119
|
+
# TODO: Remove:
|
|
120
|
+
if obj.nil?
|
|
121
|
+
log.warn "ScoreMatchL: obj is nil in iteration: #{i}, number_matched: " \
|
|
122
|
+
"#{number_matched}, match_list: #{match_list.length}"
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
mine = @actor.descendants
|
|
127
|
+
mine += @actor.body.descendants if @actor.respond_to?(:body)
|
|
128
|
+
other = @actors_location.descendants - mine
|
|
129
|
+
|
|
130
|
+
if defined?(DEBUG) && @scope_reason == PARSING_REASON && @parser_trace >= 10
|
|
131
|
+
println ">>In ScoreMatchL..."
|
|
132
|
+
println ">> ...context is #{context}"
|
|
133
|
+
println ">> ...@actor is #{@actor}"
|
|
134
|
+
println ">> ...mine are #{mine}"
|
|
135
|
+
println ">> ...obj is #{obj} (#{obj.identity})"
|
|
136
|
+
println ">> ...obj.in? mine are #{mine.include?(obj)}"
|
|
137
|
+
println ">> ...other are #{other}"
|
|
138
|
+
println ">> ...obj.in? other are #{other.include?(obj)}"
|
|
139
|
+
println ">> ...@indef_type & MY_BIT #{@indef_type & MY_BIT}"
|
|
140
|
+
println ">> ...@indef_type & MY_BIT != 0 #{@indef_type & MY_BIT != 0}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# met += 1 if @indef_type & OTHER_BIT != 0 && obj.notin?($itobj, $himobj, $herobj)
|
|
144
|
+
met += 1 if @indef_type.anybits?(OTHER_BIT) && !language_pronouns.values.map(&:last).include?(obj)
|
|
145
|
+
met += 1 if @indef_type.anybits?(MY_BIT) && (its_owner == @actor || mine.include?(obj))
|
|
146
|
+
met += 1 if @indef_type.anybits?(THAT_BIT) && (its_owner == @actors_location || other.include?(obj))
|
|
147
|
+
met += 1 if @indef_type.anybits?(LIT_BIT) && obj.has?(:light)
|
|
148
|
+
met += 1 if @indef_type.anybits?(UNLIT_BIT) && obj.hasnt?(:light)
|
|
149
|
+
met += 1 if @indef_owner && its_owner == @indef_owner
|
|
150
|
+
|
|
151
|
+
if defined?(DEBUG) && @scope_reason == PARSING_REASON && @parser_trace >= 10
|
|
152
|
+
println ">> ...met is #{met}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if met < threshold
|
|
156
|
+
if defined?(DEBUG)
|
|
157
|
+
if @parser_trace >= 4
|
|
158
|
+
print " " + The(match_list[i]) + " (" + match_list[i] + ") in " +
|
|
159
|
+
the(its_owner) + " is rejected (doesn't match descriptors)\n"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
# DEBUG
|
|
163
|
+
match_list[i] = nil
|
|
164
|
+
else
|
|
165
|
+
its_score = 0
|
|
166
|
+
its_score = SCORE__UNCONCEALED if obj != @player && obj.hasnt?(:concealed)
|
|
167
|
+
|
|
168
|
+
if its_owner == @actor
|
|
169
|
+
its_score += a_s
|
|
170
|
+
elsif its_owner == @actors_location
|
|
171
|
+
its_score += l_s
|
|
172
|
+
elsif its_owner != Inform::English::Compass
|
|
173
|
+
its_score += SCORE__NOTCOMPASS
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
its_score += (SCORE__CHOOSEOBJ * ChooseObjects(obj, 2))
|
|
177
|
+
|
|
178
|
+
its_score += SCORE__NOTSCENERY if obj.hasnt? :scenery
|
|
179
|
+
its_score += SCORE__NOTACTOR if obj != @actor
|
|
180
|
+
|
|
181
|
+
# A small bonus for having the correct GNA,
|
|
182
|
+
# for sorting out ambiguous articles and the like.
|
|
183
|
+
|
|
184
|
+
if @indef_cases & PowersOfTwo_TB[GetGNAOfObject(obj)]
|
|
185
|
+
its_score += SCORE__GNA
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
match_scores[i] ||= 0
|
|
189
|
+
begin
|
|
190
|
+
match_scores[i] += its_score
|
|
191
|
+
rescue StandardError => e
|
|
192
|
+
log.error "Failed to accumulate score", e
|
|
193
|
+
end
|
|
194
|
+
if defined?(DEBUG)
|
|
195
|
+
if @parser_trace >= 4
|
|
196
|
+
print " " + The(match_list[i]) + " (#{match_list[i].object_id}) in " +
|
|
197
|
+
the(its_owner) + " : #{match_scores[i]} points\n"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
# DEBUG
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# TODO: There has to be a better way
|
|
205
|
+
log.debug " ScoreMatchL match_list: #{match_list.map { |a| a&.name || 'nil' }}"
|
|
206
|
+
while (i = match_list.index(nil))
|
|
207
|
+
log.debug " ScoreMatchL deleting from match_list: #{i}"
|
|
208
|
+
match_list.delete_at(i)
|
|
209
|
+
match_scores.delete_at(i)
|
|
210
|
+
number_matched = match_list.length
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
[match_list, match_scores, number_matched]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def time_parse_routine
|
|
217
|
+
return false unless defined?(Chronic)
|
|
218
|
+
begin
|
|
219
|
+
println "Parsing time token..."
|
|
220
|
+
timestamp = Chronic.parse(consume_remaining_text)
|
|
221
|
+
println " result #=> #{timestamp}"
|
|
222
|
+
return timestamp
|
|
223
|
+
rescue StandardError => e
|
|
224
|
+
log.error e
|
|
225
|
+
@etype = e
|
|
226
|
+
end
|
|
227
|
+
false
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def id_parse_routine
|
|
231
|
+
l = TryNumber(@wn); @wn += 1
|
|
232
|
+
return false unless l.number?
|
|
233
|
+
getobject(l.to_i)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def query_parse_routine
|
|
237
|
+
l = consume_remaining_text
|
|
238
|
+
return false if l.empty?
|
|
239
|
+
@consult_words = l
|
|
240
|
+
@search_results = findobject(/#{l}.*/i) if l.length > 2
|
|
241
|
+
@search_results.first
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def text_parse_routine
|
|
245
|
+
@consult_words = consume_remaining_text
|
|
246
|
+
if @consult_words.empty?
|
|
247
|
+
@consult_words = nil
|
|
248
|
+
return false
|
|
249
|
+
end
|
|
250
|
+
GPR_TEXT
|
|
251
|
+
end
|
|
252
|
+
alias emote_parse_routine text_parse_routine
|
|
253
|
+
|
|
254
|
+
def user_parse_routine
|
|
255
|
+
l = consume_remaining_text
|
|
256
|
+
return false if l.empty?
|
|
257
|
+
@consult_words = l
|
|
258
|
+
@search_results = Account.where(username: l) if l.length > 2
|
|
259
|
+
@search_results.first
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
# module Parser
|
|
263
|
+
end
|
|
264
|
+
# module Inform
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# The Inform module
|
|
5
|
+
module Inform
|
|
6
|
+
# The Verb module
|
|
7
|
+
module Verbs
|
|
8
|
+
def ExampleSub
|
|
9
|
+
"This is just an example."
|
|
10
|
+
end
|
|
11
|
+
def GrinSub
|
|
12
|
+
"You grin."
|
|
13
|
+
end
|
|
14
|
+
def SmileSub
|
|
15
|
+
"You smile."
|
|
16
|
+
end
|
|
17
|
+
def HugSub
|
|
18
|
+
return "You wrap your arms around your torso and squeeze." if noun == @player
|
|
19
|
+
return if ObjectIsUntouchable(noun)
|
|
20
|
+
if noun.life? :Hug
|
|
21
|
+
j = RunLife(noun, :Hug)
|
|
22
|
+
if j.is_a?(String)
|
|
23
|
+
println j
|
|
24
|
+
else
|
|
25
|
+
println "You give " + the(noun) + " a big hug!"
|
|
26
|
+
end
|
|
27
|
+
return false if j
|
|
28
|
+
elsif noun.has? :animate
|
|
29
|
+
return The(noun) + " graciously declines."
|
|
30
|
+
end
|
|
31
|
+
return true if AfterRoutines()
|
|
32
|
+
"You give " + the(noun) + " a big hug!"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|