To manage the line skips which space paragraphs out.


§1. Paragraph Control. Ah, yes: the paragraph breaking algorithm. In {\it \TeX: The Program}, Donald Knuth writes at section 768: "It's sort of a miracle whenever \halign and \valign work, because they cut across so many of the control structures of \TeX." It's sort of a miracle whenever Inform 7's paragraph breaking system works, too. Most users probably imagine that it's implemented by having I7 look at where the cursor currently is (at the start of a line or not) and whether a line has just been skipped. In fact, the virtual machines simply do not offer facilities like that, and so we have to use our own book-keeping. Given the huge number of ways in which text can be printed, this is a delicate business. For some years now, "spacing bugs" — those where a spurious extra skipped line appears in a paragraph break, or where, conversely, no line is skipped at all — have been the least welcome in the Inform bugs database.

The basic method is to set say__p, the paragraph flag, when we print any matter; every so often we reach a "divide paragraph" point — for instance when one rule has finished and before another is about to start — and at those positions we look for say__p, and print a skipped line (and clear say__p again) if we find it. Thus:

    > WAIT
    The clock ticks ominously.  ...first rule
                         ...skipped line printed at a Divide Paragraph point
    Mme Tourmalet rises from her chair and slips out.   ...second rule
                         ...skipped line printed at a Divide Paragraph point
    >

A divide paragraph point occurs between any two rules in an action rulebook, but not an activity rulebook: many activities exist to print text, such as the names of objects, and there would be wild spacing accidents if paragraphs were divided there. Inform places DPPs elsewhere, too: and the text substitution "[conditional paragraph break]" allows the user to place one anywhere.

A traditional layout convention handed down from Infocom makes an exception of the first paragraph to appear after the prompt, but only in one situation. Ordinarily, the first paragraph of any turn appears straight after the prompt:

    > EXAMINE DOG
    Mme Tourmalet's borzoi looks as if it means fashion, not business.

The command is echoed on screen as the player types, but this doesn't set the paragraph flag, which is still clear when the text "Mme Tourmalet's..." begins to be printed. The single exception occurs when the command calls for the player to go to a new location, when a skipped line is printed before the room description for the new room. Thus:

    > SOUTH
        ...the "going look break" here
    Rocky Beach

(Note that this is not inherent in the looking action:

    > LOOK
    Rocky Beach

...which obeys the standard paragraphing conventions.)

So much for automatic paragraph breaks. However, we need a variety of different ways explicitly to control paragraphs, in order to accommodate traditional layout conventions handed down from Infocom.

The simplest exceptional kind of paragraph break is a "command clarification break", in which a single new-line is printed but there is no skipped line: as the name implies, it's traditionally used when a command such as OPEN DOOR is clarified. For example:

    (first unlocking the oak door)   ...now a command clarification break
    You open the oak door.

This is not quite the same thing as a "run paragraph on" break, in which we also deliberately suppress the skipped line, but make an exception for the skipped line which ought to appear last before the prompt: the idea is to merge two or more paragraphs together.

    > TAKE ALL
    marmot: Taken.   ...we run paragraph on here
    weasel: Taken.   ...and also here
                     ...despite which the final skip does occur
    >                ...before the next prompt

A more complicated case is "special look spacing", used for the break which occurs after the (boldface) short name of a room description is printed. This is tricky because it is sometimes followed directly by a long description, and we don't want a skipped line:

    Villa Christiane    ...a special look spacing break
    The walled garden of a villa in Cap d'Agde.
                        ...a Divide Paragraph break
    Mme Tourmalet's borzoi lazes in the long grass.

But sometimes it is followed directly by a subsequent paragraph, and again we want no skip:

    Villa Christiane    ...a special look spacing break
    Mme Tourmalet's borzoi lazes in the long grass.

And sometimes it is the only content of the room description and is followed only by the prompt:

    Villa Christiane    ...a special look spacing break
                        ...a break inserted before the prompt
    >

To recap, we have five kinds of paragraph break:

We now have to implement all of these behaviours. The code, while very simple, is highly prone to behaving unexpectedly if changes are made, simply because of the huge number of circumstances in which paragraphs are printed: so change nothing without very careful testing.

