# Rule file syntax and semantics: what's new in GS5

Updated for ver. 5.001. 2022-05-11

## New features

The syntax and semantics of rules have been significantly expanded in Game Server 5.*, in order to allow describing more interesting rules. This section describes major changes, as compared to the baseline description (Syntax).

### More variables

The original GS1 included 5 variables:

- `p` -- the number of bucket that accepted the most recently removed game piece
- `pc` -- the number of bucket that accepted the most recently removed piece of the same color as the piece that the player currently attempts to move ("the current piece")
- `ps` -- the number of bucket that accepted the most recently removed piece of the same shape as the piece that the player currently attempts to move ("the current piece")
- `Nearby` = the bucket nearest to the location of the piece being picked
- `Remotest` = the bucket farthest from the location of the piece being picked

Additionally, order names (e.g. `T, B`) evaluated to sets of board positions (depending on the current state of the board).

In GS3, when image-and-property-based objects were introduced, the `pc` and `ps` were generalized to

`p.propName` -- the number of bucket that accepted the most recently removed game piece whose property called propName has the same value as in the current piece. (Thus, `p.color` has the same meaning as the original `pc`, and `p.shape`, as the original `ps`).

In GS5, the set of variables has been further expanded. This includes:

- `this` -- the current game piece. One uses the dot notation to access its properties, such as `this.color` = the color of the current piece. The syntax `this.pos` and `this.bucket` can be used to refer to the position of the current piece on the board, and the number of the bucket to which the player attempts to moves it.
- `last` -- the game piece that was most successfully removed from the board most recently. One uses the dot notation to access its properties, such as `last.shape` = the shape of the most recently removed piece. The syntax `last.pos` and `last.bucket` can be used to refer to the board position from which a game piece was removed most recently, and the number of the bucket into which a game piece was dropped most recently. The negation expression (`!last`) evaluates to `[1]` (which is the Game Server's equivalent of "true") if no game piece has been removed yet, and to the empty set `[]` (which is the Game Server's equivalent of "false") otherwise.
- `q.propName` -- the most recently removed game piece that shared the value of the specified property with the current piece. Thus, for example, `q.shape.bucket` has the same meaning as the legacy variables `p.shape` and `ps`. All fields can be accessed with the dot notation. For example, in a game where objects have properties called "size" and "orientation", the expression `q.size.orientation` will evaluate to the orientation of the most recently removed object of the same size as the current object; `q.size.pos` will evaluate to the position from which that piece was removed. These two expressions (as `q.size` itsel) will evaluate to an empty set if no game pieces of this size have been removed yet. Similarly, `q.bucket` refers to the most recent pieces accepted by the bucket being tried now.

### Can use expressions in all fields of an atom (except for `pos:`

In GS3 (and GS4), complicated expressions could only be used in the `bucket:` field of a rule atom. (See Bucket expression arithmetic). For other fields, such as `color:` or `shape:`, only constants could be used.

In GS5, expressions, with essentially the same syntax, can be used in all fields of the rule atom, other than the `count:` field and (for now) ther `pos:` field. Thus, for example, the atom

          (color:last.color, bucket:last.bucket)

will allow the player to pick a game piece of the same color as the most recent removed piece, and to put it into the same bucket.

### The new `code:` field

A new field, `code:`, can now be used in rule atoms. As all other fields, it is optional. If it is present, it needs to evaluate to a true value (i.e. not to an empty set) in order for the move to be allowed.

For example, the atom

          (code:this.pos==last.pos+1)

will allow the player to pick a game piece that is located in the "next cell" to the cell from which the most recently moved piece was picked.

### Expanded expression syntax

#### Operators

In addition to the arithmetic operators described in (See Bucket expression arithmetic), the Rule Game expression syntaxt now also supports comparison operators (`==, <, >, <=, >=, !=`). They return `[1]` on success and `[]` on failure.

#### Data types

In the original GS1, one could say that the only data type used in the color, shape fields was string constants and sets of strings, while the only data type used in the position and bucket fields was integer (and sets of integer). As variables and operators have been added in GS3 and now GS5, one can now talk about the constants and values of expressions belonging to the following types:

Type | Example | Notes
--- | --- | ---
String | `red`, `"red"` | For compatibility with GS1-GS4, string constants can be written without quotes in simple expressions in property fields, e.g. `color:red` or `color:[red,green]`. Elsewhere, the use of double quotes is strongly recommended, to avoid confusion with variables.
Integer | `1`, `3`, `pc`, `last.color.bucket`
Integer ranges | `[0..30]` | Only used in property fields, for integer-valued properties. Both bounds of the range must be constants.
Sets | `[1, 3]`, `[red, green]` `["red", last.color]` `!pc`, `[]` | (1) Non-empty sets and empty sets are GS's equivalents of "true" and "false". (2) Order variables (T, B, L1, ...) evaluate of sets as well.
Objects | `this`, `last`, `q.color`, `q.bucket` | One can access their fields (e.g. `last.shape`), or use them in a negation operator (`!last`)

In reality, all of GS arithmetic is set-based, so a single string or a single number is interpreted as a set of 1 element.

The language used in GS rules is not strongly typed. As in Perl, implicit conversion between integers and strings take places when appropriate and possible. If the expression contains an "impossible" operation, such as division by zero or an attempt to apply an arithmetic procedure to a string, the result will be an empty set.

The equality / inequality operators (==, !=) can be applied both to numbers and to strings. The comparison operators (`<, >, <=, >=`) should only be used with numbers. Otherwise, an empty set will be returned.

## Examples

### Example 1: assembling a picture in a specific order

Experiment plan: `vm/jigsaw-02`

