I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Printing.i6t

Printing contents

Paragraph Control.

Ah, yes: the paragraph breaking algorithm. In TEX: The Program, Donald Knuth writes at § 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"
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:

(a) Standard breaks at "divide paragraph points", used between rules. (b) The "going look break", used before the room description after going to a new room. (c) A "command clarification break", used after text clarifying a command. (d) A "run paragraph on" break, used to merge multiple paragraphs into a single block of text. (e) The "special look spacing" break, used after the boldface headline of a room description.

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.

State.

The current state is stored in a combination of two global variables:

(1) say__p, the "say paragraph" flag, which is set if a paragraph break needs to be printed before the next text can begin; (2) say__pc, originally named as the "paragraph completed" flag, but which is now a bitmap: (-2a) PARA_COMPLETED is set if a standard paragraph break has been made since the last time the flag was cleared; (-2b) PARA_PROMPTSKIP is set to indicate that the current printing position does not follow a skipped line, and that further material is expected which will run on from the previous paragraph, but that if no further material turns up then a skipped line would be needed before the next prompt; (-2c) PARA_SUPPRESSPROMPTSKIP is set to indicate that, despite PARA_PROMPTSKIP being set, no skipped line is needed before the prompt after all; (-2d) PARA_NORULEBOOKBREAKS suppresses divide paragraph points in between rules in rulebooks; it treats all rulebooks, and in particular action rulebooks, the way activity rulebooks are treated. (The flag is used for short periods only and never across turn boundaries, prompts and so on.) (-2e) PARA_CONTENTEXPECTED is set after a paragraph division as a signal that if any contents looks likely to be printed soon then say__p needs to be set, because a successor paragraph will then have started. This is checked by calling ParaContent() – while it's slow to have to call this routine so often, that's better than compiling inline code with the same effect, because minimising compiled code size is more important, and speed is never a big deal when printing.

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.

