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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +188 -0
  4. data/Rakefile +58 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +90 -0
  9. data/game/example.rb +105 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/admin.inf.rb +185 -0
  12. data/game/grammar/builder.inf.rb +310 -0
  13. data/game/grammar/game_grammar.inf.rb +6 -0
  14. data/game/grammar/meta.inf.rb +41 -0
  15. data/game/languages/english.rb +571 -0
  16. data/game/models/example_model.rb +2 -0
  17. data/game/modules/example_module.rb +9 -0
  18. data/game/modules/parser_extensions.rb +264 -0
  19. data/game/rules/example_state.rb +2 -0
  20. data/game/scripts/example_script.rb +2 -0
  21. data/game/topics/example_topic.rb +2 -0
  22. data/game/verbs/game_verbs.rb +35 -0
  23. data/game/verbs/metaverbs.rb +2066 -0
  24. data/lib/story_teller/application.rb +82 -0
  25. data/lib/story_teller/cli.rb +35 -0
  26. data/lib/story_teller/color.rb +144 -0
  27. data/lib/story_teller/config.rb +61 -0
  28. data/lib/story_teller/curses_adapter.rb +30 -0
  29. data/lib/story_teller/database.rb +527 -0
  30. data/lib/story_teller/game/loader.rb +276 -0
  31. data/lib/story_teller/game.rb +22 -0
  32. data/lib/story_teller/inform/models.rb +42 -0
  33. data/lib/story_teller/inform/relational/link.rb +239 -0
  34. data/lib/story_teller/inform/relational/module.rb +203 -0
  35. data/lib/story_teller/inform/relational/object.rb +546 -0
  36. data/lib/story_teller/inform/relational/tag.rb +152 -0
  37. data/lib/story_teller/options.rb +151 -0
  38. data/lib/story_teller/persistence.rb +340 -0
  39. data/lib/story_teller/player_character.rb +99 -0
  40. data/lib/story_teller/privileges.rb +55 -0
  41. data/lib/story_teller/runtime.rb +381 -0
  42. data/lib/story_teller/snapshots.rb +412 -0
  43. data/lib/story_teller/terminal.rb +58 -0
  44. data/lib/story_teller/version.rb +24 -0
  45. data/lib/story_teller_cli.rb +34 -0
  46. metadata +158 -0
