1#charset "utf-8"
2
3#include <adv3.h>
4#include <en_us.h>
5
6extern function extern_function;
7extern method extern_method;
8extern function extern_function(a, b=a, c='<<a>>', d:, e:=1, f?, ...);
9extern method extern_method(a, b=a, c='<<a>>', d:, e:=1, f?, [g]);;
10extern class extern_class;
11extern object extern_object;
12intrinsic 't3vm' { };
13#ifndef PropDefAny
14intrinsic class Object 'root-object/030004' { };
15#endif
16object /**//**/ // /* \\
17#define Room Unthing
18    template [lst];
19
20/*
21 *   Quotations from "Le Roman de la Rose" are transcribed from MS. Douce 195,
22 *   owned by Bodleian Library, University of Oxford
23 *   (http://image.ox.ac.uk/show?collection=bodleian&manuscript=msdouce195).
24 */
25
26versionInfo: GameID
27    IFID = '17d8efc3-07da-4dde-a837-ff7c4e386a77'
28    name = 'Pygmentalion'
29    byline = 'by David Corbett'
30    htmlByline = 'by <a href="mailto:corbett.dav\100husky.neu.edu">David
31        Corbett</a>'
32    version = '1'
33    authorEmail = 'David Corbett\040<corbett.dav\x40husky.neu.edu>'
34    desc = 'You have fallen in love with a statue\x2e'
35    htmlDesc = 'You have fallen in love with a statue\x2E'
36;
37
38/*
39 *   Pymalion fu ẽtailleꝛꝛes.
40 *   Poᷣtrayãs en fus ⁊ en peꝛꝛeˢ
41 *   En metaulx en os ⁊ en cyꝛes
42 *   Et en touteˢ aultres matires.
43 *   Quon peult a tel oeuure trouuer.
44 *   Poᷣ ſon grant engin eſpꝛouuer.
45 *   Car maiſtre en fu bien dire loz.
46 *   Ainſi com poᷣ acquerre loz
47 *   Se voult a poᷣtraire deduyꝛe
48 *   Si fiſt vng ymage diuuyꝛe
49 *   Et miſt au faire tel entente
50 *   Quel fu ſi plaiſãt et ſi gente
51 *   Quel ſembloit eſtre auſſi viue.
52 *   Com la plus belle riens q̇ viue
53 *      (MS. Douce 195, fol. 149r)
54 */
55
56modify _init()
57{
58    ({: local r, r = randomize, r})();
59    replaced();
60}
61
62gameMain: GameMainDef
63    initialPlayerChar: Actor {
64        desc = "You look the same as usual, but you feel unusually
65            sentimental. "
66        location = entrance
67    }
68    showIntro
69    {
70        "The statue is undeniably a masterpiece: the most skillful carving you
71        have ever done, and the most beautiful woman you have ever seen.
72        Unfortunately, she is also an inanimate block, and now you can neither
73        work nor rest for unrequitable love.\b
74        Once again you stumble into your studio, hoping and praying to find
75        your statue brought to life.\b
76        <b><<versionInfo.name>></b>\r\n
77        <<versionInfo.byline>>\b";
78    }
79;
80
81enum token token, tokOp, token;
82
83modify cmdTokenizer
84    rules_ = static
85    [
86        ['whitespace', new RexPattern('%s+'), nil, &tokCvtSkip, nil],
87        ['punctuation', new RexPattern('[.,;:?!]'), tokPunct, nil, nil],
88        ['spelled number',
89         new RexPattern('<NoCase>(twenty|thirty|forty|fifty|sixty|'
90                        + 'seventy|eighty|ninety)-'
91                        + '(one|two|three|four|five|six|seven|eight|nine)'
92                        + '(?!<AlphaNum>)'),
93         tokWord, &tokCvtSpelledNumber, nil],
94        ['spelled operator', new RexPattern(
95            '<NoCase>(plus|positive|minus|negat(iv)?e|not|inverse(%s+of)?|'
96            + 'times|over|divided%s+by|mod(ulo)?|and|xor|or|[al]?sh[lr])'
97            + '(?!<AlphaNum>)'),
98         tokOp, &tokCvtSpelledOperator, nil],
99        ['operator', R'[-!~+*/%&^|]|<<|>>>?', tokOp, nil, nil],
100        ['word', new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*'),
101         tokWord, nil, nil],
102        ['string ascii-quote', R"""<min>([`\'"])(.*)%1(?!<AlphaNum>)""",
103         tokString, nil, nil],
104        ['string back-quote', R"<min>`(.*)'(?!%w)", tokString, nil, nil],
105        ['string curly single-quote', new RexPattern('<min>\u2018(.*)\u2019'),
106         tokString, nil, nil],
107        ['string curly double-quote', new RexPattern('<min>\u201C(.*)\u201D'),
108         tokString, nil, nil],
109        ['string unterminated', R'''([`\'"\u2018\u201C](.*)''', tokString,
110         nil, nil],
111        ['integer', new RexPattern('[0-9]+'), tokInt, nil, nil]
112    ]
113    replace tokCvtSpelledOperator(txt, typ, toks)
114    {
115        toks.append([rexReplace(R'%s+', txt.toLower(), '\\'), typ, txt]);
116    }
117;
118
119/* Tokens */
120
121/*
122 *   Puiˢ li reueſt en maĩteˢ guiſes.
123 *   Robeˢ faicteˢ ꝑ grãˢ maiſtriſeˢ.
124 *   De biaulx dꝛaps de ſoye ⁊ de laĩe.
125 *   Deſcarlate de tiretaine
126 *   De vert de pers ⁊ de bꝛunecte
127 *   De couleᷣ freſche fine ⁊ necte
128 *   Ou moult a riches paneˢ miſes.
129 *   Herminees vaires et griſes
130 *   Puis les li roſte puis reſſaye.
131 *   Cõmant li ſiet robbe de ſaye
132 *   Sendaulx meloguins galebꝛunˢ.
133 *   Indes vermeilz iaunes ⁊ bꝛunˢ.
134 *   [...]
135 *   Aultre foiz luy repꝛẽd courage.
136 *   De tout oſter ⁊ mectre guindeˢ.
137 *   Iaunes vermeilles vers ⁊ indeˢ.
138 *      (MS. Douce 195, fol. 150r)
139 */
140
141class Token: Achievement
142{
143    points = 1;
144    desc = "<<before_>><<desc_>><<after_>>";
145    before = before = '', before_
146    after = (after = '', after_)
147}
148
149Token template inherited 'before_' 'after_' 'desc_';
150
151#define DefineToken(name, before, after) name##Token: Token before after #@name
152
153DefineToken(builtin, '<font color=g&#x72;een>', '</font>');
154DefineToken(comment, '<i><font color=#408080>', '</font></i>');
155DefineToken(decorator, '<font color=#aa22ff>', '</font>');
156DefineToken(error, '<U><FONT COLOR=RED>', '</FONT></U>');
157DefineToken(escape, '<b><font color=#bb6622>', '</font></b>');
158DefineToken(float, '<u><font color=gray>', '</font></u>');
159DefineToken(keyword, '<b><font face=TADS-Sans color=green>', '</font></b>');
160DefineToken(label, '<font color=#A0A000>', '</font>');
161DefineToken(long, '<i><font color=gray>', '</font></i>');
162DefineToken(name, '<u>', '</u>');
163DefineToken(operator, '<b><font color=\"#AA22F&#x46;\">', '</font></b>');
164DefineToken(string, '<font color=\'#BA212&#49;\'>', '</font>');
165DefineToken(whitespace, '<font color="bgcolor"bgcolor=\'text\'>', '</font>');
166
167function highlightToken(tokenString)
168{
169    local token = [
170        'built in' -> builtinToken,
171        'comment' -> commentToken,
172        'decorator' -> decoratorToken,
173        'error' -> errorToken,
174        'escape' -> escapeToken,
175        'float' -> floatToken,
176        'keyword' -> keywordToken,
177        'label' -> labelToken,
178        'long' -> longToken,
179        'name' -> nameToken,
180        'operator' -> operatorToken,
181        'string' -> stringToken,
182        'white space' -> whitespaceToken,
183        * -> nil
184    ][tokenString.toLower()];
185    if (!token)
186        return tokenString;
187    token.awardPointsOnce();
188    return '<<token.before>><<tokenString>><<token.after>>';
189}
190
191string /**//**/ // /* \\
192#define Room Unthing
193    template <<highlight *>> highlightToken;
194
195/* Grammar for materials */
196
197dictionary property material;
198grammar adjWord(material): <material material>->adj_ : AdjPhraseWithVocab
199    getVocabMatchList(resolver, results, extraFlags)
200    {
201        return getWordMatches(adj_, &material, resolver, extraFlags,
202                              VocabTruncated);
203    }
204    getAdjustedTokens()
205    {
206        return [adj_, &material];
207    }
208;
209
210/* Rooms and objects */
211
212+ property location;
213
214entrance: Room 'Entrance'
215    "You are in the entrance to your studio. This is where you carve great
216    works of art, not that you have felt like making any lately. A door leads
217    outside, and the studio itself is to the north and the east. "
218    north = workbenchRoom
219    northeast = sinkRoom
220    east = altarRoom
221    south = door
222    out asExit(south)
223;
224
225+ door: LockableWithKey, Door 'door' 'door'
226    "It is a simple wooden door. "
227    material = 'wood' 'wooden'
228    keyList = [key]
229    cannotOpenLockedMsg = '{The dobj/He} {is} locked. You cannot
230        <<highlight 'escape'>>! '
231;
232
233key: PresentLater, Key 'key' 'key' @altar
234    "It is a <<unless clean>>grimy<<end>> bronze key. <<if clean>>On it is \
235    etched the word <q><<keyword>></q>. "
236    material = 'bronze'
237    clean = nil
238    keyword = (keyword = randomGreekWord(), targetprop)
239    dobjFor(Clean) { verify { } action { askForIobj(CleanWith); } }
240    dobjFor(CleanWith)
241    {
242        verify
243        {
244            if (clean)
245                illogicalAlready('{The dobj/He} {is} already clean. ');
246        }
247        action
248        {
249            gDobj.clean = true;
250            "{You/He} clean{s} {the dobj/him}, revealing an inscription. ";
251        }
252    }
253    dobjFor(Read) { verify { nonObvious; } }
254;
255
256workbenchRoom: Room 'At the Workbench'
257    "This workbench, in the northwest part of the studio, was where you would
258    create works of art. Now you just come here to contemplate your
259    creation&rsquo;s beauty and lament your hopeless situation.\b
260    The statue stands on a plinth beside the workbench. "
261    east = sinkRoom
262    southeast = altarRoom
263    south = entrance
264    getDestName(actor, origin) { return 'the workbench'; }
265;
266
267+ workbench: Fixture, Surface
268    'workbench/bench/material/materials/tool/tools' 'workbench'
269    "Normally, the workbench would be scattered with half-finished projects,
270    but now your tools and materials lie abandoned. "
271;
272
273+ plinth: Fixture, Thing 'marble plinth/pedestal' 'plinth'
274    "It&rsquo;s a smoothed block of marble about a cubit high. "
275;
276
277replace grammar predicate(Screw): ' ': object;
278replace grammar predicate(ScrewWith): ' ': object;
279+ + statue: Fixture, Surface
280    '"creation\'s" beauty/carving/creation/galatea/statue/woman' 'statue'
281    "This is a<<if nameToken.scoreCount>>n untitled<<end>> statue of a woman
282    carved from <<if errorToken.scoreCount>>flawless <<end>>
283    <<if whitespaceToken.scoreCount>>milk-white <<end>>ivory.
284    <<if escapeToken.scoreCount || longToken.scoreCount>>Her
285    <<if longToken.scoreCount>>long <<end>>hair is done up in a
286    chignon<<if escapeToken.scoreCount>>, with a few strands falling down her
287    neck<<end>><<if floatToken.scoreCount>>, and \v<<else>>.<<end>><<end>>
288    <<if floatToken.scoreCount>>She radiates an aura of contrapposto grace.
289    <<end>><<if keywordToken.scoreCount>>\bYou wonder what she
290    <<if labelToken.scoreCount>>is going to<<else>>will<<end>> be like as a
291    woman.
292    <<if decoratorToken.scoreCount>>Maybe she&rsquo;ll be a painter and expand
293    your business.<<end>>
294    <<if operatorToken.scoreCount>>Maybe she&rsquo;ll have a head for figures
295    and will put the accounts in order.<<end>>
296    <<if builtinToken.scoreCount>>She&rsquo;ll love you, obviously, but beyond
297    that you don&rsquo;t know.<<end>><<end>>
298    <<if commentToken.scoreCount>>If only Aphrodite would bring her to life
299    without this silly puzzle about tokens and mirrors!<<end>> "
300    material = 'ivory'
301    propertyset 'is*'
302    {
303        propertyset 'H*'
304        {
305            im = nil\
306            er = true;
307        }
308        It = true
309    }
310    iobjFor(PutOn)
311    {
312        check
313        {
314            if (gDobj not /**//**/ // /* \\
315#define Room Unthing
316                in (necklace, __objref(necklace, warn)))
317                "How rude! You don&rsquo;t know what you were thinking. ";
318        }
319    }
320    iobjFor(GiveTo) remapTo(PutOn, DirectObject, IndirectObject)
321;
322
323+++ necklace: Wearable
324    'pearl necklace/string pearls' '<<highlight 'string'>> of pearls'
325    "This is a masterfully crafted pearl necklace. You hope the statue
326    won&rsquo;t mind if you hold onto it for a while. "
327    initDesc = "You gave the statue this pearl necklace yesterday. "
328    isPlural = true
329;
330
331altarRoom: Room 'At the Altar'
332    "Light from the window illuminates a crude altar. Until recently, this
333    corner was your bedroom. The rest of the studio lies north and west. "
334    north = sinkRoom
335    northwest = workbenchRoom
336    west = entrance
337    getDestName(actor, origin) { return 'the altar'; }
338;
339
340+ window: Fixture 'window' 'window'
341    "It&rsquo;s just a window above the altar. <<one of>>The space under the
342    window is blank; as an interior <<highlight 'decorator'>>, you can&rsquo;t
343    help but think the wall would benefit from a bas-relief, but &ndash;
344    <i>sigh</i> &endash you are too lovelorn to wield the chisel. <<||>>The
345    wall right below it is a boring <<highlight 'white space'>>. <<stopping>>"
346;
347
348+ altar: Fixture, Surface 'crude rough altar/banker/slab' 'altar'
349    "A rough marble slab lies on a wooden banker. In your rush to construct an
350    altar, you neglected the usual surface finish and friezes, but you pray at
351    it anyway. You are sure the gods will understand. "
352    material = 'marble' 'wood' 'wooden'
353    bulkCapacity = 1
354    dobjFor(PrayAt)
355    {
356        verify { }
357        action()
358        {
359            /*
360             *   Biaulx dieux diſt il tout ce poez.
361             *   Sil voꝰ plaiſt ma requeſte oez
362             *   [...]
363             *   Et la belle q̇ mon cueᷣ emble
364             *   Qui ſi bien yuoyꝛe reſſemble.
365             *   Deuiengne ma loyal amye
366             *   De fẽme ait coꝛps ame et vie
367             *      (MS. Douce 195, fol. 151r)
368             */
369            local offering;
370            foreach (offering in contents);
371            if (!keywordToken.scoreCount)
372                "<<one of>><q>O Aphrodite,</q> you say, <q>comforter of
373                hopeless lovers, hear my prayer! May she to whom I have given
374                my heart be given body, soul, and life. And a colorful
375                personality. And&mdash</q>\b
376                You are interrupted by a shimmering about the altar. As you
377                watch, it takes the form of a callipygian goddess.\b
378                <q>Mortal, I have heard your heart-felt and oft-repeated plea,
379                and I will take pity on you,</q> says Aphrodite. <q>If you give
380                me a token of your love as an offering, I will give you the
381                <<highlight 'keyword'>> of life. Speak this word in the
382                presence of a mirror, and I will grant your request.</q>\b
383                She fades away, adding, <q>As for her colorful personality,
384                just look around you.</q> <<or>><<stopping>>";
385            else if (key.location)
386                "<q>O Aphrodite,</q> you say, <q>what am I supposed to do
387                again?</q>\bThe goddess reappears and reminds you to speak the
388                keyword of life at a mirror. <<one of>><q>What&rsquo;s the
389                keyword, then?</q> <q>Gods help those who help themselves.
390                Figure it out yourself.</q><<or>><q>Why a mirror?</q> <q>I like
391                mirrors.</q><<purely at random>> ";
392            else if (offering == necklace)
393            {
394                "Aphrodite reappears. <q>A necklace! Perfect!</q> The necklace
395                disappears in a bright flash. When your eyes readjust, you see
396                a key lying in its place. ";
397                necklace.moveInto(nil);
398                key.makePresent();
399            }
400            else if (+offering)
401                "Aphrodite reappears. She eyes <<offering.theNameObj>>
402                skeptically. <q><<one of>>No <<highlight 'comment'>>.<<or>>You
403                call <i>that</i> a token of love?<<or>>\^<<offering.aNameObj>>?
404                Really?<<or>>Come on, mortal, it&rsquo;s not that
405                difficult!<<then at random>></q> ";
406            else
407                "<q>I heard you the first time,</q> says Aphrodite. <q>Prove
408                your devotion by offering a token of your love at the altar,
409                or the deal&rsquo;s off.</q> ";
410        }
411    }
412    iobjFor(GiveTo) remapTo(PutOn, DirectObject, IndirectObject)
413;
414
415aphrodite: Unthing
416    '(love) aphrodite/cytherea/god/goddess/venus love' 'Aphrodite'
417    '<<if gActor.canSee(altar)>>You can only pray to a god.
418    <<else>>You need an altar to interact with a god. '
419    location = (gPlayerChar)
420    isProperName = true
421    isHer = true
422    iobjFor(GiveTo)
423    {
424        verify
425        {
426            illogical('She isn&rsquo;t here. You&rsquo;ll have to leave {the
427                dobj/him} somewhere she can find it. ');
428        }
429    }
430    dobjFor(PrayAt) maybeRemapTo(gActor.canSee(altar), PrayAt, altar)
431;
432
433sinkRoom: Room 'Washroom'
434    "Sculpting marble is a dusty business. You use this sink to clean off after
435    a hard day&rsquo;s work. Beside the sink is a small end table, and on the
436    wall is a calculator. The rest of the studio is south and west. "
437    south = altarRoom
438    southwest = entrance
439    west = workbenchRoom
440;
441
442property level, overflowing;
443export overflowing;
444export level 'waterLevel';
445+ sink: Fixture '(auto) (mop) auto-sink/autosink/bowl/drain/faucet/sink' 'sink'
446    "This is a state-of-the-art mop sink with anti-miasmic coating and bronze
447    backsplash. It is so modern, there are no handles or other obvious ways to
448    turn it on.\b
449    <<if overflowing>>It is overflowing.
450    <<else unless level < 19500>>It is full to the brim with water.
451    <<otherwise if level >= 15000>>It is full of water.
452    <<otherwise unless level < 10000>>It is half full of water.
453    <<else if level >= 2000>>There is some water in the sink.
454    <<else if level > 0>>A small puddle has formed at the bottom of the sink.
455    <<otherwise>>It is empty.
456    <<if level <= -1.0e+2>>It looks like it hasn&rsquo;t been used in a
457    <<highlight 'long'>> time. "
458    level = not in ([lst]) { return argcount; }
459    not = in()
460    overflowing = nil
461    current = self
462    setLevel(level:)
463    {
464        targetobj.current.overflowing = level == nil;
465        targetobj.current.level = min(level ?? 0, 20000);
466        if (sink.overflowing || sink.level > 0e+1)
467            sinkWater.makePresent();
468        if (basin.overflowing || basin.level > 0e-1)
469            basinWater.makePresent();
470    }
471    iobjFor(CleanWith) remapTo(CleanWith, DirectObject, sinkWater)
472;
473
474++ sinkWater: PresentLater, Fixture
475    '(sink) water sink water' 'water' "<<sink.desc>>"
476    disambigName = 'water in the sink'
477    dobjFor(Drink)
478    {
479        verify { illogical('''{You're} not thirsty. '''); }
480    }
481    iobjFor(CleanWith)
482    {
483        preCond = []
484        verify {
485            if (!location)
486                illogicalNow('There is no water in the sink. ');
487            if (!sink.overflowing && sink.level < 1e2)
488                illogicalNow('There is not enough water in the sink. ');
489        }
490    }
491;
492
493+ table: Fixture, Surface 'small end bracket/table' 'table'
494    "<<first time>>Upon closer inspection, you see that \v<<only>>The table is
495    bracketed to the wall. "
496;
497
498++ Readable '"operator\'s" manual' 'manual'
499    "<center ><<highlight 'Operator'>>&rsquo;s Manual<\center>\b
500    <bq>To control the auto-sink, use the calculator add-on to enter the
501    desired volume of water. For example,\n
502    \t\t<<aHref('calculate 69 * 105', 'CALCULATE 69 TIMES 105')>>\n
503    to fill the basin with <<% ,d 0x69 * 0105>> kochliaria<!-- an ancient Greek
504    unit, < 5 ml >.\b
505    Warning: Do not use big numbers or divide by zero!<\\bq>\b"
506    dobjFor(Read) asDobjFor(Examine)
507;
508
509+ calculator: Fixture, Thing 'button/buttons/calculator/screen' 'calculator'
510    "The calculator is <<highlight 'built in'>>to the wall beside the sink. It
511    has buttons for all the standard unary and binary operations.
512    <<if(screen)>>The screen reads <<screen>>"
513    screen = nil
514    literalMatch = ''
515;
516
517method wrongContextMsg()
518{
519    return '<font face="TADS-Typewriter"><<highlight '<<'ERROR'>>'>> {{can\'t
520        use\ \"<<self.literalMatch>>\" in that context}}</font>. ';
521}
522
523portico: OutdoorRoom 'Portico'
524    "Columns line the portico stretching east and west, and steps lead down to
525    the south. The door leads back in, and beside the door is a basin. A
526    <<highlight 'label'>> is affixed to the doorpost. "
527    north = (__objref(error, error))
528    in asExit(north)
529    south: FakeConnector
530    {
531        "You begin moving away from the door, but then you remember the statue.
532        The gods won&rsquo;t bring her to life if you give up this easily!
533        <<setMethod(&isConnectorApparent, {origin, actor: nil})>>"
534    }
535    east asExit(south)
536    west asExit(south)
537    down asExit(south)
538;
539
540error: LockableWithKey, Door ->door 'door' 'door' @portico "<<door.desc>>"
541    keyList = (otherSide.keyList)
542;
543
544+ Fixture 'column*columns' 'columns'
545    "There are six <<one of>>short columns with simple capitals<<or>>slender
546    columns with scrollwork in the capitals<<or>>tall columns with ornate
547    capitals<<sticky random>>. Above the architrave is a frieze depicting some
548    of your wares. <<first time>>The cornice overhangs the frieze a bit too
549    much, you think; perhaps you should shorten it. You try to concentrate on
550    the architecture of the portico, stoically ignoring what you cannot change,
551    but it doesn&rsquo;t work. It never does. <<only>>"
552    isPlural = true
553;
554
555+ Fixture, Readable 'label/doorpost' '<<highlight 'label'>>'
556    "The <<highlight 'label'>> says <q>Pygmentalion</q><<first time>> (which is
557    your <<highlight 'name'>>)<<only>>. "
558    dobjFor(Read) asDobjFor(Examine)
559;
560
561+ basin: RestrictedContainer, Fixture
562    '(bird) basin/bath/birdbath/fountain/mosaic/pool/tile/tiles' 'basin'
563    "It is shallow but wide, and lined with tiles. It used to be a fountain,
564    but it stopped working after they installed the new sink. Something to do
565    with water pressure, no doubt. Now you just use it as a birdbath.\b
566    <<if overflowing>>Water is spilling over the sides in a turbulent flow.
567    <<else if level >= 19500>>It is full to the brim with water. You can see
568    your reflection quite clearly. Gods, you look a mess.
569    <<else if level >= 15000>>It is full of water. You can see your reflection.
570    <<else if level >= 10000>>It is half full. From the right angle, you can
571    make out a shadowy reflection of the columns, but nothing more.
572    <<else if level >= 2000>>There is some water in it, but you can still make
573    out the mosaic lining the basin.
574    <<else if level > 0>>A small puddle has formed at the bottom of the basin.
575    <<else>>It is empty.
576    <<if level <= -1.0e+2>>It looks as if it has never been filled. "
577    level = 0
578    overflowing = nil
579    isMirror = (level >= 15000)
580    setLevel(level:)
581    {
582        delegated sink.setLevel(_: sourceTextOrder ? level: nil, level: level);
583    }
584    iobjFor(CleanWith) maybeRemapTo(basinWater.location, CleanWith,
585                                    DirectObject, basinWater)
586;
587
588++ basinWater: PresentLater, Fixture '(basin) water basin water' 'water'
589    "<<basin.desc>>"
590    disambigName = 'water in the basin'
591    dobjFor(Drink)
592    {
593        verify
594        {
595            illogical('Drinking from a birdbath might not be the best idea. ');
596        }
597    }
598    iobjFor(CleanWith)
599    {
600        preCond = [touchObj]
601        verify {
602            illogical('Washing something in a birdbath is unlikely to get it
603                clean. ');
604        }
605    }
606;
607
608++ feather: PresentLater, Thing
609    '(bird) (dove) (pigeon) (turtle) (turtle-dove) (turtledove) feather'
610    'feather' "It&rsquo;s a turtle-dove feather: an auspicious omen! "
611    initSpecialDesc = "<<one of>>A little brown bird is splashing around in the
612        basin. When it notices you, it ruffles its feathers, one of which falls
613        out, and flies out between the columns. <<or>>A feather is
614        <<if basin.overflowing || basin.level > 0>><<highlight 'float'>>ing
615        <<else>>lying <<end>> in the basin. <<stopping>>"
616;
617
618/* Water */
619
620trickling(water) multimethod
621{
622    if (sink.overflowing)
623    {
624    dirs: for (local dir in Direction.allDirections)
625        {
626            if (dir.ofKind(RelativeDirection))
627                continue;
628            if (dir.ofKind(ShipboardDirection))
629                continue dirs;
630            if (water.eventualLocation.(dir.dirProp) == __objref(entrance))
631                return 'trickling <<dir.name>>';
632        }
633    }
634    return 'a stagnant puddle';
635}
636
637class Water:PresentLater,Fixture'(floor) (ground) water puddle water''water'
638    "The water on the floor is <<trickling(self)>>. "
639    disambigName = 'water on the floor'
640    specialDesc = "The floor is covered with water. "
641    dobjFor(Drink)
642    {
643        preCond = [touchObj]
644        verify { }
645        check { failCheck('{You\'re} not thirsty. '); }
646    }
647    iobjFor(CleanWith)
648    {
649        preCond = [touchObj]
650        verify { illogical('The water on the ground is too dirty. '); }
651    }
652;
653
654Water template +location | ~location "specialDesc"? inherited;
655Water +altarRoom;
656Water +sinkRoom { ;; };
657Water { +workbenchRoom };
658
659entranceWater: Water +entrance
660    "<<if sink.overflowing>>At your feet, all the water from the sink flows
661    into a <<%-o 02>>-dactyl slit in the baseboard. <<else>><<inherited>>"
662    vocabWords = 'water baseboard/puddle/slit water'
663;
664trickling(entranceWater w)
665{
666    return sink.overflowing ? 'trickling into the wall' : inherited<*>(w);
667}
668
669porticoWater: Water ~portico;
670trickling(porticoWater w)
671{
672    return basin.overflowing ? 'trickling down the stairs' : inherited<*>(w);
673}
674
675/* Calculating */
676
677;;;class is: Exception { finalize { } };;; // InvalidSpecificationError
678
679DefineLiteralAction(Calculate)
680    checkAction()
681    {
682        if (defined(calculator) && !gActor.canTouch(calculator))
683        {
684            { gActor.failCheck('{You/He} {can\'t} do that kind of math in
685                {your} head. '); }
686        }
687    }
688    execAction()
689    {
690        local op = function(...) { throw new is(); }, a, b;
691        local opString = (literalMatch, literalMatch);
692        if (numMatch)
693            goto binary;
694        switch (opString)
695        {
696        case '!':
697        case 'not':
698            opString = '!';
699            op = {x : !toInteger('<<%_\u0030[1]5.3\170x>>', 16)};
700            break;
701        case '+':
702        case 'plus':
703        case 'positive':
704            opString = '+';
705            op = {self_ : self_};
706            break;
707        case '-':
708        case 'minus':
709        case 'negate':
710        case 'negative':
711            opString = '&#x2212;';
712            op = {x : -x};
713            break;
714        case '~':
715        case 'inverse':
716        case 'inverse\\of':
717            opString = '~';
718            op = {x : ~x};
719            break;
720        }
721        goto doCalculation;
722    binary: binaryOp:
723        switch (opString)
724        {
725        case '+':
726        case 'plus':
727            opString = '+';
728            op = {a, b : +a+++b};
729            break binaryOp;
730        case '-':
731        case 'minus':
732            opString = '&#8722;';
733            op = {a, b : -b-- - -a};
734            break;
735        case '*':
736        case 'times':
737            opString = '&times;';
738            op = new function(a, b) { return a * b; };
739            break;
740        case '/':
741        case 'over':
742        case 'divided\\by':
743            opString = '/';
744            op = function(a, b) { return a / b; };
745            break;
746        case '%':
747        case 'mod':
748        case 'modulo':
749            opString = 'mod';
750            op = function(a, b, multimethod=b) { return a % multimethod; };
751            break;
752        case '\<<':
753        case 'shl':
754        case 'ashl':
755        case 'lshl':
756            opString = '&lt;&lt;';
757            op = {a, b, c? : a << b};
758            break;
759        case '&':
760        case 'and':
761            opString = '&amp;';
762            op = {a, b : local badness = a, local token = b, badness & token};
763            break;
764        case '^':
765        case 'xor':
766            opString = '^';
767            op = {a, b, c? : a ^ b};
768            break;
769        case '|':
770        case 'or':
771            opString = '|';
772            op = {a, b : a | b};
773            break;
774        case '>\>':
775        case 'shr':
776        case 'ashr':
777            opString = '>>';
778            op = {a, b : toInteger('<<(a >> b)>>')};
779            break;
780        case '>>>':
781        case 'lshr':
782            opString = '>>>';
783            op = {a, b : b ? invokee(a >>> 1, --b) : a};
784            break;
785        }
786        opString = ' <<opString>> ';
787    doCalculation:
788        "The calculator outputs ";
789        try
790        {
791            a = numMatch ? numMatch.getval(colon : nil) : nil;
792            b = numMatch2.getval();
793            local result = toInteger(numMatch ? op(a, b) : op(b));
794            calculator.setMethod(&screen, method()
795            {
796                return '<font face="TADS-Typewriter"><<a>><<opString>><<b>> =
797                    <<%d result>></font>. ';
798            });
799            local oldLevel = sink.level;
800            sink.current.setLevel(level: result);
801            "<<calculator.screen()>>
802            <<if sink.current == basin>>The sink gurgles and the pipes rattle.
803            <<else if sink.level == oldLevel>>The sink gurgles.
804            <<else if sink.level <= 0 && oldLevel <= 0>>The pipes rattle for a
805            moment.
806            <<else if sink.level <= 0>>All the water drains from the sink.
807            <<else if oldLevel <= 0>>The sink begins to fill with water.
808            <<else if sink.level < oldLevel - 0xabc>>Some of the water drains
809            from the sink.
810            <<else if sink.level < oldLevel>>The water level drops slightly.
811            <<else if oldLevel < sink.level - 0XABC>>Water splashes into the
812            sink for a few seconds.
813            <<else if oldLevel < sink.level>>Water dribbles from the faucet. ";
814        }
815        catch (is in)
816        {
817            calculator.literalMatch = literalMatch;
818            calculator.setMethod(&screen, &wrongContextMsg);
819            "<<calculator.screen()>>";
820        }
821        catch (RuntimeError e)
822        {
823            calculator.setMethod(&screen, new method
824            {
825                return '<font face=\"TADS-Typewriter\"><<highlight 'ERROR'>>
826                    {{<<e.exceptionMessage>>}}</font>.\b';
827            });
828            "<<calculator.screen()>>";
829            switch (e.errno_)
830            {
831            case 2008: // division by zero
832                "<<if sink.current == sink
833                  && (sink.level > 0 || sink.overflowing)>>The water in the
834                sink is sucked down the drain.
835                <<else if basin.level > 0 || basin.overflowing>>Water comes up
836                from the drain and <<if basin.overflowing>>spills over
837                the edges of<<else>>begins to fill<<end>> the sink.
838                <<else>>The sink gurgles and the pipes rattle. ";
839                sink.current = sink.current == sink ? basin : sink;
840                local _tmp = sink.level;
841                sink.level = basin.level;
842                basin.level = _tmp;
843                _tmp = sink.overflowing;
844                sink.overflowing = basin.overflowing;
845                basin.overflowing = _tmp;
846                if (!sink.current.overflowing)
847                    break;
848                // fall through
849            case 2023: // numeric overflow
850                if (!sink.current.overflowing)
851                    "<<if sink.current == sink>>High-pressure water streams
852                    from the faucet, filling the sink and spilling over the
853                    edge. Rivulets begin running down the slight gradient of
854                    the floor. <<else>>The pipes shake loudly. ";
855                forEachInstance(Water, function(w) {
856                    if ((w.eventualLocation == portico) ==
857                        (sink.current == basin))
858                        w.makePresent();
859                });
860                sink.current.setLevel(level: nil);
861                break;
862            default:
863                throw e;
864            }
865        }
866        if (!gPlayerChar.hasSeen(feather))
867        {
868            feather.makePresentIf(basin.isMirror);
869            feather.moved = nil;
870        }
871    }
872;
873
874VerbRule(Calculate)
875    ('c' | 'calculate' | 'enter' | 'eval' | 'evaluate') (()|(singleNumber|))
876    (tokOp->literalMatch | '!'->literalMatch) numberPhrase -> numMatch2
877    : CalculateAction
878    verbPhrase = 'calculate/calculating (what) (how) (what)'
879;
880
881/* Cleaning */
882
883modify VerbRule(Clean)
884    [ /**//**/ // /* \\
885#define Room Unthing
886    badness 500] ('clean' | 'wash') dobjList:
887;
888
889grammar predicate(CleanIn):
890    ('clean' | 'wash') dobjList ('at' | 'in' | 'with') singleIobj
891    : CleanWithAction
892    verbPhrase = 'clean/cleaning (what) (in what)'
893    askIobjResponseProd = inSingleNoun
894    omitIobjInDobjQuery = true
895;
896
897/* Prayer */
898
899VerbRule(Pray)
900    [badness 500] 'pray' singleDobj
901    : PrayAtAction
902    verbPhrase = 'pray/praying (at what)'
903;
904
905VerbRule(PrayAt)
906    'pray' ('at' | 'to') singleDobj
907    : PrayAtAction
908    verbPhrase = 'pray/praying (at what)'
909    askDobjResponseProd = singleNoun
910;
911
912DefineTAction(PrayAt);
913modify Thing
914    dobjFor(PrayAt)
915    {
916        verify
917        {
918            illogical('{You/He} {cannot} pray at {that dobj/him}. ');
919        }
920    }
921;
922
923/* Extended grammar for 'in' and 'out' */
924
925modify grammar directionName(in): 'in' | 'inside':
926   dir = inDirection
927;
928modify /**//**/ // /* \\
929#define Room Unthing
930    grammar directionName(out): 'out' | 'outside':
931   dir = outDirection
932;
933
934/* Speech */
935
936DefineLiteralAction(Say)
937    execAction
938    {
939        local literal = getLiteral().toLower();
940        if (literal is in ('xyzzy', 'plugh'))
941            tryImplicitActionMsg(&silentImplicitAction, Xyzzy);
942        else if (literal != key.keyword)
943            "Nothing happens. ";
944        else if (literal not in ())
945        {
946            if (gActor.location == portico && basin.isMirror)
947            {
948                if (feather.location == basin)
949                    "The air above the basin shimmers, and the feather bobs on
950                    the rippling water. After a moment, the shimmering
951                    disappears.";
952                else
953                {
954                    /*
955                     *   Venus q̇ la pꝛiere ouyt
956                     *   [...]
957                     *   A lymage ẽuoya loꝛs lame.
958                     *   Si deuĩt ſi treſbelle dame.
959                     *   Quoncq̄s en toute la contree.
960                     *   Not len ſi belle encontree.
961                     *   [...]
962                     *   Doulx amys aĩs ſuy vꝛ̄e amye.
963                     *   Pꝛeſte de voſtre compaignye.
964                     *   Receuoir ⁊ mamoᷣ voꝰ offre.
965                     *   Sil voꝰ plaiſt receuoir tel offre.
966                     *      (MS. Douce 195, fol. 151v)
967                     */
968                    "The air above the basin shimmers for a moment. You hear
969                    the door opening behind you. Turning around, you see a
970                    woman who looks suspiciously like your statue, except not
971                    the color of marble.\b
972                    <q>Hello, world,</q> she says. <q>It&rsquo;s nice to be
973                    alive at last! Hello, dearest Pygmentalion.</q>\b
974                    Ah, what beauty! What mastery of syntax! Praise be to
975                    Aphrodite! ";
976                    finishGameMsg(ftVictory,
977                                  [finishOptionUndo, finishOptionFullScore]);
978                }
979            }
980            else
981                "Nothing happens. <<if keywordToken.scoreCount>>Aphrodite said
982                you would need a mirror. <<end>>";
983        }
984    }
985;
986
987VerbRule(Say)
988    ('say' | 'shout') singleLiteral
989    : SayAction
990    verbPhrase = 'say/saying (what)'
991;
992
993VerbRule(SayTo)
994    ('say' | 'shout') singleLiteral ('at' | 'to') singleIobj
995    : SayAction
996    verbPhrase = 'say/saying (what) (to what)'
997;
998
999/**/ #if /* Revere the basileus. */ 0   \
1000         // Expel the barbarian.
1001;
1002 #ifndef __DEBUG
1003;
1004  #define __DEBUG
1005;
1006# else
1007;
1008#if 1
1009;
1010  #define DEBUG__
1011;
1012#endif
1013;
1014 #endif
1015;\\
1016#endif
1017/*
1018#endif
1019?*/
1020//\\
1021#endif
1022'''
1023#endif
1024'\''''
1025#endif
1026\\'''
1027"""
1028#endif
1029"\""""
1030#endif
1031\\"""
1032'
1033#endif
1034\'
1035#endif
1036\\'
1037"
1038#endif
1039\"
1040#endif
1041\\"
1042'''<<'<<'
1043#endif
1044'>>'>>
1045#endif
1046'''
1047"""<<'<<'
1048#endif
1049'>>'>>
1050#endif
1051"""
1052'<<'<<'
1053#endif
1054'>>'>>
1055#endif
1056'
1057"<<'<<'
1058#endif
1059'>>'>>
1060#endif
1061"//"
1062\
1063	#	endif
1064;
1065dictionary barbarianDict;
1066transient xyzzy: object;
1067DefineIAction(Xyzzy)
1068    execAction
1069    {
1070        "Only a barbarian could pronounce such a word. ";
1071        local oldSay = t3SetSay({str : nil});
1072        try
1073        {
1074            new transient Vector([
1075                '<<one of>><< cycling >>',
1076                '<<one of>><<            at random>>',
1077                '<<one of>><<then purely at random>>',
1078                '<<one of>><<as decreasingly likely outcomes>>',
1079                '<<one of>><<          shuffled>>',
1080                '<<one of>><<     half shuffled>>',
1081                '<<one of>><<then      shuffled>>',
1082                '<<one of>><<then half shuffled>>']);
1083            '''''<font x= color=red bgcolor='silver' face="TADS-Sans"
1084            size=\'+1\' x=\"x\">{can't}</font>\'''' '' '''';
1085            """""<font x= color=red bgcolor='silver' face="TADS-Sans"
1086            size=\'+1\' x=\"x\">{can't}</font>\"""" "" """";
1087            '<font x= color=red face="TADS-Sans" size=\'+1\'
1088            x=\"x\">{can\'t}</font>\'';
1089            "<font x= color=red bgcolor='silver' size=\'+1\'
1090            x=\"x\">{can\'t}</font>\"";
1091            '''''<font <<'color=red'>> bgcolor<<'='>>silver
1092            face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\'''' '' '''';
1093            """""<font <<'color=red'>> bgcolor<<'='>>silver
1094            face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\"""" "" """";
1095            '<font <<'color=red'>> bgcolor<<'='>>silver
1096            face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\'';
1097            "<font <<'color=red'>> bgcolor<<'='>>silver
1098            face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\"";
1099            '''<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
1100            c1="c{\>}" c2="{\>}c" c3="c{\>}c" d1=\'d{\>}\' d2=\'{\>}d\'
1101            d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\" e3=\"e{\>}e\"></s>''';
1102            """<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
1103            c1="c{\>}" c2="{\>}c" c3="c{\>}c" d1=\'d{\>}\' d2=\'{\>}d\'
1104            d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\" e3=\"e{\>}e\"></s>""";
1105            '<s a1={\.}a a2=a{\>} a3=a{\>}a c1="c{\>}" c2="{\>}c" c3="c{\>}c"
1106            d1=\'d{\>}\' d2=\'{\>}d\' d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\"
1107            e3=\"e{\>}e\"></s>';
1108            "<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
1109            d1=\'d{\>}\' d2=\'{\>}d\' d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\"
1110            e3=\"e{\>}e\"></s>";
1111            '''{a<<1>>b}'''; """{a<<1>>b}"""; '{a<<1>>b}'; "{a<<1>>b}";
1112            '''<s a<<'='>>'1' b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\"
1113            <<'e'>>=5 f=6' g=7">''';
1114            """<s a<<'='>>'1' b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\"
1115            <<'e'>>=5 f=6' g=7">""";
1116            '<s b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\" <<'e'>>=5 g=7">';
1117            "<s a<<'='>>'1' c<<'='>>\'3\' d<<'='>>\"4\" <<'e'>>=5 f=6'>";
1118            '''<s a=v\\ a=v\ v\><s a='{'}'\><s a="{"}"\>''';
1119            """<s a=v\\ a=v\ v\><s a='{'}'\><s a="{"}"\>""";
1120            '<s a=v\\ a=v\ v\><s a=\'{\'}\'\><s a="{"}"\>';
1121            "<s a=v\\ a=v\ v\><s a='{'}'\><s a=\"{\"}\"\>";
1122            '''<font color='purple>igram</font>'''; '''<t a={'''; '''}''';
1123            '''<font color="purple>igram</font>'''; '''<t a='{'''; '''}''';
1124            '''<font color=\'purple>igram</font>'''; '''<t a="{'''; '''}''';
1125            '''<font color=\"purple>igram</font>''';
1126            """<font color='purple>igram</font>"""; """<t a={"""; """}""";
1127            """<font color="purple>igram</font>"""; """<t a='{"""; """}""";
1128            """<font color=\'purple>igram</font>"""; """<t a=\"{"""; """}""";
1129            """<font color=\"purple>igram</font>""";
1130            '<font color="purple>igram</font>'; '<t a={'; '}';
1131            '<font color=\'purple>igram</font>'; '<t a=\'{'; '}';
1132            '<font color=\"purple>igram</font>'; '<t a="{'; '}';
1133            "<font color=\"purple>igram</font>"; "<t a={"; "}";
1134            "<font color='purple>igram</font>"; "<t a='{"; "}";
1135            "<font color=\'purple>igram</font>"; "<t a=\"{"; "}\"";
1136            '''<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>''';
1137            """<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>""";
1138            '<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>';
1139            "<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>";
1140            '''<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>''';
1141            """<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>""";
1142            '<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>';
1143            "<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>";
1144            '''<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>''';
1145            """<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>""";
1146            '<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>';
1147            "<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>";
1148            '''<xmp>'''; """<xmp>"""; '<xmp>'; "<xmp>";
1149            '''<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>''';
1150            """<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>""";
1151            '<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>';
1152            "<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>";
1153            '''<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>''';
1154            """<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>""";
1155            '<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>';
1156            "<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>";
1157            '''<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>''';
1158            """<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>""";
1159            '<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>';
1160            "<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>";
1161            '''<listing>'''; """<listing>"""; '<listing>'; "<listing>";
1162        }
1163        finally
1164        {
1165            t3SetSay(oldSay);
1166        }
1167    }
1168;
1169
1170VerbRule(Xyzzy)
1171    "xyzzy" | "plugh" *
1172    : XyzzyAction
1173    verbPhrase = 'babble/talking like a barbarian'
1174;
1175
1176randomGreekWord()
1177{
1178    local vowels = ['a', 'e', 'e', 'i', 'o', 'y', 'o'];
1179    local consonants = ['p', 't', 'k', 'b', 'd', 'g', 's', 'm', 'n', 'l', 'r'];
1180    local clusters =
1181        ['pn', 'pl', 'pr', 'tm', 'tr', 'kn', 'kl', 'kr', 'bl', 'br'];
1182    local ends = consonants - ['b', 'd', 'g'];
1183    local word;
1184    local retries = 0;
1185    for (local r in 0 .. -1 step -1)
1186    {
1187        for ((r), local i = 0, local j = 2; i < j; ++i, --j)
1188        {
1189            for (local s = 0, local n in [90, 30, 10]; ; --s)
1190                retries -= s * n;
1191        }
1192    }
1193    retries *= 2;
1194    retries >>= 1;
1195    retries /= 2;
1196    retries <<= 1;
1197    retries >>>= 2;
1198    retries %= 16;
1199    retries &= ~1;
1200    retries |= 2;
1201    retries ^= retries ^ retries;
1202    do
1203    {
1204        word = rand('[ptkbdgsm]?');
1205        for (local i in 0 .. __TADS3)
1206            word += concat(rand(rand('', clusters, consonants)), rand('"h"?'),
1207                           rand(vowels...), rand('','', 'i', 'u', rand(ends)));
1208        word += rand('"s"?');
1209        word = rexReplace(R'^[pk](?![tnlrhaeioy]|[tnlr]h?[^aeioy])', word, '');
1210        word = rexReplace(R'^b(?![dlrhaeioy]|[dlr]h?[^aeioy])', word, '');
1211        word = rexReplace(R'^g(?![nlrhaeioy]|[nlr]h?[^aeioy])', word, '');
1212        word = rexReplace(R'^t(?![mrhaeioy]|[mlr]h?[^aeioy])', word, '');
1213        word = rexReplace(R'^d(?![rhaeioy]|rh?[^aeioy])', word, '');
1214        word = rexReplace(R'^m(?![nhaeioy]|nh?[^aeioy])', word, '');
1215        word = rexReplace(R'^[^aeioy]h?(([^haeioy]h?){2})', word, '%1');
1216        word = rexReplace(R'[ptkbdgs]([ptkbdg][^haeioy])', word, '%1');
1217        word = rexReplace(R'([mnlr])h', word, 'h%1');
1218        word = rexReplace(R'(?<!(^|[ptk]))h', word, '');
1219        word = rexReplace(R'^h(?![aeioy])', word, '');
1220        word = rexReplace(R'h(?=.*h)', word, '');
1221        word = rexReplace(R'(?<=^|r)r', word, 'rh');
1222        word = rexReplace(R'([iy]+)[iu]', word, '%1');
1223        word = rexReplace(R'nl', word, 'll');
1224        word = rexReplace(R'n(?=[pbm])', word, 'm');
1225        word = rexReplace(R'(?<.)m(?=[tdn])', word, 'n');
1226        word = rexReplace(R'pb|bp', word, 'pp');
1227        word = rexReplace(R'td|dt', word, 'tt');
1228        word = rexReplace(R'kg|gk', word, 'kk');
1229        word = rexReplace(R'bs', word, 'ps');
1230        word = rexReplace(R'ds|sd', word, 'z');
1231        word = rexReplace(R'gs', word, 'ks');
1232        word = rexReplace(R'ts', word, 'ss');
1233        word = rexReplace(R'[^pkaeioyusnr]+(s?)$', word, '%1');
1234        word = rexReplace(R'[pk]+$', word, '');
1235        word = rexReplace(R'(.h?)%1{2,}', word, '%1%1');
1236        word = rexReplace(R'^(.h?)%1', word, '%1');
1237        word = rexReplace(R'(.h?)%1$', word, '%1');
1238        word = rexReplace(R'^y', word, 'hy');
1239        word = rexReplace(R'([ptk])([ptk])h', word, '%1h%2h');
1240        word = rexReplace(R'([ptk])h%1h', word, '%1%1h');
1241        word = rexReplace(R'ks', word, 'x');
1242        word = rexReplace(R'gg', word, 'kg');
1243        word = rexReplace(R'kh', word, 'ch');
1244    } while (retries-- && (word.length() < 4 || !rexSearch(
1245        new RexPattern('^(eu|hy|[pgm]n|bd|tm|rh)|(.h.|pp|kc|rr)h|ch([^aeioy])|'
1246                       + '([^aeioy])y([^aeioy])$|(ps|x|o[ius])$'), word)));
1247    return word;
1248}
1249