167!Constant TRACE_I7_SPACING; 168 169[ ClearParagraphing r; 170    say__p = 0; say__pc = 0; 171]; 172 173[ DivideParagraphPoint; 174    #ifdef TRACE_I7_SPACING; print "[DPP", say__p, say__pc, "]"; #endif; 175    if (say__p) { 176        new_line; say__p = 0; say__pc = say__pc | PARA_COMPLETED; 177        say__pc_save = true; 178        if (say__pc & PARA_PROMPTSKIP) say__pc = say__pc - PARA_PROMPTSKIP; 179        if (say__pc & PARA_SUPPRESSPROMPTSKIP) say__pc = say__pc - PARA_SUPPRESSPROMPTSKIP; 180    } 181    #ifdef TRACE_I7_SPACING; print "[-->", say__p, say__pc, "]"; #endif; 182    say__pc = say__pc | PARA_CONTENTEXPECTED; 183    say__pc_save = (say__pc & PARA_COMPLETED); 184]; 185 186[ AdjustParagraphPoint; 187    #ifdef TRACE_I7_SPACING; print "[APP ", say__p, " ", say__pc, " ", say__pc_save, "]^"; #endif; 188    if (say__pc_save) say__pc = (say__pc | PARA_COMPLETED); 189]; 190 191[ ParaContent; 192    if (say__pc & PARA_CONTENTEXPECTED) { 193        say__pc = say__pc - PARA_CONTENTEXPECTED; 194        say__p = 1; 195    } 196]; 197 198[ GoingLookBreak; 199    if (say__pc & PARA_COMPLETED == 0) new_line; 200    ClearParagraphing(10); 201]; 202 203[ CommandClarificationBreak; 204    new_line; 205    ClearParagraphing(11); 206]; 207 208[ RunParagraphOn; 209    #ifdef TRACE_I7_SPACING; print "[RPO", say__p, say__pc, "]"; #endif; 210    say__p = 0; 211    say__pc = say__pc | PARA_PROMPTSKIP; 212    say__pc = say__pc | PARA_SUPPRESSPROMPTSKIP; 213]; 214 215[ SpecialLookSpacingBreak; 216    #ifdef TRACE_I7_SPACING; print "[SLS", say__p, say__pc, "]"; #endif; 217    say__p = 0; 218    say__pc = say__pc | PARA_PROMPTSKIP; 219]; 220 221[ EnsureBreakBeforePrompt; 222    if ((say__p) || 223        ((say__pc & PARA_PROMPTSKIP) && ((say__pc & PARA_SUPPRESSPROMPTSKIP)==0))) 224        new_line; 225    ClearParagraphing(12); 226]; 227 228[ PrintSingleParagraph matter; 229    say__p = 1; 230    say__pc = say__pc | PARA_NORULEBOOKBREAKS; 231    TEXT_TY_Say(matter); 232    DivideParagraphPoint(13); 233    say__pc = 0; 234];

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.

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

Prompt.

This is the text printed just before we wait for the player's command: it prompts him to type.

251[ PrintPrompt i; 252    RunTimeProblemShow(); 253    ClearRTP(); 254    style roman; 255    EnsureBreakBeforePrompt(); 256    TEXT_TY_Say( (+ command prompt +) ); 257    ClearBoxedText(); 258    ClearParagraphing(14); 259];

Boxed Quotations.

These appear once only, and happen outside of the paragraphing scheme: they are normally overlaid as windows on top of the regular text. We can request one at any time, but it will appear only at prompt time, when the screen is fairly well guaranteed not to be scrolling. (Only fairly well since it's just possible that Border Zone-like tricks with real-time play might be going on, but whatever happens, there is at least a human-appreciable pause in which the quotation can be read before being taken away again.)

271Global pending_boxed_quotation; ! a routine to overlay the quotation on screen 272 273[ DisplayBoxedQuotation Q; 274    pending_boxed_quotation = Q; 275]; 276 277[ ClearBoxedText i; 278    if (pending_boxed_quotation) { 279        for (i=0: Runtime_Quotations_Displayed-->i: i++) 280            if (Runtime_Quotations_Displayed-->i == pending_boxed_quotation) { 281                pending_boxed_quotation = 0; 282                return; 283            } 284        Runtime_Quotations_Displayed-->i = pending_boxed_quotation; 285 286        ClearParagraphing(15); 287        pending_boxed_quotation(); 288        ClearParagraphing(16); 289 290        pending_boxed_quotation = 0; 291    } 292];

Score Notification.

This doesn't really deserve to be at I6 level at all, but since for traditional reasons we need to use conditional compilation on USE_SCORING, and since we want a fancy text style for Glulx, ...

300[ NotifyTheScore d; 301#Iftrue USE_SCORING ~= 0; 302    if (notify_mode == 1) { 303        DivideParagraphPoint(); 304        VM_Style(NOTE_VMSTY); 305        d = score-last_score; 306        if (d > 0) { ANNOUNCE_SCORE_RM('D', d); } 307        else if (d < 0) { ANNOUNCE_SCORE_RM('E', -d); } 308        new_line; 309        VM_Style(NORMAL_VMSTY); 310    } 311#Endif; 312];

Status Line.

Status line printing happens on the upper screen window, and outside of the paragraph control system.

Support for version 6 of the Z-machine is best described as grudging. It requires a heavily rewritten DrawStatusLine equivalent, to be found in ZMachine.i6t.

323#Ifdef TARGET_ZCODE; 324#Iftrue (#version_number == 6); 325[ DrawStatusLine; Z6_DrawStatusLine(); ]; 326#Endif; 327#Endif; 328 329#Ifndef DrawStatusLine; 330[ DrawStatusLine width posb; 331    @push say__p; @push say__pc; 332    BeginActivity(CONSTRUCTING_STATUS_LINE_ACT); 333    VM_StatusLineHeight(1); VM_MoveCursorInStatusLine(1, 1); 334    if (statuswin_current) { 335        width = VM_ScreenWidth(); posb = width-15; 336        spaces width; 337        ClearParagraphing(17); 338        if (ForActivity(CONSTRUCTING_STATUS_LINE_ACT) == false) { 339            VM_MoveCursorInStatusLine(1, 2); 340            TEXT_TY_Say(left_hand_status_line); 341            VM_MoveCursorInStatusLine(1, posb); 342            TEXT_TY_Say(right_hand_status_line); 343        } 344        VM_MoveCursorInStatusLine(1, 1); VM_MainWindow(); 345    } 346    ClearParagraphing(18); 347    EndActivity(CONSTRUCTING_STATUS_LINE_ACT); 348    @pull say__pc; @pull say__p; 349]; 350#Endif;

Status Line Utilities.

Two convenient routines for the default values of right_hand_status_line and left_hand_status_line respectively. SL_Location also implements the text substitution "[player's surroundings]".

358Array T_SL_Score_Moves --> CONSTANT_PACKED_TEXT_STORAGE SL_Score_Moves; 359 360[ SL_Score_Moves; 361    if (not_yet_in_play) return; 362    #Iftrue USE_SCORING > 0; print sline1, "/", sline2; #endif; 363]; 364 365Array T_SL_Location --> CONSTANT_PACKED_TEXT_STORAGE SL_Location; 366 367[ SL_Location even_before; 368    if ((not_yet_in_play) && (even_before == false)) return; 369    if (location == thedark) { 370        BeginActivity(PRINTING_NAME_OF_DARK_ROOM_ACT); 371        if (ForActivity(PRINTING_NAME_OF_DARK_ROOM_ACT) == false) 372             DARKNESS_NAME_INTERNAL_RM('A'); 373        EndActivity(PRINTING_NAME_OF_DARK_ROOM_ACT); 374    } else { 375        FindVisibilityLevels(); 376        if (visibility_ceiling == location) print (name) location; 377        else print (The) visibility_ceiling; 378    } 379]; 380 381[ DARKNESS_NAME_INTERNAL_R; ];

Banner.

Note that NI always compiles Story and Headline texts, but does not always compile a Story_Author.

388[ Banner; 389   BeginActivity(PRINTING_BANNER_TEXT_ACT); 390   if (ForActivity(PRINTING_BANNER_TEXT_ACT) == false) { 391           VM_Style(HEADER_VMSTY); 392        TEXT_TY_Say(Story); 393        VM_Style(NORMAL_VMSTY); 394        new_line; 395        TEXT_TY_Say(Headline); 396        #ifdef Story_Author; 397        print " by "; TEXT_TY_Say(Story_Author); 398        #endif; ! Story_Author 399        new_line; 400        VM_Describe_Release(); 401        print " / Inform 7 build ", (PrintI6Text) NI_BUILD_COUNT, " "; 402        print "(I6/v"; inversion; 403        print " lib ", (PrintI6Text) LibRelease, ") "; 404        #Ifdef STRICT_MODE; 405        print "S"; 406        #Endif; ! STRICT_MODE 407        #Ifdef DEBUG; 408        print "D"; 409        #Endif; ! DEBUG 410        new_line; 411    } 412    EndActivity(PRINTING_BANNER_TEXT_ACT); 413];

Print Decimal Number.

DecimalNumber is a trivial function which just prints a number, in decimal digits. It is left over from the I6 library's support routines for Glulx, where it was intended as a stub to pass to the Glulx Glulx_PrintAnything routine (which I7 does not use). In I7, however, it's also used as the default printing routine for new kinds of value.

423[ DecimalNumber num; print num; ];

Print English Number.

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

430[ EnglishNumber n; LanguageNumber(n); ];

Print Text.

The routine for printing an I7 "text" value, which might be text with or without substitutions.

437[ PrintI6Text x; 438    if (x ofclass String) print (string) x; 439    if (x ofclass Routine) return (x)(); 440    if (x == EMPTY_TEXT_PACKED) rfalse; 441    rtrue; 442]; 443[ I7_String x; TEXT_TY_Say(x); ]; ! An alternative name now used only by extensions

Print Or Run.

This utility remains from the old I6 library: it essentially treats a property as textual and prints it where possible. Where the no_break flag is set, we expect the text to form only a small part of a paragraph, and it's inappropriate to break here: for instance, for printing the "printed name" of an object. Where the flag is clear, however, the text is expected to form its own paragraph.

Where PrintOrRun is used in breaking mode, which is only for a very few properties in I7 (indeed at present only initial and description), the routine called is given the chance to decide whether to print or not. It should return true or false according to whether it did so; this allows us to divide the paragraph or not accordingly.

460[ PrintOrRun obj prop no_break pv st routine_return_value; 461    @push self; self = obj; 462    if (prop == 0) { 463        print (name) prop; routine_return_value = true; 464    } else { 465        routine_return_value = TEXT_TY_Say(obj.prop); 466    } 467    @pull self; 468    if (routine_return_value) { 469        say__p = 1; 470        if (no_break == false) { 471            new_line; 472            DivideParagraphPoint(); 473        } 474    } 475 476    return routine_return_value; 477];

Short Name Storage.

None of the following functions should be called for the Z-machine if the short name exceeds the size of the following buffer: whereas the Glulx implementation of VM_PrintToBuffer will safely truncate overlong text, that's impossible for the Z-machine, and horrible results will follow.

CPrintOrRun is a variation on PrintOrRun, simplified by not needing to handle entire paragraphs (so, no fuss about dividing) but complicated by having to capitalise the first letter. We do this by writing to the buffer and then altering the first character.

491Array StorageForShortName buffer 250; 492 493[ CPrintOrRun obj prop v length i; 494    if ((obj ofclass String or Routine) || (prop == 0)) 495        VM_PrintToBuffer (StorageForShortName, 160, obj); 496    else { 497        if (obj.prop == NULL) rfalse; 498        if (metaclass(obj.prop) == Routine or String) 499            VM_PrintToBuffer(StorageForShortName, 160, obj, prop); 500        else return RunTimeError(2, obj, prop); 501    } 502 503    length = StorageForShortName-->0; 504 505    StorageForShortName->WORDSIZE = VM_LowerToUpperCase(StorageForShortName->WORDSIZE); 506    for (i=WORDSIZE: i<length+WORDSIZE: i++) print (char) StorageForShortName->i; 507    if (i>WORDSIZE) say__p = 1; 508 509    return; 510]; 511 512[ Cap str nocaps; 513    if (nocaps) print (string) str; 514    else CPrintOrRun(str, 0); 515];

Object Names I.

We now begin the work of printing object names. In the lowest level of this process we print just the name itself (without articles attached), and we do it by carrying out an activity.

523[ PSN__ o; 524    if (o == 0) { LIST_WRITER_INTERNAL_RM('Y'); rtrue; } 525    switch (metaclass(o)) { 526        Routine: print "<routine ", o, ">"; rtrue; 527        String: print "<string ~", (string) o, "~>"; rtrue; 528        nothing: print "<illegal object number ", o, ">"; rtrue; 529    } 530    RegardingSingleObject(o); 531    CarryOutActivity(PRINTING_THE_NAME_ACT, o); 532];

Standard Name Printing Rule.

In its initial state, the "printing the name of" activity has just one rule: the following "for" rule.

539Global caps_mode = false; 540 541[ STANDARD_NAME_PRINTING_R obj; 542    obj = parameter_value; 543 544    if (obj == 0) { 545        LIST_WRITER_INTERNAL_RM('Y'); return; 546    } 547    switch (metaclass(obj)) { 548        Routine: print "<routine ", obj, ">"; return; 549        String: print "<string ~", (string) obj, "~>"; return; 550        nothing: print "<illegal object number ", obj, ">"; return; 551    } 552    if (obj == player) { 553           if (indef_mode == NULL && caps_mode) PRINT_PROTAGONIST_INTERNAL_RM('A'); 554           else PRINT_PROTAGONIST_INTERNAL_RM('B'); 555           return; 556       } 557    #Ifdef LanguagePrintShortName; 558    if (LanguagePrintShortName(obj)) return; 559    #Endif; ! LanguagePrintShortName 560    if (indef_mode && obj.&short_name_indef ~= 0 && 561        PrintOrRun(obj, short_name_indef, true) ~= 0) return; 562    if (caps_mode && 563        obj.&cap_short_name ~= 0 && PrintOrRun(obj, cap_short_name, true) ~= 0) { 564        caps_mode = false; 565        return; 566    } 567    if (obj.&short_name ~= 0 && PrintOrRun(obj, short_name, true) ~= 0) return; 568    print (object) obj; 569];

Internal Rule.

574[ PRINT_PROTAGONIST_INTERNAL_R; ];

Object Names II.

The second level of the system for printing object names handles the placing of articles in front of them: the red herring, an elephant, Some bread. The following routine allows us to choose:

(a) obj, the object whose name is to be printed; (b) acode, the kind of article needed: capitalised definite (0), lower case uncapitalised definite (1), or uncapitalised indefinite (2); (c) pluralise, a flag forcing to a plural form (e.g., "some" being the pluralised form of an indefinite article in English); (d) capitalise, a flag forcing us to capitalise the article – it's by setting this that we can achieve the fourth option missing from (b), viz., capitalised indefinite. (All of this is a legacy design from a time when the I6 library did not support capitalised indefinite articles.)

The routine then looks after issues such as which contraction form to use: for instance, in English, whether to use "a" or "an" for the indefinite singular depends on the text of the object's name.

596Global short_name_case; 597 598[ PrefaceByArticle obj acode pluralise capitalise i artform findout artval; 599    if (obj provides articles) { 600        artval=(obj.&articles)-->(acode+short_name_case*LanguageCases); 601        if (capitalise) 602            print (Cap) artval, " "; 603        else 604            print (string) artval, " "; 605        if (pluralise) return; 606        print (PSN__) obj; return; 607    } 608 609    i = GetGNAOfObject(obj); 610    if (pluralise) { 611        if (i < 3 || (i >= 6 && i < 9)) i = i + 3; 612    } 613    i = LanguageGNAsToArticles-->i; 614 615    artform = LanguageArticles 616        + 3*WORDSIZE*LanguageContractionForms*(short_name_case + i*LanguageCases); 617 618    #Iftrue (LanguageContractionForms == 2); 619    if (artform-->acode ~= artform-->(acode+3)) findout = true; 620    #Endif; ! LanguageContractionForms 621    #Iftrue (LanguageContractionForms == 3); 622    if (artform-->acode ~= artform-->(acode+3)) findout = true; 623    if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true; 624    #Endif; ! LanguageContractionForms 625    #Iftrue (LanguageContractionForms == 4); 626    if (artform-->acode ~= artform-->(acode+3)) findout = true; 627    if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true; 628    if (artform-->(acode+6) ~= artform-->(acode+9)) findout = true; 629    #Endif; ! LanguageContractionForms 630    #Iftrue (LanguageContractionForms > 4); 631    findout = true; 632    #Endif; ! LanguageContractionForms 633 634    #Ifdef TARGET_ZCODE; 635    if (standard_interpreter ~= 0 && findout) { 636        StorageForShortName-->0 = 160; 637        @output_stream 3 StorageForShortName; 638        if (pluralise) print (number) pluralise; else print (PSN__) obj; 639        @output_stream -3; 640        acode = acode + 3*LanguageContraction(StorageForShortName + 2); 641    } 642    #Ifnot; ! TARGET_GLULX 643    if (findout) { 644        if (pluralise) 645            Glulx_PrintAnyToArray(StorageForShortName, 160, EnglishNumber, pluralise); 646        else 647            Glulx_PrintAnyToArray(StorageForShortName, 160, PSN__, obj); 648        acode = acode + 3*LanguageContraction(StorageForShortName); 649    } 650    #Endif; ! TARGET_ 651 652    Cap (artform-->acode, ~~capitalise); ! print article 653    if (pluralise) return; 654    print (PSN__) obj; 655];

Object Names III.

The routines accessible from outside this segment.

661[ IndefArt obj i; 662    if (obj == 0) { LIST_WRITER_INTERNAL_RM('Y'); rtrue; } 663    i = indef_mode; indef_mode = true; 664    if (obj has proper) { indef_mode = NULL; print (PSN__) obj; indef_mode = i; return; } 665    if ((obj provides article) && (TEXT_TY_Compare(obj.article, EMPTY_TEXT_VALUE) ~= 0)) { 666        PrintOrRun(obj, article, true); print " ", (PSN__) obj; indef_mode = i; 667        return; 668    } 669    PrefaceByArticle(obj, 2); indef_mode = i; 670]; 671 672[ CIndefArt obj i; 673    if (obj == 0) { LIST_WRITER_INTERNAL_RM('X'); rtrue; } 674    i = indef_mode; indef_mode = true; 675    if (obj has proper) { 676        indef_mode = NULL; 677        caps_mode = true; 678        print (PSN__) obj; 679        indef_mode = i; 680        caps_mode = false; 681        return; 682    } 683    if ((obj provides article) && (TEXT_TY_Compare(obj.article, EMPTY_TEXT_VALUE) ~= 0)) { 684        TEXT_TY_Say_Capitalised(obj.article); print " ", (PSN__) obj; indef_mode = i; 685        return; 686    } 687    PrefaceByArticle(obj, 2, 0, 1); indef_mode = i; 688]; 689 690[ DefArt obj i; 691    i = indef_mode; indef_mode = false; 692    if ((~~obj ofclass Object) || obj has proper) { 693        indef_mode = NULL; print (PSN__) obj; indef_mode = i; 694        return; 695    } 696    PrefaceByArticle(obj, 1); indef_mode = i; 697]; 698 699[ CDefArt obj i; 700    i = indef_mode; indef_mode = false; 701    if ((obj ofclass Object) && (obj has proper || obj == player)) { 702        indef_mode = NULL; 703        caps_mode = true; 704        print (PSN__) obj; 705        indef_mode = i; 706        caps_mode = false; 707        return; 708    } 709    if ((~~obj ofclass Object) || obj has proper) { 710        indef_mode = NULL; print (PSN__) obj; indef_mode = i; 711        return; 712    } 713    PrefaceByArticle(obj, 0); indef_mode = i; 714]; 715 716[ PrintShortName obj i; 717    i = indef_mode; indef_mode = NULL; 718    PSN__(obj); indef_mode = i; 719];

Say One Of.

These routines are described in the Extensions chapter of the Inform documentation.

726[ I7_SOO_PAR oldval count; if (count <= 1) return count; return random(count); ]; 727[ I7_SOO_RAN oldval count v; if (count <= 1) return count; 728    v = oldval; while (v == oldval) v = random(count); return v; ]; 729[ I7_SOO_STI oldval count v; if (oldval) return oldval; return I7_SOO_PAR(oldval, count); ]; 730[ I7_SOO_CYC oldval count; oldval++; if (oldval > count) oldval = 1; return oldval; ]; 731[ I7_SOO_STOP oldval count; oldval++; if (oldval > count) oldval = count; return oldval; ]; 732[ I7_SOO_TAP oldval count tn rn c; if (count <= 1) return count; tn = count*(count+1)/2; 733    rn = random(tn); for (c=1:c<=count:c++) { rn = rn - c; if (rn<=0) return (count-c+1); } ]; 734[ I7_SOO_TRAN oldval count; if (oldval<count) return oldval+1; 735    return count + 1 + I7_SOO_RAN(oldval%(count+1), count); ]; 736[ I7_SOO_TPAR oldval count; if (oldval<count) return oldval+1; 737    return count + 1 + I7_SOO_PAR(oldval%(count+1), count); ]; 738 739Array I7_SOO_SHUF->32; 740 741[ I7_SOO_SHU oldval count sd ct v i j s ssd scope cc base; 742    base = count+1; 743    v = oldval%base; oldval = oldval/base; ct = oldval%base; sd = oldval/base; 744    if (count > 32) return I7_SOO_PAR(oldval, count); 745    if (count <= 1) v = count; 746    else { 747        !print "^In v=", v, " ct=", ct, " sd=", sd, "^"; 748        cc = base*base; 749        scope = (MAX_POSITIVE_NUMBER-1)/cc; 750        !print "Scope = ", scope, "^"; 751        if (sd == 0) { sd = random(scope); ct=0; } 752        for (i=0:i<count:i++) I7_SOO_SHUF->i = i; 753        ssd = sd; 754        for (i=0:i<count-1:i++) { 755            j = (sd)%(count-i)+i; sd = (sd*31973)+17; if (sd<0) sd=-sd; 756            s = I7_SOO_SHUF->j; I7_SOO_SHUF->j = I7_SOO_SHUF->i; I7_SOO_SHUF->i = s; 757        } 758        !for (i=0:i<count:i++) print I7_SOO_SHUF->i, " "; print "^"; 759        v = (I7_SOO_SHUF->ct)+1; 760        ct++; if (ct >= count) { ct = 0; ssd = 0; } 761    } 762    !print "Out v=", v, " ct=", ct, " ssd=", sd, "^"; 763    !print "Return ", v + ct*base + ssd*base*base, "^"; 764    return v + ct*base + ssd*base*base; 765];