dbx manual
7. The rules engine
I won't lie to you here ... the rule syntax is quite complicated. But this complexity gives rise to powerful functionality in a compact and flexible format. I honestly couldn't think of a way to make it simpler without also making it extremely verbose.
Please note that this page does not document the
API
event onruletest
, which fires whenever a rule is tested;
for information about that, please see
API Events
- Pre-requisites
- Creating rules
- Deleting rules
- Basic rule syntax
- Extended rule syntax
- Syntax reference
Pre-requisites
To use the rules engine, you must have the dbx.rules.js
codebase
included on your page:
<!-- dbx rules engine -->
<script type="text/javascript" src="dbx.rules.js"></script>
It can go anywhere after the main dbx script, but before your configuration script.
The rules engine only works for two-dimensional orientations,
ie. it will not work at all if the
orientation
is set to "vertical"
or "horizontal"
. To avoid confusion,
the setRule()
method validates your orientation
and will throw an exception if it's invalid.
Apart from that, there are no specific limitations on how your implementation is configured.
But there are a couple of design considerations to bear in mind, which are that the rules engine works best on boxes which are all approximately the same size, and which are all roughly square.
Creating rules
Rules are created using the addRule()
method of a group, calls
to which must obviously come after your original group constructor.
A simple rule definition looks like this:
purple.setRule("N, S");
In that example, we've defined the rule "N, S"
, which means
that boxes can only move according to the pattern move north, then move south
.
Using a single argument in setRule()
creates a global rule,
that is, a rule which applies to all boxes. You can only create one
global rule, and any subsequent global rule definitions will overwrite
the previous one. But you can also create additional rules which only apply to
specific boxes, by specifying a box class
as the
second argument to setRule()
:
purple.setRule("N, S", "foo");
That rule will apply to any box which has the value "foo"
as
part of its class
name (for example, a box with the overall
class
attribute "dbx-box foo"
.
As with the global rule, if you create a rule using a class
name
that already has a rule defined for it,
the new rule will overwrite the previous one.
Deleting rules
The companion method, for deleting rules, is called removeRule()
. If no argument
is specified then it will delete the global rule; if a class
is specified then it will delete any rule that was defined for that class
:
purple.removeRule("foo");
Basic rule syntax
The basic direction tokens are points on a compass; these tokens must be uppercase:
N |
the box can move north |
---|---|
E |
the box can move east |
S |
the box can move south |
W |
the box can move west |
You can also define diagonal directions; these tokens must be an uppercase letter then a lowercase letter:
Ne |
the box can move north-east |
---|---|
Se |
the box can move south-east |
Sw |
the box can move south-west |
Nw |
the box can move north-west |
And there's one more directional token, which is a wildcard:
* |
the box can move in any direction |
---|
You can string any number of directional tokens together for multiple choices. So for example, this rule definition:
"NESW"
Means that a box can move north, east, south or west; in other words, the box can move in a straight line, but not a diagonal.
Here we also see an example of why case is important in defining compass
directions — otherwise how is the rules engine supposed to distinguish
"SE"
(meaning south or east
) from "Se"
(meaning south-east
). In fact case is enforced — if it's not used properly
the rules engine will throw an exception.
Note that diagonal directions do not need to be the exact compass angles they're
described as, for example, north-east
does not need to be 45°
.
Any line which is more than nominally off a perfect straight line will be considered
a diagonal. A further implication of this is that, if you expect a rule to match
a perfect straight line, you must ensure that the blocks sit in a perfect straight line;
only a few pixels' discrepancy will be considered a diagonal.
A single rule can define a sequence of moves, not just one; multiple definitions within a single rule are referred to as patterns. Wherever a rule defines more than one pattern, these are separated with commas:
, |
seperate multiple patterns within a single rule |
---|
The purpose of patterns is to define how a box can move over time, allowing you to describe a path that takes several moves to traverse. So for example, the following rule definition:
"N, EW, S"
Means that a box can move: north; then east or west; then south — a rule which defines three moves in total, because it has three separate patterns.
Okay.
So far we've looked at rules which only describe direction, they don't limit the distance — every rule we've defined so far allows for movement in a specified direction for any number of blocks. A block is the distance covered by a single box, so if we had 16 boxes laid out in a square, we would have four blocks distance along each side.
You can limit the number of blocks that can be covered in a single move, by defining that number in braces after the direction. This is referred to as the range of a move:
{n} |
the range of a single move |
---|
So for example, defining a rule like this:
"NS{2}"
Means that a box can move north or south within a range of two blocks.
This means up to two blocks, not exactly two; in other words,
the range token defines a maximum. The exception to this is the value
{1}
, which by its nature means exactly one move, since less than that is
no move at all!
Combining the blocks syntax with the directional wildcard, we can then make rules that define only distance and not direction. So this rule:
"*{1}"
Means that a box can move in any direction but only for one block
(otherwise known as the Blake's 7 Rule
).
So then, let's briefly recap, and examine a sample rule. You should be able to work this out before reading the description below it:
"NeSw{2}, *, NS{1}, SwNw{4}"
Got it figured out? That rule means: north-east or south-west within a range of two blocks; then any direction as far as you like; then north or south by one block only; then south-west or north-west within a range of four blocks.
There are two more tokens we need to look at before we've finished the basic syntax, which provide more flexibility in defining patterns and directions. The first of these is the dollar symbol, which means that a move must repeat the previous move:
$ |
repeat the previous move |
---|
Note that repeating the previous move means that the box must move in exactly the same direction, it does not mean that the box has the same choices as the previous move. For example, if a rule is defined like this:
"NS, $"
This means: north or south, then the same move again — so if a user chooses to move the box north, on her next move she must move it north again, she doesn't have the option to move it north or south again.
However the range of a movement does not have to be exactly copied in a repeating rule. You can specify that a move is in the same direction but with a different range, for example:
"NS{4}, ${2}"
That rule means: north or south within a range of four blocks, then the same direction again but only within a range of two blocks.
Finally, there's an "or" token that can describe multiple choices in a single pattern; this uses the pipe symbol, and is useful for situations where you want to describe multiple choices that are more complex than single directions:
| |
separate multiple choices within a single pattern |
---|
So if we define a rule like this:
"NS{2} | EW{3}"
That means: north or south within a range of two blocks, OR, east or west within a range of three blocks. You can separate any combination of tokens using the or symbol, allowing you define choices that are as distinct as you need them to be. For example:
"N{2} | S{3} | *{1}, $"
Which means: north within a range of two blocks, OR, south within a range of three blocks, OR, any direction for just one block; THEN the same move again (which, remember, means exactly the same move again, not the same choices again).
Good.
One more thing to note is that whitespace is ignored within rules and patterns. I've separated them with a space in these examples, but that's just to make them easier to read.
Extended rule syntax
The extended syntax allows you to specify shapes that a move should describe. Currently the only shapes that are available are triangles.
Let's begin with an overview of all the available triangle syntax, then we'll look at each one in turn:
T:n/n |
describe a right-angled triangle of an exact size |
---|---|
T:n/{n} |
describe a right-angled triangle where one side is an exact size and the other side is a range |
T:{n}/{n} |
describe a right-angled triangle where both sides are ranges |
T:n |
describe a right-angled triangle where both sides are the same size |
T:{n} |
describe a right-angled triangle where both sides are the same range |
So, a basic triangle definition might look this:
"T:2/1"
Where the "T:"
is what declares a triangle definition, and the two
numbers refer to the length (in blocks) of the a and b
sides of a right-angled triangle (ie. the two
sides that are not the hypotonuse). A triangle movement effectively
traverses the hypotonuse line.
So for example, the triangle definition just above means: to a point that's two blocks in one direction and one block in another direction; this describes the movement of a knight in chess.
The rules engine is not strict about the orientation of the triangle, so this definition for example:
"T:2/3"
Will be treated exactly the same as this definition:
"T:3/2"
Unlike the range tokens in the basic syntax, the numbers in a triangle definition are precise — the example above means exactly two blocks in one direction then exactly three in another direction. However you can also specify ranges by using the same range syntax:
"T:{2}/{3}"
That definition means up to two blocks in one direction, then up to three in another direction. You can also combine a precise number and a range within a single triangle, for example:
"T:1/{2}"
Which means exactly one block in one direction then up to two blocks in another direction.
In all these examples, of course, the two directions must be at right angles to one another, so that overall a right-angled triangle is always described. You cannot use the definition above to mean, for example, north by exactly one block then south by up to two blocks; it would be have be something like north and then east, or west and then north.
You can, however, use the triangle syntax to effectively define straight lines, by specifying zero as one of the values. A triangle definition like this:
"T:2/0"
Would mean exactly two blocks in a straight line (in any direction).
Finally, if you want to define a triangle where both sides are the same size, you can do so by only defining a single number. This for example:
"T:{3}"
Means a move that's up to three blocks in one direction then the same number of blocks in another direction. That differs from this definition:
"T:{3}/{3}"
Because in the former example, both sides must be the same length, whatever that length is chosen to be.
Too easy!
One final final note is that you can mix basic and extended syntax freely within a single pattern or rule. You could send your boxes on epic journeys by coming up with beasts like this:
"NE, $ | T:1/3, NeSeSwNw, EW | NeSw{2}, ${1}, NS, T:2/2, NeSw | NeSw{3}"
Overall then, there's a huge range of movement that can be described using the rules engine — enough, I should think, for even the most complicated applications. Rules can even be defined on the fly, allowing you to describe movement that's conditional upon where a box is at the moment.
Syntax reference
For reference, here's a complete table of all the available syntax:
N |
the box can move north |
---|---|
E |
the box can move east |
S |
the box can move south |
W |
the box can move west |
Ne |
the box can move north-east |
Se |
the box can move south-east |
Sw |
the box can move south-west |
Nw |
the box can move north-west |
* |
the box can move in any direction |
, |
separate multiple patterns within a single rule |
{n} |
the range of a single move |
$ |
repeat the previous move |
| |
separate multiple choices within a single pattern |
T:n/n |
describe a right-angled triangle of an exact size |
T:n/{n} |
describe a right-angled triangle where one side is an exact size and the other side is a range |
T:{n}/{n} |
describe a right-angled triangle where both sides are ranges |
T:n |
describe a right-angled triangle where both sides are the same size |
T:{n} |
describe a right-angled triangle where both sides are the same range |
← Remote controls | ← dbx main page