What would you like to learn next?
Game-State Formulas
Game-State Formulas
Set by formula lets a chapter calculate a Number, Percent, or Age when a reader enters it. A formula can combine current Game State values, arithmetic, conditions, rounding, and random results. Enter the formula in Changes to game state on the chapter form, not in the chapter's text. The calculation needs an active game and does not place a visible control in the chapter.
Build your first formula
Suppose a shop should subtract an item's discounted price from the reader's money. First create three Number game variables as described in Game State Variables and Inventory, with these initial values:
money= 100item_price= 30discount= 5
Then edit the shop chapter:
- Turn on Changes to game state.
- Choose the
moneyvariable. - Choose Set by formula.
- Enter
game:money - (game:item_price - game:discount). - Save the chapter and test it in a fresh game.
The formula calculates 100 - (30 - 5), so the reader enters the chapter with money set to 75. A formula may read the same value it changes, as this one does, but that value must already exist in the current game.
Values a formula can use
Write game:name to read a game variable. The name and capitalization must exactly match Story variables.
- Number, Percent, and Age values can be used as numbers.
- Yes/No values can be used with
true,false, comparisons,AND,OR,NOT, andIF. - Text, Dropdown, and reader variables cannot be used in a formula.
- The completed formula must return a number, even when it uses a Yes/No check.
- Hidden Game State values remain available to formulas; a hidden value is different from a missing or removed value.
Every referenced name must be a known Game State variable when the chapter is saved. During play, every reference that the formula actually calculates must exist in the reader's current game. Define important inputs with starting values so a fresh game has them. A variable added to the story after a reader began playing can still be missing from that existing game until the reader resets it or a chapter creates the value. If an earlier chapter can remove a value, recreate it before the formula. Another option is to use a separate, existing flag with IF, AND, or OR so the branch containing the possibly missing reference is skipped.
Useful formula patterns
These examples use common game-story tasks. Replace their variable names with the exact names from your story:
| Task | Formula | What it does |
|---|---|---|
| Keep health in range | CLAMP(game:health + game:healing; 0; 100) |
Adds healing, but never returns less than 0 or more than 100 |
| Give a conditional bonus | IF(game:has_bonus = true; game:score + 10; game:score) |
Adds 10 only when has_bonus is Yes |
| Divide safely | IF(game:item_count = 0; 0; ROUND(game:total / game:item_count)) |
Returns 0 for an empty group and otherwise returns a rounded average |
| Make a random skill check | IF(DICE(20) + game:bargain_skill >= 20; 5; 0) |
Returns 5 when the die result plus skill reaches 20, otherwise 0 |
IF calculates only the selected result. In the safe-division example, the division is not attempted when item_count is 0. AND and OR also stop once the result is known, so a skipped right side is not calculated.
Changes run from top to bottom
A formula sees the results of change rows above it. Suppose one row sets bonus to 5 and the next formula adds game:bonus to score; that formula sees 5. Use Move Up and Move Down to put dependent changes in the required order.
After each row, CHYOA keeps the result within the target type's range: Number -100000 through 100000, Percent 0 through 100, and Age 18 through 150. The next row sees that adjusted value.
CHYOA checks formula syntax, variable names, and compatible types when the chapter is saved. Some problems depend on the reader's current game, such as a missing value, division by zero, a fractional final result, or calculated random bounds in the wrong order. If any formula cannot be completed when the reader enters the chapter, the reader sees a Game State calculation error, none of that chapter's change rows are kept, and the previous Game State remains intact. If the failed calculation is on the first chapter, CHYOA leaves Game Mode for that attempt so the reader is not trapped there.
Arithmetic and checks
| Syntax | Meaning |
|---|---|
+, -, * |
Add, subtract, and multiply |
/ |
Divide exactly; round the result when it may be fractional |
// |
Divide and round down to a whole number |
% |
Find the remainder using the same round-down division |
^ |
Raise the left value to a whole-number power |
( ) |
Group a calculation and make its order clear |
=, ==, !=, <>, <, <=, >, >= |
Compare two numbers, or compare two Yes/No values with equality |
AND, OR, NOT |
Combine or reverse true-or-false checks |
The usual arithmetic order applies: powers, multiplication and division, then addition and subtraction. Powers group from right to left, so 2 ^ 3 ^ 2 means 2 ^ (3 ^ 2). Use parentheses whenever the intended order might not be obvious.
Write formula logic with the words AND, OR, and NOT. The &&, ||, and ! aliases accepted by some chapter conditions are not formula syntax.
Decimal values may use a point or comma, so 1.5 and 1,5 mean the same exact value. Function arguments are always separated with semicolons, not commas.
Formula functions
Function names are not case-sensitive.
| Function | Result |
|---|---|
IF(check; when_true; when_false) |
Uses one result when the check passes and the other when it does not; only the selected result is calculated |
MIN(value; ...) |
The smallest of one or more numeric values |
MAX(value; ...) |
The largest of one or more numeric values |
ABS(value) |
The value without a negative sign |
ROUND(value) |
The nearest whole number; halfway values round away from zero |
ROUND(value; places) |
The value rounded to the requested decimal place, from -12 through 12 |
FLOOR(value) |
The next whole number at or below the value |
CEIL(value) |
The next whole number at or above the value; CEILING also works |
CLAMP(value; minimum; maximum) |
The value kept between the supplied minimum and maximum |
DICE(sides) |
A whole number from 1 through sides, including both ends |
RANDOM(minimum; maximum) |
A whole number between the two bounds, including both ends |
Whole-number results
The completed formula must produce a whole number. game:total / 4 works only when that division has no remainder. When a fraction is possible, choose the behavior the story needs:
ROUND(game:total / 4)uses the nearest whole number.FLOOR(game:total / 4)rounds down.CEIL(game:total / 4)rounds up.
Rounding is needed only before the completed result is saved. A formula may use exact fractions in the middle of a longer calculation.
Random results inside formulas
Use a random function when the result also depends on Game State or needs custom bounds:
DICE(20)chooses a whole number from 1 through 20, including both ends.RANDOM(-2; 2)chooses -2, -1, 0, 1, or 2.game:score + DICE(6)adds a six-sided result to the current score.RANDOM(game:minimum; game:maximum)uses current numeric game values as inclusive bounds.
Each call makes a separate choice. DICE(6) + DICE(6) therefore produces 2 through 12, with results near 7 occurring more often.
DICE needs a whole-number side count from 1 through 1000000. RANDOM needs whole-number bounds from -1000000 through 1000000, and its minimum cannot be greater than its maximum.
If no calculation is needed and a fixed upper bound is enough, use Random Values in Chapter Changes. For a reader-controlled roll button, use Dice Rolls. For a themed choice from written outcomes, use Random Draws.
Fix common formula problems
| Problem | What to check |
|---|---|
| Unknown variable while saving | Match the name and capitalization shown on Story variables |
| Missing value while playing | Test an existing game as well as a fresh one; reset the game or create the value on an earlier route |
| Formula returns a fraction | Use ROUND, FLOOR, or CEIL for the completed result |
| Division by zero | Use IF to choose a result that does not perform the division when the divisor is 0 |
| Function arguments are rejected | Separate them with semicolons, such as MIN(2; 5), rather than commas |
| A range comparison is rejected | Write two complete checks, such as game:score > 0 AND game:score < 10; do not write 0 < game:score < 10 |
CLAMP is rejected while playing |
Put the minimum before the maximum |
| A value has the wrong type | Use only numeric game values for calculations and Yes/No game values for true-or-false checks |
Formula limits
Most story calculations stay well below these limits:
| Limit | Maximum |
|---|---|
| Formula length | 4096 bytes; ordinary formula characters use one byte each |
| Formula pieces, including values, names, operators, separators, and parentheses | 512 |
| Nested expressions | 32 levels |
| Arguments in one function call | 32 |
| Calculated steps during one use | 1024 |
| Digits after a decimal separator | 12 |
| Power | -32 through 32 |
| Exact whole or fraction component during a calculation | 1000000000000000 in either direction |
DICE sides |
1 through 1000000 |
RANDOM bounds |
-1000000 through 1000000 |
What the reader experiences
The change happens before the chapter is shown. The reader sees its effect in chapter text, Game State, inventory, conditions, and follow-up choices, but does not see the formula itself.
Going Back past the chapter restores the earlier Game State. Entering the chapter again runs the formula again and makes new choices for every DICE or RANDOM call.
Before publishing
Test the chapter with both a fresh game and an existing game. Check that:
- every referenced value exists on each route that can reach the chapter;
- change rows are in the required order;
- division, rounding, minimums, and maximums behave as intended;
- when the formula uses
DICEorRANDOM, its smallest and largest results fit the story; - Percent and Age results stay meaningful at their supported limits;
- going Back and entering the chapter again gives the expected result.
Older game-variable names
New variable names use letters, numbers, and underscores, so game:name is the normal formula form. An older story may have a game-variable name with spaces or punctuation. Put that exact name in double quotes inside brackets:
game["Money on hand"] - game:price
Inside the quoted name, write \" for a double quote and \\ for a backslash. Braces and control characters are not allowed. Keep a working older name unchanged unless the story is being carefully revised.
You've reached the end of this Guide topic.
- No further chapters
0 comments
No comments yet
The story has no discussion yet. Leave a note here when a branch gives you something to say.
No chapter comments yet
No one has commented on this branch yet. Add the first note above.