ec2-44-220-44-148.compute-1.amazonaws.com | ToothyWiki | ToothyGDL | RecentChanges | Login | Webcomic


A game logic definition consists of:

The runtime interpreter does the following:

The display section describes a set of page elements (currently TABLE cells, but this is an implementation detail which doesn't greatly matter), possibly with content/css, each of which has zero or more mappings between it and action labels, and may have content/formatting predicated on whether any of the mapped labels curently exist in the set of actions the player is permitted to take (the predicates are reevaluated whenever the user performs an action).

This should be sufficient for most board/card games, and for some puzzles such as Solitaire.


Game Logic Syntax

C-style comments are supported throughout the code (C-style escapes are *not*, however; use HTML entities instead.)

logic block contains a list of statements
each statement is a variable declaration or a game rule declaration
A variable may be declared as an array or an integer; each variable must be fully initialised on declaration. The syntax for declarations is
 optional-qualifier int identifier { integer expression }
optional-qualifier array identifier { comma-separated list of integer expressions }
optional-qualifier may or may not be present. Currently, it can only be "local". A local variable is not stored in saved games or links or transmitted via chat; its contents is preserved over game loads.

A game rule declaration is either a for loop containing a list of game rule declarations, or a game rule

A for loop has the syntax
 for identifier ( minimum .. maximum ) { list of game rule declarations }
It evaluates once for each integer in the range minimum to maximum inclusive, creating an integer variable identifier, valid within the loop body, that has the correct value for each pass. minimum and maximum are integer expressions.

A game rule has the syntax
 when { boolean expression } allow { list of semicolon-terminated assignments } label { list of string expressions }
Whenever the boolean expression is true, any integer expressions within the label part are evaluated using the world state at the time, all elements are concatenated and a binding between the resulting string and the assignment block created. If multiple rules bind to the same string, all allowed actions will take place when the user selects that string.

A string expression is either a text string enclosed in double quotes, or an integer expression (which is evaluated and converted to a string)

Lists of assignments in the allow block may contain for and if statements, which may contain further lists of assignments.

An assignment has the syntax
 lvalue assignment-operator integer expression
where an lvalue is either the name of an integer variable, or the name of an array followed by an integer expression in square brackets to give the particular element; assignment-operator may be =, += or -=.

The supported integer operators are
 + - / * %
with their usual meanings; also, brackets and the bitwise logical operators ^, & and |. Pre/post-increment and decrement (++, --) and unary plus / minus are also permitted. Boolean operators are and, or and !.
Integer expressions in variable declarations are evaluated immediately; referencing variables that have not yet been defined in a variable declaration body produces a compiler error.

NB. operator precedence is not implemented at this time; use brackets to set explicit precedence.
NB. the for() loops execute once, at initialisation time, to generate the full ruleset; the local context is remembered, and each rule is then reevaluated whenever the user takes an action. Therefore, the integer expressions used for the min/max values should not rely on global state being anything other than what you set it to initially.

Display code syntax:
The display block contains a list of statements. If the display block produces no display elements during initialisation, the debug menu will be enabled automatically.

Each statement is either a stringtable declaration or a display element imperative. The syntax for stringtable declarations is
 stringtable identifier { comma-separated list of whitespace-separated lists of string expressions }

A display element imperative is either a for loop containing display element imperatives (syntax/caveats as for logic), an if statement (evaluated at initialisation time, like the loops) or a display element declaration.

A display element declaration declares either a newline element, a box or a sprite.

The syntax for a newline element is

The syntax for a box is
 box { list of formatting commands }

The box formatting is reset and list of formatting commands reevaluated every time the set of allowed actions changes. If multiple instances of the same command are encountered while evaluating box formatting, the last one is the one that takes effect.
A formatting command is one of the following:
 width { css }
height { css }
border { css }
margin { css }
padding { css }
background { css }
position { list of position-related css }
content { list of string expressions, which may include string table lookups }
bind_action { list of string expressions, which may include string table lookups }
while_active { list of formatting commands }
..or a for or if statement containing a list of formatting commands.

position likewise appends CSS; the block contains a semicolon-separated list of any or all of:

width, height, border and background append the appropriate CSS to the element's STYLE property. css may contain lengths, colours and border styles. Lengths may contain integer expressions, and colours may also contain integer expressions if given in the rgb(a,b,c) form.

content replaces the current content of the box with the result of evaluating and concatenating all the supplied expressions.

bind_action evaluates/concatenates all supplied string expressions to generate a label. If the label exists in the set of allowed actions that resulted from evaluating game logic against current state, that action's assignment block is bound to the box's onclick property. If multiple labels get bound in this way, all their actions will be executed on click, in the order in which they were bound.

while_active executes the formatting commands supplied if and only if the box has at least one bound action that was allowed at the time while_active was encountered (that is, you can have several bind_actions, then a while_active that acts based on those, then some more binds, then another while_active that decides based on the lot, etc..).

The syntax for a sprite is
 sprite { list of formatting commands }
where the formatting commands are the same as for a box, except that mouse handlers will not attach to sprites, and bind_action/while_active are forbidden.
Sprites are maintained in a separate DOM block from boxes, improving performance if they animate regularly; they are currently implemented using DIVs.

The keyword repeatedly is also allowed in the display section. The syntax is
 repeatedly { list of string expressions }
where list of string expressions is defined as for label. Several times per second (currently trying for ~10-15fps), the list of string expressions is resolved and any actions bound to the resulting text performed. All sprites are redisplayed, and any boxes for which the style/content has changed are also redisplayed. Notice that MSIE will fail to register clicks within 250ms (often more) of a box being redisplayed, so try to arrange things so you don't need to do that during a game.

Optimisation tips

Premature optimisation is the root of all evil. Write the game first, optimise once everything works. That said,
(todo: more here)

Notes on Implementation

The minilanguage is "compiled" to a set of operations which are executed on a virtual stack machine.
The compiler contains an emulator for the stack machine, allowing it to precompute constant expressions. It does not emulate some of the opcodes, notably the display-related ones.
The runtime engine performs a small amount of dynamic optimisation on some of the operations. The functions for some operations, when executed, do some work, capture the result in a closure, and replace the call to them within the caller to a call of the closure, so that the work does not have to be repeated.
If a repeatedly{} section is present, and debug mode is not active, all UI events are queued in a FIFO and processed inside the timer handler. If no such section is present, the timer is not started and UI events are handled directly in onclick(). Any suggestions for improvements to the way the timer is implemented are welcome.

ec2-44-220-44-148.compute-1.amazonaws.com | ToothyWiki | ToothyGDL | RecentChanges | Login | Webcomic
Edit this page | View other revisions | Recently used referrers
Last edited January 25, 2006 10:57 am (viewing revision 6, which is the newest) (diff)