Thus, for example, in Charles' last example (assembling a picture in required order), one can assign to each of the four pieces a numerically-valued property "seq", with values {0,1,2,3}. The property file (shapes/vm/jigsaw-02) is as follows:

    image,name,seq
    b1-nw.svg,b1-nw,0
    b1-ne.svg,b1-ne,1
    b1-se.svg,b1-se,2
    b1-sw.svg,b1-sw,3

The rule set then can look as follows:

    1  ()
    (seq:(last.seq+1)%4,  bucket:p)     (seq:(last.seq+3)%4,  bucket:p)

Here, the first rule line of the rule set will allow to pick any piece and to put it into any bucket; the control then goes to the second line. The second line will allow to pick a piece whose property "seq" has a value that's subsequent to the one of the last-used piece (in the first atom, clockwise, i.e. 3 after 2, 0 after 3, etc; in the second atom, counterclockwise, i.e. 1 after 2, etc), and to put it into the same bucket as the previous piece.

As with the bucket arithmetic, the arithmetic for the property fields will be set-based, yielding empty set when "last" is not defined, or when a particular property in the last piece wasn't defined.

### Example 2

Experiment plan: `vm/jigsaw-03`

If there are several "pictures" on the board, and the player is allowed to choose any picture to start assembling, but then must assemble the entire picture before proceeding to the next one, we can use the property "body" to identify the picture. Say, here there are 2 pictures (numbered body=0 thru body=1), within each of which the pieces are numbered with seq=0 thru 3. The pieces of each picture must be assembled in different buckets (say, the player chooses the bucket for the first picture at will; the pieces of the second picture being put to the next [clockwise] bucket after the one used for the first picture, and so on).

The property file may look like this:

    image,name,body,seq
    b1-nw.svg,b1-nw,1,0
    b1-ne.svg,b1-ne,1,1
    b1-sw.svg,b1-sw,1,2
    b1-se.svg,b1-se,1,3
    b2-nw.svg,b2-nw,2,0
    b2-ne.svg,b2-ne,2,1
    b2-sw.svg,b2-sw,2,2
    b2-se.svg,b2-se,2,3

The rules:

    1 (bucket:[  !p*[0,1,2,3],  p+1]  )
    (body:last.body, seq:[(last.seq+1)%4, (last.seq+3)%4], bucket:p)

One can also write an equivalent rule set differently, for example like this:

    1 (code:!p) (bucket:p+1)
    (body:last.body, seq:(last.seq+1)%4, bucket:p) (body:last.body, seq:(last.seq+3)%4],  bucket:p)

### Example 3

What if we have several pictures on the board, and we allow the player to work on them in parallel, with a different destination bucket for the pieces of each picture? Let's first design a game where the destination bucket for the pieces of each picture is predetermined by the designer. Thus, the pieces of the picture with body=0 go to bucket No. 0, etc. Experiment plan: `vm/jigsaw-04`

    (code:!q.body, bucket:this.body)  (seq:(q.body.seq+[1,3])%4, bucket:this.body)

(The first atom allows picking pieces for which q.body.seq is not defined, i.e. pieces of pictures that have not been worked on yet; the other atom allows assembly clockwsise and counterclockwise).

### Example 4

What if, as in the previous example, we want the player to "assemble" each picture in a different bucket, allow him to work on them all in parallel, and we want to allow the player to independently choose the destination bucket for each picture? (Not tested)

    (seq:(!q.body)*[0,1,2,3], bucket:(!q.bucket)*this.bucket)   (seq:(q.body.seq+[1,3])%4, bucket:p.body)

Here, the first atom allows picking a piece from a "not-started-yet" picture, and putting it into any empty bucket. (One on which q.bucket.body is not defined yet). The other atom provides for the clockwise and counterclockwise assembly of the already-started pictures.

The first atom can be more elegantly written with the "code:" field, Experiment plan: `vm/jigsaw-05`

    (code:(!q.body)*(!q.bucket))   (seq:(q.body.seq+[1,3])%4, bucket:p.body)

The above clearly shows that there are two conditions for the first atom to apply: q.body should not be defined (i.e. the current piece belongs to a picture from which no piece has been moved yet), and q.bucket is not defined (i.e. no piece has been put into the current bucket -- thus, it's empty).

### Example 5

This game pieces represent playing cards, with the properties "suite" (= "spades", "clubs", etc) and "value" (= 2,3,...,14). The player must put different suites into different buckets (of his own choice), in the order of increasing values. (I.e. a queen of spades can be put on top of a jack or 10 of 9 of spades, but not on top of a king or ace of spades, and not on top of a card of a dfferent suite).

Note that this game allows stalemates. If, for example, the player put the queen of spades into a bucket before the jack of spades has been removed, then this jack won't be able to ever leave the board. Experiment plan: `vm/gs5-example05`

To implement this, I made a set of composite image objects, with the attribute "color:" in lieue of "suite:". Visually, the "value" of each object is represented by the number of filled elements in the grid.

    (code:!q.color*!q.bucket) (color:q.bucket.color, code:this.value>=q.bucket.value)

### Example 6

This comes from Paul's 2021-04-30 message.

> Rule_PK2n: alternately clear all of the highest row and then all of the lowest row of the displayed objects (and iterate). The first object can go anywhere, but after that the objects must be placed in clockwise order.

Experiment plan: `vm/gs5-example06`

    1 (pos:T, bucket: [!p*[0.1,2,3],  p+1)
    (code: (pos-1)/6 = (last.pos-1)/6),    bucket:p+1)
    1 (pos:B, bucket:p+1)
    (code: (pos-1)/6 = (last.pos-1)/6),    bucket:p+1)