A-code texts explained

A-code texts are de facto objects with their own methods, making them into a far more powerful and flexible game component than in any other IF system. Yet they can be treated also as simple text strings – the complexity of "text morphing" is there to be used, but it does not force itself onto game writers.

This document explains A-code texts and their handling as of A-code version 12.91.


General notes on style

  • The use of upper/lower case in the examples is purely my programming convention; except within text definitions, A-code is case insensitive.
  • The use of dots to separate words in A-code entity names is also conventional; any other convention could be used, e.g. using dashes or underscores instead of dots, or (not recommended!) uppercasing the first letter of each word.
  • The use of commas as A-code statement parameter separators is purely optional, for increased readability; A-code counts commas to be white spaces.
  • All examples use the up-to-date A-code notation (A-code 12), which differs in some respects from that in the original version of the language by Dave Platt.

A-code text basics

This part of the document deals with the basics of A-code text declaration and use.

A-code text declarations

As of version 12, in-line texts are also permitted (see the in-line-text section), but traditionally A-code defines all its texts (other than place and objects descriptions) as separate named entities. The basic A-code text declaration has the form

   TEXT <text_name>

Like all major A-code directives, the keyword TEXT must be at the very beginning of a line. The text lines following it must all have at least one leading blank. The declaration is terminated by a line with a non-blank first character (i.e. another major directive or a comment), or by end-of-file.

While generally line breaks are ignored in the text definition (see the next section for more details), any texts declared using the TEXT directive are deemed to have a trailing end-of-line. A text fragment, without the trailing EOL can be declared using the FRAGMENT variant of a text declaration:

   FRAGMENT <text_name>

Here is a simple, purely artificial example:

      This line is sp
      lit into two parts.

When displayed by the A-code primitive SAY

   say line.start
   say line.end

The result is a single line "This line is split into two parts." followed by a line break.

Both TEXT and FRAGMENT directives can be used to declare "anonymous" texts by omitting the text name. This practice goes back to Dave Platt's original version and is now deprecated -- text switches can be used instead. See Appendix C on handling anonymous texts.

Basics of text definitions

A-code text definitions (the lines of text following the TEXT or FRAGMENT declaration line), are processed as follows:

  • Any leading or trailing spaces on an individual line are removed.
  • Simple line breaks (i.e. single end-of-line characters) are replaced with a space, after the removal of trailing characters. (But see the below discussion of text switches for an exception to this rule.)
  • Successive line breaks are reduced to two line breaks. I.e. successive blank lines are reduced to a single blank line
  • If after the removal of leading spaces, a line starts with a forward slash, the slash is removed; the only purpose of this arrangement is to be able to force a line to start with some leading spaces after all, or to force multiple successive blank lines.
  • The reverse slash character \ is used as a logical escape, to enable the display of characters with a special meaning (see Appendix B). The reverse slash itself can be escaped, of course, if it needs to be displayed as an ordinary character.
  • All unescaped underscores are converted into spaces. This is another mechanism for forcing leading spaces in a line of text.

In-line texts

In-line texts are accepted in all circumstances in which a reference to a named text is acceptable. The basic syntax is extremely simple. If a string beginning with a double quote is encountered where a text name would be appropriate, everything (line feeds included!) up to the next double quote is accepted as the desired text. E.g.

  say "This is a line of text, follwed by a blank line...

   followed by this line."

The acdc translator in fact declares a standard A-code text, assigns an automatically generated name to it, and replaces the whole quoted string with that name. The upshot is that all text features described in this document apply to in-line texts as well.

There is an additional syntax wrinkle to permit in-line texts to be text fragments and to permit them to have their own internal dynamics, as described in the dynamic implicit qualifiers section further in this document.

If an in-line text starts with one of i, c, r or f, followed the colon character :, it gives the text a special property. If the character is f ("f:") then the text is deemed to be a fragment, without a trailing line feed. The other three possibilities declare the text dynamic method to be one of increment, cycle or random, as explained in the dynamic explicit qualifiers section below.

Since fragment texts may also have their own dynamic methods, this additional syntax may be repeated as e.g. "f:c:" or "r:f:" etc.

Centered text

A text definition line beginning with the plus character + is treated as a line to be separately centered on the display. The plus sign is stripped off, and the line is prefixed with a line break (unless it happened to be preceded with one) and a line break is appended to it. The result is displayed centered.