@@ -0,0 +1,571 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # The Inform module
5
+ module Inform
6
+ # The English
7
+ module English
8
+ AgainWords = [
9
+ Inform::English::AGAIN1__WD,
10
+ Inform::English::AGAIN2__WD,
11
+ Inform::English::AGAIN3__WD
12
+ ].freeze
13
+
14
+ # Naive opposite mapping of directions
15
+ CompassDirectionNames = Inform::English::Compass.map(&:name).flatten.uniq
16
+ CompassDirectionOpposites = {
17
+ $n_obj => $s_obj,
18
+ $s_obj => $n_obj,
19
+ $e_obj => $w_obj,
20
+ $w_obj => $e_obj,
21
+ $ne_obj => $sw_obj,
22
+ $sw_obj => $ne_obj,
23
+ $nw_obj => $se_obj,
24
+ $se_obj => $nw_obj,
25
+ $u_obj => $d_obj,
26
+ $d_obj => $u_obj,
27
+ $in_obj => $out_obj,
28
+ $out_obj => $in_obj
29
+ }.freeze
30
+
31
+ if defined?(TalkingVerbs)
32
+ TalkingVerbs.push(%i[AnswerInanimate AskFor AskIf AskQuestion Inform])
33
+ end
34
+
35
+ def available_exits(ceiling = visibility_ceiling)
36
+ return [] if ceiling.nil?
37
+ Inform::English::Compass.select { |d| ceiling.linked?(d.door_dir) }
38
+ end
39
+
40
+ def openorclosed(door)
41
+ (door.has?(:open) ? "open" : "closed") + " " + door
42
+ end
43
+
44
+ def obvious_exits(exits, ceiling = visibility_ceiling)
45
+ obvious_exits = []
46
+ for exit_obj in exits
47
+ dir = exit_obj.door_dir
48
+ destination = ceiling.linkto(dir)
49
+ if parent(destination).nil?
50
+ obvious_exits << exit_obj
51
+ next
52
+ end
53
+ door = destination
54
+ next if door.hasany?(:concealed, :secret, :scenery)
55
+ if door.has?(:openable)
56
+ if door.has?(:pluralname)
57
+ print "Some " + openorclosed(door) + " lead "
58
+ else
59
+ print A(openorclosed(door)) + " leads "
60
+ end
61
+ elsif door.has?(:pluralname)
62
+ print "Some " + door + " lead "
63
+ else
64
+ print A(door) + " leads "
65
+ end
66
+ LanguageDirection(dir)
67
+ println "."
68
+ obvious_exits << exit_obj
69
+ end
70
+ obvious_exits
71
+ end
72
+
73
+ def entersorarrives(o)
74
+ x = parent(o)
75
+ if !x.nil? && x.has?(:room)
76
+ "enters"
77
+ else
78
+ "arrives"
79
+ end
80
+ end
81
+
82
+ def exitsorleaves(o)
83
+ if parent(o).has?(:room)
84
+ "exits"
85
+ else
86
+ "leaves"
87
+ end
88
+ end
89
+
90
+ def upordown(preposition, obj)
91
+ unless Inform::English::Compass.include?(obj)
92
+ return case obj.&(:door_dir)
93
+ when :d_to then "down"
94
+ when :u_to then "up"
95
+ when NilClass then ""
96
+ else preposition
97
+ end + " " + the(obj)
98
+ end
99
+ case obj
100
+ when $d_obj then "down below"
101
+ when $u_obj then "up above"
102
+ else
103
+ if preposition.nil?
104
+ the(obj)
105
+ else
106
+ preposition + " " + the(obj)
107
+ end
108
+ end
109
+ end
110
+
111
+ def sitorstand(obj)
112
+ if obj.has?(:prone)
113
+ "lay"
114
+ elsif obj.has?(:kneeling)
115
+ "kneel"
116
+ elsif obj.has?(:sitting)
117
+ "sit"
118
+ else
119
+ "stand"
120
+ end
121
+ end
122
+ def sitsorstands(obj); sitorstand(obj) + 's'; end
123
+
124
+ def sayexclaimorask(s)
125
+ case s
126
+ when /.*\!+$/ then "exclaim$"
127
+ when /.*\?+$/ then "ask$"
128
+ else "say$"
129
+ end
130
+ end
131
+
132
+ LanguageCounters = {
133
+ 'first' => 1, 'second' => 2, 'third' => 3, 'fourth' => 4, 'fifth' => 5,
134
+ 'sixth' => 6, 'seventh' => 7, 'eighth' => 8, 'ninth' => 9, 'tenth' => 10,
135
+ 'eleventh' => 11, 'twelvth' => 12, 'thirteenth' => 13, 'fourteenth' => 14,
136
+ 'fifteenth' => 15, 'sixteenth' => 16, 'seventeenth' => 17, 'eighteenth' => 18,
137
+ 'nineteenth' => 19, 'twentieth' => 20
138
+ }.freeze
139
+ CountersLanguage = LanguageCounters.invert.freeze
140
+
141
+ LanguageDirections = {
142
+ n_to: "north",
143
+ s_to: "south",
144
+ e_to: "east",
145
+ w_to: "west",
146
+ ne_to: "northeast",
147
+ nw_to: "northwest",
148
+ se_to: "southeast",
149
+ sw_to: "southwest",
150
+ u_to: "up",
151
+ d_to: "down",
152
+ in_to: "in",
153
+ out_to: "out"
154
+ }.freeze
155
+
156
+ Articles = Inform::English::LanguageArticles.flatten.uniq.freeze
157
+
158
+ def LanguageVerb(i)
159
+ case i
160
+ when 'i', 'inv', 'inventory'
161
+ print "take inventory"
162
+ when 'l' then print "look"
163
+ when 'x' then print "examine"
164
+ when 'z' then print "wait"
165
+ when 'at', 'att', 'atta', 'attac' then print "attack"
166
+ else false
167
+ end
168
+ true
169
+ end
170
+
171
+ def capitalize_sentences(s)
172
+ s.gsub(/\w.*?[.!?](?:\s+|\z)/, &:sentence_case)
173
+ end
174
+
175
+ def ensure_punctuation(a, punctuation = '.')
176
+ a = NOTHING__TX if a.nil?
177
+ punctuate(a.to_s, punctuation)
178
+ end
179
+
180
+ PunctuationPattern = %r{[.!?]['"]?$}
181
+
182
+ def punctuate(a, punctuation)
183
+ return punctuation if a.nil?
184
+ a = a.split if a.respond_to?(:split)
185
+ unless a.empty?
186
+ last_word = a.pop if a.respond_to?(:pop)
187
+ last_word << punctuation unless PunctuationPattern.match?(last_word)
188
+ a.push last_word if a.respond_to?(:push)
189
+ end
190
+ return a unless a.respond_to?(:join)
191
+ a.join(' ')
192
+ end
193
+
194
+ def th(n)
195
+ CountersLanguage[n]
196
+ end
197
+
198
+ MyWordConjugationIndicatorPattern = %r{ my (\w+)}
199
+ WordConjugationIndicatorPattern = %r{(\w+)\$}
200
+
201
+ def inflect(word, person = :third_person, obj = player)
202
+ word = conjugate(word, person)
203
+ case person
204
+ when :third_person, :third_person_singular
205
+ word.gsub(MyWordConjugationIndicatorPattern) { " " + intensify(obj) + " " + Regexp.last_match(1) }
206
+ when :first_person, :first_person_singular
207
+ word.dup.gsub(WordConjugationIndicatorPattern) { reflexify(Regexp.last_match(1)) }
208
+ else
209
+ word.dup.gsub(WordConjugationIndicatorPattern) { reflexify(Regexp.last_match(1)) }
210
+ end
211
+ end
212
+
213
+ AreConjugationIndicatorPattern = %r{are\$}
214
+ AnyConjugationIndicatorPattern = %r{\$}
215
+
216
+ def conjugate(word, person = :third_person_singular)
217
+ word = word.to_s
218
+ case person
219
+ when :third_person, :third_person_singular
220
+ word.dup.gsub(WordConjugationIndicatorPattern) { conjugate_present(Regexp.last_match(1)) }
221
+ when :first_person, :first_person_singular
222
+ word.gsub(AreConjugationIndicatorPattern, 'is').gsub(AnyConjugationIndicatorPattern, '')
223
+ else
224
+ word.gsub(AreConjugationIndicatorPattern, 'is').gsub(AnyConjugationIndicatorPattern, '')
225
+ end
226
+ end
227
+
228
+ SibilantPhoneticPattern = %r{.*(ch|s|sh|x|z)$}i
229
+ LongVowelPhoneticPattern = %r{.*(ay|ey|oy|uy)$}i
230
+ ConsonantYPhoneticPattern = %r{.*[^aeiou]y}i
231
+
232
+ def conjugate_present(verb)
233
+ case verb
234
+ when SibilantPhoneticPattern
235
+ verb << "es"
236
+ when LongVowelPhoneticPattern
237
+ verb << "s"
238
+ when ConsonantYPhoneticPattern
239
+ verb << verb << "ies"
240
+ else
241
+ verb << "s"
242
+ end
243
+ end
244
+
245
+ def intensify(obj)
246
+ return "its" if obj.nil?
247
+ return "their" if obj.has?(:pluralname)
248
+ if obj.has?(:animate)
249
+ return "her" if obj.has?(:female)
250
+ return "his" if obj.hasnt?(:neuter)
251
+ end
252
+ return "its"
253
+ end
254
+
255
+ def reflexify(_obj)
256
+ return "my"
257
+ end
258
+
259
+ # Returns the plural form of the word in the string.
260
+ def pluralize(word, n = nil)
261
+ return word if n == 1
262
+ apply_inflections(word, StoryTeller::Inflector.inflections.plurals)
263
+ end
264
+
265
+ # The reverse of +pluralize+, returns the singular form of a word in a
266
+ # string.
267
+ def singularize(word)
268
+ apply_inflections(word, StoryTeller::Inflector.inflections.singulars)
269
+ end
270
+
271
+ def singular?(s)
272
+ singularize(s) == s
273
+ end
274
+
275
+ def plural?(s)
276
+ pluralize(s) == s
277
+ end
278
+
279
+ # Applies inflection rules for +singularize+ and +pluralize+.
280
+ def apply_inflections(word, rules)
281
+ return "" if word.empty?
282
+ result = word.to_s.dup
283
+ candidate = result.downcase[/\b\w+\Z/]
284
+ return result if StoryTeller::Inflector.inflections.uncountables.include?(candidate)
285
+ for (rule, replacement) in rules
286
+ break if result.sub!(rule, replacement)
287
+ end
288
+ result
289
+ end
290
+
291
+ def theirself(obj)
292
+ return "themselves" if obj.has?(:pluralname)
293
+ if obj.has?(:animate)
294
+ return "herself" if obj.has?(:female)
295
+ return "himself" if obj.hasnt?(:neuter)
296
+ end
297
+ return "itself"
298
+ end
299
+
300
+ def oneself(obj)
301
+ return "yourself" if obj == player
302
+ return "themselves" if obj.has?(:pluralname)
303
+ if obj.has?(:animate)
304
+ return "herself" if obj.has?(:female)
305
+ return "himself" if obj.hasnt?(:neuter)
306
+ end
307
+ return "itself"
308
+ end
309
+
310
+ def hasorhave(obj)
311
+ return (obj.to_i == 1 ? "has " : "have ") + obj if obj.number?
312
+ if obj.has?(:pluralname) || obj == player then "have" else "has" end
313
+ end
314
+
315
+ def ItorThem(obj, first_person = true)
316
+ return "yourself" if first_person && obj == player
317
+ super(obj)
318
+ end
319
+
320
+ # Using reference: https://nonbinary.wiki/wiki/English_neutral_pronouns
321
+ MOST_COMMON_NEUTRAL_PRONOUNS = 'They/Them/Their'.freeze
322
+
323
+ def CThatorThose(obj) # Used in the nominative
324
+ if obj == player; return "You"; end
325
+ if obj.has?(:pluralname); return "Those"; end
326
+ if obj.has?(:animate)
327
+ if obj.has?(:female); return "She"
328
+ elsif obj.has?(:nonbinary)
329
+ return obj.&(:pronouns, MOST_COMMON_NEUTRAL_PRONOUNS).split('/').first
330
+ elsif obj.hasnt?(:neuter); return "He"; end
331
+ end
332
+ return "That"
333
+ end
334
+
335
+ IT_OR_ENDS_WITH_E_PATTERN = %r{(it|e)$}
336
+
337
+ def CTheyreorThats(obj)
338
+ if obj == player; return "You're"; end
339
+ if obj.has?(:pluralname); return "They're"; end
340
+ if obj.has?(:animate)
341
+ if obj.has?(:female); return "She's"
342
+ elsif obj.has?(:nonbinary)
343
+ pronoun = obj.&(:pronouns, MOST_COMMON_NEUTRAL_PRONOUNS).split('/').first
344
+ return pronoun + (IT_OR_ENDS_WITH_E_PATTERN.match?(pronoun) ? "'s" : "'re")
345
+ elsif obj.hasnt?(:neuter); return "He's"; end
346
+ end
347
+ return "That's"
348
+ end
349
+
350
+ def CItorThose(obj) # Used in the nominative
351
+ if obj == player; return "You"; end
352
+ if obj.has?(:pluralname); return "Those"; end
353
+ if obj.has?(:animate)
354
+ if obj.has?(:female); return "She"
355
+ elsif obj.has?(:nonbinary)
356
+ return obj.&(:pronouns, MOST_COMMON_NEUTRAL_PRONOUNS).split('/').first
357
+ elsif obj.hasnt?(:neuter); return "He"; end
358
+ end
359
+ return "It"
360
+ end
361
+
362
+ def himorher(obj, _first_person = true)
363
+ return "them" if obj.has?(:pluralname)
364
+ if obj.has?(:animate)
365
+ return "her" if obj.has?(:female)
366
+ return "him" if obj.hasnt?(:neuter)
367
+ end
368
+ return "it"
369
+ end
370
+
371
+ def ThatorThose(obj, first_person = true) # Used in the accusative
372
+ return "you" if first_person && obj == player
373
+ CThatorThose(obj).downcase
374
+ end
375
+
376
+ def hisorher(obj, x1); HisorHer(obj, x1); end
377
+ def HisorHer(obj, x1, first_person = false)
378
+ return your(x1) if first_person && obj == player
379
+ return ItsorTheir(obj, x1, first_person)
380
+ end
381
+ def Citsortheir(obj, x1, first_person = true)
382
+ return Your(x1) if first_person && obj == player
383
+ return "Their " + x1 if obj.has?(:pluralname)
384
+ if obj.has?(:animate)
385
+ return "Her " + x1 if obj.has?(:female)
386
+ return "His " + x1 if obj.hasnt?(:neuter)
387
+ end
388
+ return "Its " + x1
389
+ end
390
+ def ItsorTheir(obj, x1, first_person = true)
391
+ Citsortheir(obj, x1, first_person).downcase
392
+ end
393
+ def Itsortheir(obj, x1, first_person = true)
394
+ Citsortheir(obj, x1, first_person).downcase
395
+ end
396
+ def itsortheir(obj, x1, first_person = true)
397
+ Citsortheir(obj, x1, first_person).downcase
398
+ end
399
+
400
+ def TheyreorThats(obj)
401
+ CTheyreorThats(obj).downcase
402
+ end
403
+ def Theyreorthats(obj); CTheyreorThats(obj); end
404
+ def Thatorthose(obj, x1); CThatorThose(obj, x1); end
405
+ def thatorthose(obj) # Used in the nominative
406
+ CThatorThose(obj).downcase
407
+ end
408
+ alias theyreorthats TheyreorThats
409
+
410
+ def your(x1); return YOUR2__TX + " " + x1; end
411
+ def Your(x1); return YOUR__TX + " " + x1; end
412
+
413
+ def CloseMessages(n, x1)
414
+ case n
415
+ when 4 then A(player) + " closes " + indefart(x1) + "."
416
+ end
417
+ end
418
+
419
+ def OpenMessages(n, x1)
420
+ case n
421
+ when 6 then A(player) + " opens " + indefart(x1) + "."
422
+ end
423
+ end
424
+
425
+ def LockMessages(n, x1)
426
+ case n
427
+ when 7 then A(player) + " locks " + the(x1) + "."
428
+ when 8 then "The lock demands its key."
429
+ end
430
+ end
431
+
432
+ def UnlockMessages(n, x1 = nil)
433
+ case n
434
+ when 6 then A(player) + " unlocks " + the(x1) + "."
435
+ end
436
+ end
437
+
438
+ def LookMessages(n, x1)
439
+ case n
440
+ when 5,6
441
+ if x1 != visibility_ceiling
442
+ if x1.has?(:supporter)
443
+ print "On "
444
+ else
445
+ print "In "
446
+ end
447
+ print the(x1) + " you"
448
+ else print "You"
449
+ end
450
+ print " can "
451
+ print "also " if n == 5
452
+ print "see "
453
+ WriteListFrom(child(x1), Inform::ENGLISH_BIT + Inform::PARTINV_BIT +
454
+ Inform::TERSE_BIT + Inform::CONCEAL_BIT +
455
+ Inform::WORKFLAG_BIT + Inform::NONANIMATE_BIT)
456
+ if x1 != visibility_ceiling
457
+ "."
458
+ else
459
+ " here."
460
+ end
461
+ when 9 then print " "
462
+ if x1 == @player
463
+ x1.ancestors.reverse.each do |o|
464
+ print "["
465
+ color :cyan
466
+ print string(o.id)
467
+ uncolor :cyan
468
+ print "]"
469
+ end
470
+ else
471
+ print "["
472
+ color :cyan
473
+ print string(x1.id)
474
+ uncolor :cyan
475
+ print "]"
476
+ end
477
+ when 10
478
+ if x1 != visibility_ceiling
479
+ if x1.has?(:supporter)
480
+ print "On "
481
+ else
482
+ print "In "
483
+ end
484
+ print the(x1) + " you notice "
485
+ else print "You notice that "
486
+ end
487
+ WriteListFrom(child(x1),
488
+ Inform::ENGLISH_BIT + Inform::RECURSE_BIT + Inform::PARTINV_BIT +
489
+ Inform::TERSE_BIT + Inform::CONCEAL_BIT) do |o|
490
+ o.has?(:animate) && o.hasnt?(:concealed, :scenery)
491
+ end
492
+ if x1 != visibility_ceiling
493
+ "."
494
+ else
495
+ n = visibility_ceiling.count do |o|
496
+ o != player && o.has?(:animate) && o.hasnt?(:concealed, :scenery)
497
+ end
498
+ if n > 1 then print " are" else print " is" end
499
+ " nearby."
500
+ end
501
+ end
502
+ end
503
+
504
+ def MountMessages(n, x1)
505
+ case n
506
+ when 1 then "But you're already on " + the(x1) + "."
507
+ when 2 then "You'll have trouble getting on " + thatorthose(x1) + "."
508
+ when 4 then "You can only mount something free-standing."
509
+ when 5 then "You mount " + the(x1) + "."
510
+ when 6 then print "(getting "
511
+ if x1.has?(:supporter)
512
+ print "off "
513
+ else
514
+ print "out of "
515
+ end
516
+ print the(x1); print ")\n"; @say__p = 0; return
517
+ when 7 then @say__p = 0
518
+ print "(getting onto " + the(x1) + ")\n" if x1.has?(:supporter)
519
+ "(entering " + the(x1) + ")\n"
520
+ when 8 then The(player) + " gets onto " + the(x1) + "."
521
+ end
522
+ end
523
+
524
+ def RubMessages(n, x1)
525
+ case n
526
+ when 2 then if player.descendants.include?(x1)
527
+ A(player) + " rubs " + hisorher(player, x1) + "."
528
+ else
529
+ A(player) + " rubs " + a(x1) + "."
530
+ end
531
+ end
532
+ end
533
+
534
+ def DismountMessages(n, x1)
535
+ case n
536
+ when 1 then "But you have no mount."
537
+ when 3 then println "You dismount from " + the(x1) + "."
538
+ when 5 then The(player) + " dismounts from " + the(x1) + "."
539
+ end
540
+ end
541
+
542
+ def MiscellanyMessages(n, _x1)
543
+ case n
544
+ when 17 then nil
545
+ end
546
+ end
547
+ end
548
+ # English module
549
+ end
550
+ # Inform module
551
+
552
+ # The Inform module
553
+ module Inform
554
+ # The Inform::Object class
555
+ class Object
556
+ def plural(*args)
557
+ unless args.empty?
558
+ self.properties[:plural] = args.first
559
+ self.save
560
+ end
561
+ return self.properties[:plural] if self.properties.include?(:plural)
562
+ pluralize self.to_s
563
+ end
564
+ end
565
+ end
566
+
567
+ # The Inform module
568
+ module Inform
569
+ alias emotion topic
570
+ alias time topic
571
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # The Huggable module
5
+ module Huggable
6
+ def life_kiss
7
+ ":-)"
8
+ end
9
+ end