§2. Tracing. Uncomment this line and rebuild the kit to enable tracing of what the algorithm below is doing. (This constant should not be used anywhere except in this file, where #Ifdef on it will have the expected effect: elsewhere, it might not.)

onstant LKTRACE_SPACING;

§3. State. The current state is stored in a combination of two global variables:

Not all printing is to the screen: sometimes the output is to a file, or to memory, and in that case we want to start the switched output at a clear paragraphing state and then go back to the screen afterwards without any sign of change. The correct way to do this is to push the say__p and say__pc variables onto the VM stack and call ClearParagraphing() before starting to print to the new stream, and then pull the variables back again before resuming printing to the old stream.

In no other case should any code alter say__pc except via the routines below.

[ ClearParagraphing r;
    say__p = 0; say__pc = 0;
];

[ DivideParagraphPoint;
    #ifdef LKTRACE_SPACING; print "[DPP", say__p, say__pc, "]"; #endif;
    if (say__p) {
        new_line; say__p = 0; say__pc = say__pc | PARA_COMPLETED;
        say__pc_save = true;
        if (say__pc & PARA_PROMPTSKIP) say__pc = say__pc - PARA_PROMPTSKIP;
        if (say__pc & PARA_SUPPRESSPROMPTSKIP) say__pc = say__pc - PARA_SUPPRESSPROMPTSKIP;
    }
    #ifdef LKTRACE_SPACING; print "[-->", say__p, say__pc, "]"; #endif;
    say__pc = say__pc | PARA_CONTENTEXPECTED;
    say__pc_save = (say__pc & PARA_COMPLETED);
];

[ AdjustParagraphPoint;
    #ifdef LKTRACE_SPACING; print "[APP ", say__p, " ", say__pc, " ", say__pc_save, "]^"; #endif;
    if (say__pc_save) say__pc = (say__pc | PARA_COMPLETED);
];

[ ParaContent;
    if (say__pc & PARA_CONTENTEXPECTED) {
        say__pc = say__pc - PARA_CONTENTEXPECTED;
        say__p = 1;
    }
];

[ GoingLookBreak;
    if (say__pc & PARA_COMPLETED == 0) new_line;
    ClearParagraphing(10);
];

[ CommandClarificationBreak;
    new_line;
    ClearParagraphing(11);
];

[ RunParagraphOn;
    #ifdef LKTRACE_SPACING; print "[RPO", say__p, say__pc, "]"; #endif;
    say__p = 0;
    say__pc = say__pc | PARA_PROMPTSKIP;
    say__pc = say__pc | PARA_SUPPRESSPROMPTSKIP;
];

[ SpecialLookSpacingBreak;
    #ifdef LKTRACE_SPACING; print "[SLS", say__p, say__pc, "]"; #endif;
    say__p = 0;
    say__pc = say__pc | PARA_PROMPTSKIP;
];

[ EnsureBreakBeforePrompt;
    if ((say__p) ||
        ((say__pc & PARA_PROMPTSKIP) && ((say__pc & PARA_SUPPRESSPROMPTSKIP)==0)))
        new_line;
    ClearParagraphing(12);
];

[ PrintSingleParagraph matter;
    say__p = 1;
    say__pc = say__pc | PARA_NORULEBOOKBREAKS;
    TEXT_TY_Say(matter);
    DivideParagraphPoint();
    say__pc = 0;
];

§4. Say Number. The global variable say__n is set to the numerical value of any quantity printed, and this is used for the text substitution "[s]", so that "You have been awake for [turn count] turn[s]." will expand correctly.

[ STextSubstitution;
    if (say__n ~= 1) print "s";
];

§5. Print English Number. Another traditional name, this: in fact it prints the number as text in whatever is the current language of play.

[ EnglishNumber n; LanguageNumber(n); ];

[ LanguageNumber n f;
    if (n == 0)    { print "zero"; rfalse; }
    if (n == MIN_NEGATIVE_NUMBER) {
#Iftrue (WORDSIZE == 4);
        print "minus two billion"; n = 147483648; f = 1;
#Ifnot;
        print "minus thirty-two thousand"; n = 768; f = 1;
#Endif;
    }
    if (n < 0)     { print "minus "; n = -n; }
#Iftrue (WORDSIZE == 4);
    if (n >= 1000000000) {
        if (f == 1) print ", ";
        print (LanguageNumber) n/1000000000, " billion"; n = n%1000000000; f = 1;
    }
    if (n >= 1000000) {
        if (f == 1) print ", ";
        print (LanguageNumber) n/1000000, " million"; n = n%1000000; f = 1;
    }
#Endif;
    if (n >= 1000) {
        if (f == 1) print ", ";
        print (LanguageNumber) n/1000, " thousand"; n = n%1000; f = 1;
    }
    if (n >= 100)  {
        if (f == 1) print ", ";
        print (LanguageNumber) n/100, " hundred"; n = n%100; f = 1;
    }
    if (n == 0) rfalse;
    if (BasicInformKit`AMERICAN_DIALECT_CFGF) {
        if (f == 1) print " ";
    } else {
        if (f == 1) print " and ";
    }
    if ((n >= 20) && (n<100)) {
        switch (n/10) {
            2:  print "twenty";
            3:  print "thirty";
            4:  print "forty";
            5:  print "fifty";
            6:  print "sixty";
            7:  print "seventy";
            8:  print "eighty";
            9:  print "ninety";
        }
        if (n%10 ~= 0) print "-", (LanguageNumber) n%10;
    } else {
        switch (n) {
          1:    print "one";
          2:    print "two";
          3:    print "three";
          4:    print "four";
          5:    print "five";
          6:    print "six";
          7:    print "seven";
          8:    print "eight";
          9:    print "nine";
          10:   print "ten";
          11:   print "eleven";
          12:   print "twelve";
          13:   print "thirteen";
          14:   print "fourteen";
          15:   print "fifteen";
          16:   print "sixteen";
          17:   print "seventeen";
          18:   print "eighteen";
          19:   print "nineteen";
        }
    }
];