A-code also understands "block centering". A centered block is a set of successive text definition lines, each of which is prefixed with the equals character =. The equals signs are stripped off, and the whole block is displayed offset to the right in such a way that its longest line appears centered on the display.

As with individual line centering, the block is prefixed by a line break if one is required (i.e. there wasn't one already), and suffixed with a line-break. Ends of line within the centered block are also honoured, contrary to the more general A-code convention.

Here is an artificial example showing both kinds of centering:

      This text shows
     +a centered line
     +and another centered line,
      as well as
     =a whole block
     =of lines, all of which
     =are centered as a single unit.
     Which is sometimes useful.

If displayed (e.g.) in console (i.e. a fixed size font) on an 60-character wide display by

   say centering.example

this would result in

   |  This  text shows                                        |
   |                      a centered line                     |
   |                 and another centered line,               |
   |  as well as                                              |
   |                a whole block                             |
   |                of lines, all of which                    |
   |                are centered as a single unit.            |
   |  Which is sometimes useful.                              |

Non-console mode (i.e. proportional font) displays achieve the same overall effect.

Text post-processing

A-code texts are not displayed to the player immediately. They are accumulated instead in an internal, dynamically sized buffer. The contents of this buffer are post-processed and displayed by the kernel when either a command prompt is issue to the player, or the game is about to exit. This permits some A-code directives to affect previously output text.

For example the resay directive, instead of appending its text to the output buffer, first empties the buffer so that its text completely replaces anything accumulated so far. On the other hand the append directive preserves the accumulated text, but strips from it any terminating blank lines, effectively appending its text to the last output paragraph.

The A-code kernel processes the accumulated buffer before displaying its contents, e.g. by normalising any successive blank lines to a single blank line. (Multiple blank lines may be magicked up by using the non-breaking space character – see the list of special characters in Appendix B.

A-code text-morphing features

This part of the document describes the "text-morphing" features of A-code texts.

Text switches defined

A text switch is effectively an indexed array of strings embedded in a text message. More than one text switch can be embedded in a single text, and on the other hand, a whole text may consist of a single text switch.

Formally, a text switch is a sequence of arbitrary text strings separated by forward slashes, with the whole sequence being enclosed in square brackets. E.g.


is an example of a simple text switch of four elements.

The idea is that when the whole message is displayed to the player, just one element from every given switch is selected for display, according to some rule. The selection is made by using some "qualifier" value as the index of each text switch array encountered in the message. Text switch elements are indexed from zero upwards, so that in the above example the element None has the index value of zero, while the element Many has the index value of three.

While ends of lines are treated in A-code text definitions as white spaces (just like, for example in HTML), an end of line immediately following a text switch separator character / is simply ignored, which conveniently allows text switches to be spread across several lines. E.g. the above shown switch could have been equivalently written as


because A-code ignores any line-leading (and line-trailing) spaces in text definitions.

Simple text switches, explicitly qualified

This text morphing feature was introduced by Dave Platt to handle messages such as the report of dwarf attacks on the player. Here's the definition of a text called knives.thrown and containing two text switches:

       [/One/Two/Three/Four/Five/Six/Seven/Many] nasty sharp 
       kni[/fe is/ves are] thrown at you!

This is displayed by an A-code text-handling language primitive SAY:

   say <text_name>, [<explicit_qualifier>]

or in this specific case

   say knives.thrown, thrown

The second parameter ("thrown") is in this case the name of a variable holding the number of knives thrown at the player, though from the point of view of the language syntax, it could be a constant, or any A-code entity possessing a value (e.g. an object or a location). In the absence of an implicit qualifier (to be explained later), this value is used to index any text switches embedded in the text supplied as the 1st parameter, where switch elements are counted from zero. So, if one knife is thrown, the displayed message will read:

   One nasty sharp knife is thrown at you!

But suppose more than one knife is thrown, say 5 of them. The first switch is unproblematic -- its index values go from 0 to 8 -- but the second switch only has elements 0, 1 and 2. This is handled by the primary rule of text switches: use the index value nearest to the qualifier value. So the message becomes:

   Five nasty sharp knives are thrown at you!

While in this example, the explicit qualifier was supplied as a variable, any A-code entity which has a value associated with it (e.g. an object, or a location, or a plain numeric constant) is also acceptable as an explicit qualifier.

Repeated text switch elements

In practice, text switch elements can be long, multi-line strings, and sometimes it is necessary to have some of the elements repeated. For example, if a player has a purse which can contain some coins, its contents could be described by the following text:

      There [are/is/are] 
      several/several/many] coin[s//s] in the purse.

Such repetition is clearly wasteful, as well as tiresome, and the maintenance of switches with repeat elements is unnecessarily complex, because any changes to the repeated elements need to be applied to each of them.

To get around this, A-code interprets a switch element consisting of the single character = to mean a repeat of the immediately preceding element. This in turn may have the same special format, in which case its preceding element is considered. For obvious reasons, the first (zero index) element of a switch may not be a repeat element.

Using this convention, the above PURSE.CONTAINS text could be defined as

      There [are/is/are] [no/one/two/three/several/
      =/=/=/=/=/many] coin[s//s] in the purse.

When qualified by an actual number of coins, this would specify the number as several for 4 to 9 coins and as many for any more than that.

Word and value holders

A-code texts can also contain word and value place-holders, which get replaced dynamically at run-time by words or values specified by the explicit qualifier. Place-holders of either kind can occur both outside and inside text switches.

The dollar sign $ is used as a value place-holder. If an unescaped dollar sign is encountered in a text to be displayed, and if the text is used with an explicit qualifier, the dollar is replaced with the numerical value of the qualifier. For example, the PURSE.CONTAINS text defined in the last section, could be re-defined as

      There [are/is/are] [no/$] coin[s//s] in the purse.

In this case, the A-code statement

   say purse.contains, 13

would result in the display of

   There are 13 coins in the purse.

Of course, as before, any A-code value-bearing entity (variable, location, object...) could be used as a qualifier, instead of a constant value.

A word place-holder is signalled by an unescaped hash sign #, and is conceptually similar, except that a word, rather than a value, indicated by the explicit qualifier is inserted in place of the hash sign. Just what can be used as an explicit qualifier in this case, is a bit more complicated.

All declared vocabulary terms obviously have one (or more, if there are synonyms) actual word associated with them. At the author's discretion, most objects and possibly some locations will also have an associated vocabulary word or words. In all these cases there will be a "primary" word -- the first one declared in the list of synonyms (if there are any synonyms). It is this primary word that gets inserted in place of a word place-holder.

All of this is easier to understand on some practical illustrations. Take for example the object chair1 with the associated nouns "chair" and "seat". Should there be a text declared as

      The # is not something mortal, so cannot be killed!

and an object declared as


where the name CHAIR1 is explicitly excluded from the player's vocabulary, then the statement

   say no.kill.things, chair1

would produce

   The chair is not something mortal, so cannot be killed!

What makes this much more useful than may appear at the first glance, is the fact that A-code variables may store either values or pointers (references, to you Algol fans!) to arbitrary A-code entities. Hence if the game code executes somewhere the statement

   lda target, chair1 # Point variable TARGET at object CHAIR1

where "target" is a variable name, then a subsequent statement

   say no.kill.things, target

will produce exactly the same display text as if the object "chair1" or just the simple noun "chair" were used as the explicit qualifier.

Word place-holders really come into their own because the mandatory A-code variables ARG1 and ARG2 hold respectively the verb and the noun of the player's last command. So assuming that the player said "KILL CHAIR", then

   say no.kill.things, arg2

would once again tell the player that chairs are not for killing.

However, there is a further subtlety here, when it comes to using player command words pointed at by the ARG1 and ARG2 variables, because in this case what is echoed as a part of the response is not necessarily the primary word associated with the referenced object, but the word actually used by the player (allowing for expansion of abbreviations, typo correction and vocabulary folding -- see a separate document on the full 3D structure of the A-code vocabulary). So if the player typed "KILL STO" (note the abbreviation of STOOL to STO, assumed to be unambiguous in this example), the displayed response would be

   The stool is not something mortal, so cannot be killed!

Word place-holders can also appear both within and without text switches, but we'll cover that somewhat later on, under the heading of switch and holder interplay.

Simple implicit qualifiers

Not all texts need explicit qualification. For example, any texts associated with objects or locations (i.e. various forms of object and location descriptions) are deemed to be implicitly qualified by the current state of the object or location -- i.e. by its current internal value.

Take for example the object BATTERIES defined as

   [/Fresh/Worn-out] batteries
  %[There is a pair of brand-new batteries in the goods tray./
   There are fresh batteries here./
   Some worn-out batteries have been discarded nearby.]
  &The two batteries are just the right size and shape for the
   lamp. Both are marked as "[BRAND-NEW/FRESH/WORN-OUT]" in
   chunky [blue/green/red] letters.

which has the usual triplet of the inventory, ordinary and detailed descriptions. Each of these contains one or more text switches, which will get automatically qualified by the state of the object. So for instance, if the batteries are spent, the statement

   describe batteries

will display

   The two batteries are just the right size and shape for the
   lamp. Both are marked as "WORN-OUT" in chunky red letters.

Similarly the INVENTORY command and the general LOOK command will display their appropriate descriptions qualifying the embedded text switches by the object's state.

It is important to note that implicit qualifiers always override any explicit qualifiers -- a point which will have a great significance in the next section. For now it is sufficient to observe that

   describe batteries, 0

would be completely pointless.

Dynamic implicit qualifiers

As already noted elsewhere, A-code texts also carry an internal state (or value), initialised by default (like all A-code values) to zero. Note, however, that to avoid overriding explicit qualifiers in simple text switches, internal text states are only used as implicit qualifiers if a "method" for their manipulation is given as a part of the text definition. Formally, a full text definition looks like this:

   TEXT [<method>] <text_name>

where <method> is one of increment, cycle, random and assigned. All of these have different effects.

  • If the method specified is increment, then every time the text is displayed, its internal state is incremented by one, until it reaches the number of elements of the switch with most elements embedded in the text. Here is an example from Adv770:
    TEXT increment ONCE.IS.ENOUGH
      [And thank goodness for that! /]I [/really /*really* /
      *REALLY* ]don't know why you decided to go and get lost
      in that dark forest. Let's say [once/twice/thrice/
      enough] is enough and not do it again, huh?
    This will automatically produce messages of increasing exasperation, as the value of the text tick up after every use, finally sticking at the value of 4.
  • The cycle method also causes the internal text state to increment by one, but the index value used by each individual switch within the text is the state value modulo the switch size, and the state gets reset to zero once it reaches the least common multiple of all switch sizes within that text.

    This is best demonstrated on a purely artificial example:

       TEXT cycle DIGITS
          [1/2] [1/2/3] [1/2/3/4]

    If used repeatedly, the state of this text will increment by one from zero to 11 and then return to zero again and repeat the same cycle. In the process it will produce successive displays "1 1 1", "2 2 2", "1 3 3", "2 1 4", "1 2 1", "2 3 2", "1 1 3", "2 2 4", "1 3 1", "2 1 2", "1 2 3", "2 3 4", "1 1 1" etc...

    As you can see above, each of the switches cycles independently of the rest, yet only one text state value is driving the whole process. This is very useful in assembling automatically at run-time a wide variety of responses.

  • The random method does what one would expect. After the text is displayed using its current state value, this value is reset at random to a value within the index range of the largest embedded text switch. The new value chosen is guaranteed to be different from the current one, so for switches with two components, this method acts exactly as the cycle one.

  • The assigned method is really a pseudo-method in that it does not actually do anything, other than enabling the text value as an implicit qualifier. Without it, the internal text state is ignored by switch processing, and an explicit qualifier has to be supplied instead.

It should be noted that none of the above described methods, nor the "null" non-method (i.e. when no method is specified) preclude the state value to be assigned into a text or numerically manipulated and examined like any ordinary variable. So for example, taking the above defined DIGITS text, the statements

   say digits      # Internal state starts from 0!
   add digits, 10  # It got incremented to 1, will now be 11
   say digits      # It's now 11 will be reset back to zero
   say digits

would result in "1 1 1", "2 3 4", "1 1 1"

In-line texts can also have dynamic implicit qualifiers. If such a text starts with the colon character ':', followed by a letter, followed by another colon, these three characters are stripped off and a dynamic method is associated with the in-line text in question on the basis of the letter between the two colons: i for increment, c for cycle, r for random. Note that the absence of the assigned pseudo-method, on the grounds that there is no way for an in-line text to be referred to by the rest of the A-code source.

Switch and holder interplay

Now that we have been through the details of implicit qualifiers, the interplay between text switches and place-holders can be stated very simply as two rules:

  1. Only explicit qualifiers are used when substituting for place-holders of either kind.
  2. For processing text switches, implicit qualifiers always override explicit qualifiers.

This enables responses such as exemplified by this Adv770 text:

    It's just a #. [Nothing very remarkable about it/Not
    remarkable in any way/Nothing special to it/The apt 
    description is "unremarkable"].

The typical use for this text is

   say its.just.a, arg2

which will replace the word place-holder in the text with the noun from the player's last command, while cycling in its successive uses through its embedded text switch.

It may at first appear strange that implicit qualifiers are ignored in place-holder substitution, while explicit qualifiers are overridden by implicit ones for switch processing. However, this arrangement does exactly what is often wanted, because it makes it much more sensible to have place-holders within text switches. Again, an example from Adv770 is probably a good illustration:

    [My ignorance shames me, but I do not know what action
    might be signified by "#"./Alas, my vocabulary is too
    limited to encompass "#". Try some other verb?/Very
    remiss of me to be sure, but I've never learned to "#"./
    To my shame, I have no idea what you mean by "#"./"#"? 
    Sorry, I don't what it means./I am afraid "#" is not a 
    verb I've ever learned./Ahem... "#" is not in my
    dictionary. Would you care to re-phrase?/Regrettably,
    that is not something I know how to do.]

If the game fails to make sense of the player's verb, all it has to do now is

   say nocomprende.verb, arg1

thereby producing a wide variety of responses.

As an aside, in practice I find that the cycle method is much more useful for this purpose than the random one. Contrary to our intuitive expectation, randomness tends to be non-uniform (i.e. "clumpy") and hence requires a large number of options, if obvious repetitions are to be avoided.

Text tying

As already noted, A-code variables can be in fact pointers to other A-code entities. The same is true for internal values of A-code texts. A text can be "tied" to another value-bearing entity, thereby removing the need for the game code to explicitly ensure the text value stays in sync with the state of that other entity.

In effect, tying texts to other entities is also a text "method" in that it activates the text's implicit qualifier (which in this case happens to be the value of the entity to which the text is tied). Because this additional text method is not purely internal to the text, there may be reasons for the tying to be performed dynamically within the game code. Hence tying is performed by means of an executable language statement, rather than in text declaration.

Once again, an example may be of help. Adv770 has a quartz seal, which can have one of two states. If the player tries to examine the seal when it is not in his inventory, the game tells him the seal is too small and needs to be picked up. The actual wording of this admonition depends on the state of the seal, hence the game initialisation code contains the statement

   tie pick.up.seal, seal

This effectively ties the value of the text PICK.UP.SEAL to the value of the object SEAL. From now on, if the state of the object changes, the message displayed by

   say pick.up.seal

will automatically change to match, because PICK.UP.SEAL contains a two-component text switch.

Yes, in this particular case one could use SEAL as an explicit qualifier:

   say pick.up.seal, seal

The seal example merely illustrates the technique. It is too simple to show the justification of that technique. The actual motivation for introducing text tying in A-code was provided by my efforts to resolve a highly complex problem of constructing a location description which depended on several independent factors. It is far too complex to be presented as an illustrative example.

Text nesting

Sometimes a number of separate messages (e.g. location descriptions) consist of a part which is common to them all, and another part which is specific to a given message (or group of messages). This can present maintenance problems (e.g. fixing a typo in all identical parts) and is also wasteful. A-code offers an alternative approach. The common message part can be defined as a separate text fragment (i.e. a text without a trailing end-of-line character), which can be "nested" within individual messages.

An A-code message text may include the symbolic name of another text enclosed in unescaped braces (i.e. curly brackets): {}. When the message is displayed, this construct gets replaced with the text indicated by the text name within the braces. The nesting mechanism is recursive, so the nested text may have further texts nested within it.

Here's an example from Adv770, showing the declarations of the first six of the Adv550's ice tunnels. It uses double-level nesting.

   You are in an intricate network of ice tunnels.
   {INTRICATE.TUNNELS} The only exit is
   {ICE.TUNNELS} north and west.
   {ICE.DEAD.END} south.
   {ICE.TUNNELS} north, east and west.
   {ICE.TUNNELS} north and south.
   {ICE.TUNNELS} north and east.
   {ICE.DEAD.END} south.

Nested texts may have their own implicit qualifiers, of course. If the top level text is used with an explicit qualifier, this is passed on as an explicit qualifier to all nested texts, to any depth.

As a special case, A-code also permits nesting of the ARG1 and ARG2 variables, which are treated as the words actually typed by the player (possibly expanded from an abbreviated state, and typo-corrected). This is used, for example, as follows:

      You {ARG1} the {ARG2}.

If the player typed (e.g.) "G LAMP", and succeeded in picking up the lamp,

   say you.do.it

would display "You get the lamp."

Appendix A: A-code text-handling primitives

say <text_name>, [<qualifier>]
Display the named text with an optional explicit qualifier.
resay <text_name>, [<qualifier>]
Like say, but completely replaces any text output accumulated since the last prompt.
append <text_name>, [<qualifier>]
Like say, but strips off any end-of-line characters at the end of any text output since the last prompt, before displaying the named text.
quip <text_name>, [<qualifier>]
Like say, but having displayed the text, aborts all further processing and jumps to the top of the main loop (i.e. equivalent to a say immediately followed by a quit).
respond <vocabulary_word> [...] <text_name>, [<qualifier>]
Like quip but executed conditionally -- only if the player's last command contained any of the listed vocabulary words.
smove <location> <text_name>, [<qualifier>]
An amalgam of say and move, equivalent to saying the specified text and then moving the player to the nominated location.
vocab {<object>|<place>|<verb>}, {<place>}, {<flag>}, {<text>}

This can be thought of as a wrapper for the SAY directive, which is used to display game's vocabulary in a context sensitive manner (e.g. omitting any nouns referring to objects the player has not yet seen). See a separate document dealing with A-code vocabulary handling.

Appendix B: Special characters in text definitions

The following characters are have special meaning in text definitions, and have to be escaped with a reverse slash if they are to be displayed "raw".

\ (reverse slash)
A universal logical escape character. Any immediately following character other, including the reverse slash itself, but excluding end-of-line, is treated as a literal character with no special meaning.
$ (dollar)
A value place-holder, replaced by the current value of the explicit qualifier
# (hash)
A word place-holder replaced by the primary word associated with the explicit qualifier.
/ (forward slash)
If found as the first non-blank character on a line, the forward slash is replaced by a line feed, except if it is the very first non-blank character of the whole text definition, in which case it issimlpy removed. Any blanks immediately following it are not stripped off. If a forward slash is found within a text switch, it delimits text switch components. Not special otherwise.
[ and ] (square brackets)
Signal the beginning and the end respectively of an embedded text switch.
{ and } (curly brackets or braces)
Enclose the symbolic name of a nested text.
< and > (angle brackets, or less-then and greater-then)
These enclose HTML tags. All tags are quietly removed in non-HTML modes, echoed as they are otherwise.
+ (plus sign)
If found as the first non-blank character of a line, signals an individually centered line. Not special otherwise.
= (plus sign)
If found as the first non-blank character of a line, signals a line of a centered block. If found as the single character constituting a text switch component, represents a back reference to the previous component. Not special otherwise.
_ (underscore or underline)
A forced blank, not removed by the line-trimming mechanism

Appendix C: Handling anonymous texts (deprecated!)

Anonymous texts can be only handled through pointers. Count text declarations backwards from the anonymous text in question, until you come to a named text. The resulting count is the anonymous text's offset from that named text. Point a variable at that named text and then add the offset to the variable. The variable now points at your anonymous text and all A-code primitives which handle automatic indirection, will accept the variable as a reference to the anonymous text.

Here's an example in the shape of a complete A-code test program:

   style A-code 12
      First text
      Second text
      Third text
      local ptr
      lda ptr, first.text
      add ptr,2
      say ptr

It will print "Third text" and then stop. (NB: The repeat section is null, but the translator will complain if it is absent.)

Back to the documentation index
To the Mipmip home page
Feel free to leave a comment!
Mike Arnautov (27 March 2023)