Skip to content

Math, Loops & Expressions

Arithmetic, comparison, loop control, and expression evaluation in passage logic.

Reference: Loops, Arrays, Numbers & Strings and Conditionals.

Arithmetic

Anywhere a value is expected (inside (set:), conditions, or another macro's arguments), you can do math:

ana
(set: _dmg to ($player.str * 2) + 5)
(set: _half to (floor: $player.gold / 2))
(if: $turn % 2 is 0)[An even turn.]

Operators: + - * / % and unary minus. Precedence, tightest first: unary minus → * / %+ - → comparisons (is, >, …) → notandor. Parenthesize when in doubt. / is floating-point (7 / 23.5); wrap in (floor:), (ceil:), or (round:) for whole numbers. + also concatenates when either operand is a string: "HP: " + $player.hp.

and / or short-circuit (the right side is skipped when the left already decides the result), so (if: $has_key and (check-lock:)) is safe even when the right side is expensive.

Picking a value instead of branching

When you want to choose a value rather than emit a block of content, (cond:) is shorter than a chain of (if:)/(elseif:)/(else:). It takes condition/result pairs and returns the first result whose condition is true, with an optional trailing default:

ana
(set: $status to (cond: $cash >= 1000, "loaded", $cash >= 500, "stable", "broke"))
Your (cond: $wonTheRace, "gasps of triumph", "wheezes of defeat") drown out all other noise.

Polymorphic helpers

Several utility macros accept either a single array or a series of inline values, so you don't need a different macro depending on where your data lives: (sort:), (shuffle:), (pick:), (sum:), and (avg:). (length:) likewise spans strings (character count) and arrays (entry count), and (join:) concatenates strings or merges arrays. The longer arr-* / str-* names still work as aliases.

Loop control

(each: _x in collection)[...] repeats its body once per element. Inside the body:

  • (break:) stops the loop immediately.
  • (continue:) skips to the next element.

Both target the innermost loop and work inside (if:) branches, which is handy for "find the first match" scans:

ana
(each: _id in (ids: $inv))[
    (if: (get: $item, _id, "type") is "weapon")[
        (set: $world.drawn to _id)
        (break:)
    ]
]

Defining your own macros

When you find yourself repeating the same little cluster of macros (a styled name plate, a health bar, a "can the player afford this?" check), give it a name with (macro:). You compose existing macros once and call the result everywhere. Define them in GameInit so they exist before any passage runs.

A content macro emits whatever its body renders. Parameters are bare names, available in the body as _temps:

ana
(macro: "healthbar", current, max)[
    (meter: _current, _max)
]
(healthbar: $player.hp, 100)
(healthbar: $rival.hp, 100)

A value macro computes something you use elsewhere; mark it by adding a trailing return to the definition. The macro then hands back the value of its last expression (it doesn't draw anything), so you can use it inside (set:) or (if:). The last line must be an expression-producing macro call, and that value is what comes back:

ana
(macro: "can-afford", cost, return)[
    (cond: $player.gold >= _cost, true, false)
]
(if: (can-afford: 50))[The merchant smiles. "A fine choice."]

Parameters can have defaults (a literal right after the parameter), redefining your own macro replaces it, and you can't shadow a built-in. When a macro needs logic the language genuinely can't express, like shuffling a deck or running a canvas minigame, reach for JavaScript instead; see Extending with JavaScript. The full rules and more examples live in the (macro:) reference.