8 releases
0.4.1 | Jul 23, 2021 |
---|---|
0.4.0 | Jul 23, 2021 |
0.3.0 | Mar 4, 2020 |
0.2.1 | Feb 4, 2020 |
0.1.2 | Feb 4, 2020 |
#737 in Game dev
Used in storylets
190KB
5K
SLoC
Throne
A game scripting language for prototyping and story logic:
// Declare the initial state as 'phrases', with one phrase per line.
Mary is sister of David
Sarah is child of Mary
Tom is child of David
// Define rules with the format: INPUT = OUTPUT.
CHILD is child of PARENT . AUNT is sister of PARENT .
COUSIN is child of AUNT = COUSIN is cousin of CHILD
// The final state will be:
// Sarah is cousin of Tom
Motivation
The inspiration for Throne comes from languages used to author interactive fiction, such as Inform. As described in this article, Inform can be used to prototype games from genres besides interactive fiction. By defining gameplay through rules, much of the verbosity of general purpose programming languages can be avoided. However, Inform and other interactive fiction authoring systems are too slow to execute their rules in every frame of a real-time gameplay loop and are difficult to embed in an existing engine.
Throne allows gameplay logic to be defined through rules and so provides some of the benefits of a rule-based language like Inform, but is also fast to execute and easy to embed in an existing engine. Its syntax is strongly influenced by the Ceptre programming language.
Reference
Rules are of the format INPUT = OUTPUT
, where INPUT
and OUTPUT
are lists that use period (.
) as a separator between items:
INPUT
is a list of one or more conditions that must pass for the rule to be executed. The conditions can either be state phrases that must exist or predicates (such as>
using prefix notation) that must evaluate to true. Any matching state phrases are consumed by the rule on execution.OUTPUT
is a list of state phrases that will be generated by the rule if it is executed.- Identifiers in rules that use all capital letters (
CHILD
,PARENT
,AUNT
andCOUSIN
in the snippet above) are variables that will be assigned when the rule is executed.
Evaluating a Throne script involves executing any rule that matches the current state until the set of matching rules is exhausted. Rules are executed in a random order and may be executed more than once.
Predicates
The following predicates can be used as one of the items in a rule's list of inputs:
Syntax | Effect | Example |
---|---|---|
+ X Y Z |
Matches when the sum of X and Y equals Z |
+ HEALTH 10 HEALED |
- X Y Z |
Matches when the sum of Y and Z equals X |
- HEALTH 10 DAMAGED |
< X Y |
Matches when X is less than Y |
< MONEY 0 |
> X Y |
Matches when X is greater than Y |
> MONEY 0 |
<= X Y |
Matches when X is less than or equal to Y |
<= MONEY 0 |
>= X Y |
Matches when X is greater than or equal to Y |
>= MONEY 0 |
% X Y Z |
Matches when the modulo of X with Y equals Z |
% DEGREES 360 REM |
= X Y |
Matches when X equals Y |
= HEALTH 100 |
!X |
Matches when X does not exist in the state |
!this does not exist |
^X |
Calls the host application and matches depending on the response | ^os-clock-hour 12 |
When a predicate accepts two input variables, both variables must be assigned a value for the predicate to produce a match. A value is assigned either by writing a constant inline or by sharing a variable with another of the rule's inputs.
When a predicate accepts three input variables and one of the variables remains unassigned, it will be assigned the expected value according to the effect of the predicate e.g. A
will be assigned the value 8
in + 2 A 10
.
Constructs
Special syntax exists to make it easier to write complex rules, but in the end these constructs compile down to the simple form described in the introduction. The following table lists the available constructs:
Syntax | Effect | Example | Compiled Form |
---|---|---|---|
Input phrase prefixed with $ |
Copies the input phrase to the rule output. | $foo = bar |
foo = bar . foo |
A set of rules surrounded by curly braces prefixed with INPUT: where INPUT is a list of phrases |
Copies INPUT to each rule's inputs. |
foo . bar: { |
foo . bar . hello = world |
<<PHRASE . PHRASES where PHRASE is a single phrase and PHRASES is a list of phrases |
Replaces <<PHRASE with PHRASES wherever it exists in a rule's list of inputs. |
<<arithmetic A B . + A 1 B |
foo . + X 1 Y = Y |
The ()
atom
Finally, ()
is the absence of state. When present in a rule's list of outputs it has no effect, besides making it possible to write rules that produce no output (e.g. foo = ()
).
When present in a rule's list of inputs it has the effect of producing a match when no other rules can be matched. For example, in the following script the first rule will only ever be matched last:
foo // initial state
() . bar = () // matched last
foo = bar // matched first
In this way ()
can be used as a form of control flow, overriding the usual random order of rule execution.
Examples
- blocks - a simple tile matching game run with
cargo run --example blocks
. - throne-playground - a web-based editor for Throne.
- Urban Gift - uses Throne for gameplay logic.
Build for Wasm
- Run
cargo install wasm-pack
to install wasm-pack. - Run
npm install ; npm start
in this directory.
Dependencies
~5–7MB
~128K SLoC