Writing:The Ways of Printing

Most of what Seltani does is display text, so the text-display code is rather complicated. (Some people would say, over-complicated. I would not disagree with those people.)

The most familiar form of text is the  property; we’ve seen how these are used for room descriptions and object close-up views. You can put various kinds of formatting in a text property, using square-bracket markup.

However, there are other ways to generate text. The most obvious is a  property containing a string (that is, a value in quotes or double-quotes). This behaves just like a  property, except that square brackets have no special meaning. properties use Python syntax, so you may use backslashes to escape quotes and other special characters.

You can also write a  property which contains   statements. In general, a  property

...is equivalent to a  property:

print("You see stuff.")

"One important distinction: if you name a property on a line by itself, or as a link target, it is displayed in the focus window. If you name a   property this way, it is not focussed; it runs immediately. To set a   property as the focus, do:."

Again, square brackets have no special meaning in Python string literals. So this will not do what you want:

print("You see stuff.[$para]Does not produce a new paragraph.")

However, there are function equivalents to all of the special text operators. So you might do this:

print("You see stuff.") parabreak print("This is a ") style("emph") print("new") endstyle("emph") print(" paragraph.")

If you print several items in a single line, they are separated by spaces:

print("You", "see", "stuff.")

You can change or remove this separator by passing a  argument:

print("You", "see", "stuff.", sep="/")

(However, see the note below about whitespace. Passing a Python newline as a separator will not generate a visible line break, because Seltani collapses whitespace.)

There’s one more way to generate text: the  property. But this is sufficiently complicated to warrant its own section.

A note about Unicode and whitespace
Seltani uses Unicode text throughout. This means that you do not need special escape sequences to generate accented or foreign-language characters. You may type any character you want directly into the world-building interface, and it will appear correctly in the world.

"The D’ni alphabet is an exception, because it has no standard Unicode representation. For D’ni characters and digits, use the interpolation in, or   calls in."

Whitespace is collapsed according to the usual HTML rules. Any number of space, tab, and newline characters are collapsed together into a single space. To generate a paragraph break, use the  interpolation in a   property. As a special case, a blank line (double line break) in  is also treated as a paragraph break.

GenText: procedural text generation
A  property contains code that generates text. However, the code language is rather different from the code you’ve seen so far.

To demonstrate this, open the world editor and create a  property that looks like this (with the quote):

"You see a thing."

Use this as a link, and you’ll see the focus window pop up, just as if you’d created a  property. (Except for the quotes.) So why is this interesting?

Here’s a convenient way to chain together several values:

Seq("You see ", 1, " thing.")

Okay, that’s still not very interesting -- the  operator strings several bits of text together. You could do the same with a  property:

But here’s a new trick:

Seq("You see a ", Alt("blue", "green", "red"), " thing.")

The  operator selects one of its arguments at random -- or rather, not at random. The choice is always consistent based on the structure of  property and the identity of the instance you’re in. If you release an Age with this property in it, players in a given instance will always see the same value. (Technically this is a deterministic pseudo-random generator with the instance ObjectId as the seed.)

The syntax is simply a Python expression, made of nested function calls. That can be a bit verbose, however. You can simplify it a little. Square brackets can be used for the  operator, and parentheses for the   operator. So the above can be rewritten:

["You see a ", ("blue", "green", "red"), " thing."]

The value of this becomes more apparent when you begin building  on top of each other. For example, you could define one named :

("blue", "green", "red")

And then make use of it:

["You see a ", color, " thing."]

By piling these together, one can construct a complex, variable piece of text which will always be consistent within a given instance. This is a powerful tool for customizing Ages.

There are several other  operators. The most interesting is :

Shuffle("blue", "green", "red")

This does a (pseudo)random selection like, but the choice will never repeat until all choices are run through, and it will never repeat twice in a row. If you define the  property this way, you could make use of it like this:

["You see a ", color, " and ", color, " thing."]

This might produce “You see a red and blue thing,” but never “You see a blue and blue thing.”

Cooked text output
The examples above have been careful to add extra spaces around words, so that sequences don’t run together. As you write larger  structures, spaces become more of a nuisance. You need to worry about proper capitalization and punctuation as well; a phrase might occur at the beginning or end of a sentence.

The printing system can carry this load for you, although you need to explicitly turn it on. In this mode, spaces are automatically inserted between strings; capital letters appear whenever you apply a period. We call this “cooked mode” -- in contrast to the default “raw mode”, which leaves spacing and capitalization alone.

Create a room in the build interface and set its  property to:

Then create a  property named  :

gentext.display(scenery, cooked=True)

Finally, create a  property named  :

["you see", "a thing"]

This is all lower-case with no extra spaces or punctuation. However, it appears in the room description as “You see a thing.” That’s cooked mode.

Here’s a more ornate example:

["you see", A, color, "thing", STOP, color, "confetti rains down"]

The  token generates “a” or “an” (so you’d see “a red thing” or “an orange thing”, as appropriate). is a period, followed by capitalization. So the result could be “You see a green thing. Red confetti rains down.”

Other break tokens include,   (semicolon), and   (paragraph break). Redundant break tokens are merged, so you can use  at the end of one phrase and at the beginning of another without worrying about double punctuation. Similarly, if  is followed by   (or vice versa), the comma is dropped.

Cooked mode does not change the spacing and capitalization behavior inside  properties -- it assumes that you’ve already formatted the   the way you want it. It also does not apply between the terms of a compound  call. However, it does apply to the beginning and end of a  call.

Custom seeds
We’ve said that the random selection of  or   is based on the instance ID. In some cases, we might want it to be based on some other variable. (For example, the scenery in the Age of Fleuven is based on a counter that increases over time.)

To customize this, use the  function again:

gentext.display(scenery, cooked=True, seed=5)

You can set the seed to any numeric, string, or ObjectId value. If you use a constant, as in this example, the random choices will be fixed everywhere, for every instance. If you use an instance variable, you can cause the choices to change by changing the variable value.

A warning: the pseudo-random generator combines the seed, the  property name, and the property structure to make its choices. This means that if you add new elements to a, the entire output may change! There is no guaranteed way to add a new chunk while keeping the rest of the generated text the same.

This would not matter for Fleuven, where the scenery changes periodically anyhow; players are unlikely to notice. However, if you design an Age with a  sculpture in it, players might be annoyed if their personal instance sculpture changes. In these cases, it is best to leave the  structure unchanged after you release the Age.