The ZRF Language

 Last update: 30/10/2000

The program Zillions of Games is not only a collection of almost 300 entertaining games from all over the world. Its most outstanding feature is the built-in scripting language ZRF which describes the rules for each game played. This language is very flexible and can describe almost any perfect-information abstract board game.
However, due its versatility, the grammar of ZRF is not trivial.

This document is meant as an addition to those guidelines given in the included documentation.
 
 

Introduction

 (game
    (board
        (grid
            (dimensions ... )

        );end grid
    );end board

 (win-condition ...)
 (players ...)
 (turn-order...)

 (piece
  (name ...)
  (moves
     (n add) 
     (s add)
     (e add)
     (w add)
  );end moves
 );end piece

);end game

]------------------

<-- action block
<-- action block

]------------------
 
 

 

]
]
]-move block
]
]
]
 
 

 

Note that everything in a line which follows a ; is ignored.

This is an example for the basic structure of a ZRF file. It is composed of several blocks that are surrounded by a pair of round brackets. Each block begins with a keyword, after which the definitions follow.
The outermost block is the game block; it begins in the first line of the file and ends in the last line (that is, unless you specify variants of the game or use macros, which would be on the same level as game).
Several sub-blocks need to exist within the game block; for example, a list of players must be specified.
There are other blocks like pass-turn which are optional. If you don't specify them, default values are used.

Very important in every game are the movement capabilities of the pieces. You specify them in the moves block, which is a sub-block of piece. The move block itself has an arbitrary number of sub-blocks which describe different moves of the piece. Unfortunately, the terms used for the critical distinction between the move block and its sub-blocks are sometimes confused. This document will conform to a suggestion made by Adrian King: He introduced the term "action block" to describe a sub-block of move.
But note that you may also find the terms "move line" or "move block" to describe an action block.

Be aware that the action blocks of one piece are executed in the order you give them. However, you have no guarantee about the order the pieces will generate their moves in.
 

1. Macros

You can write in ZRF without using any macros, but it is very advisable to use them as they save you a lot of typing and keep the structure of your script more logical. Have a look at this:

(define step
 ( $1
   (verify empty?)
   add
 )
)

This is the definition of the macro "step". Note the $1 in the command list. It refers to the first argument which is given to the macro.
You use it like this in the "move" block of a piece:

(piece
 ...
  (moves
    (step n)
  )
)

Zillions now takes the  definition of "step", replaces $1 with n and puts the entire command list from the definition into the move block.
Macro calls along with their arguments are always enclosed in brackets.

You can use more than one argument in a macro call ($2, $3 ...), and they need not necessarily be directions, you can also pass over piece types or flag names.

ATTENTION:
As always in ZRF, the correct use of brackets is very important in macro definitions! Remember that the macro call is simply replaced with the contents of the definition.
So, in the above example, the command list in the macro is enclosed in its own pair of brackets, and the macro can be called with (step n). It can however not be called with (n (step n)) to make 2 steps northward, as the inner brackets of the definition would appear in the middle of an action block where they do not belong.
If you want to use a macro both stand-alone and in the middle of a block, define it without inner brackets:

(define step
  $1
   (verify empty?)
   add
)

You can now use (n (step n)) as well as the stand-alone call ((step n)).

By the way, note that carriage returns are not important to the compiler.
(define step $1 (verify empty?) add)
This definition is equivalent.
The compiler is case-sensitive, however. Try to avoid all upper-case letters except in piece names.
 


2. Move Generation (introduction)

This is the most important task of your ZRF file, so plan it carefully!
You tell Zillions how pieces move in the (moves ...) and (drops...) blocks of the piece section.

The move blocks of course apply to all pieces on the board. At the start of each block, the current square is set to the from-square.

The drop blocks only apply to pieces in the off-board pool. This a special pool where all your (off ...) pieces from (board-setup...) start and pieces recycled with (recycle-capture) go to. These pieces are never visible.

(piece
...
  (moves
    (n add)
  )
)

This is simple example of a move block. Zillions interprets it like this:
--Start on the square where the piece stands.
--Go 1 square n (north).
--The piece may move to this square.

Add (or add-partial, add-copy, ...) generates a move possibility to the current square. A piece can choose between as many moves as add commands are present in the move block.

Well, quite good so far, but usually pieces are not allowed to capture friendly pieces on their destination square.
Let's add this:

(piece
...
  (moves
    (n (verify not-friend?) add)
  )
)

Zillion thinks:
--Start on the square where the piece stands.
--Go 1 square n (north).
--If there is a not-friendly piece here, go on. Else stop at once, the piece may not make this move.
--The piece may move to this square.

Note that if a (verify ...) test fails, the generation of this move is halted immediately! The rest of this action block is skipped, and the next block in the section is processed.
This does not happen if you use (if...) instead.

(moves
  (n (if enemy? capture) n (verify empty?) add)
)

Can you see what that does?
--Go n.
--If there is an enemy piece here, mark it for capture. If not, do nothing, but CONTINUE either case!
--Go n.
--If this square is empty, continue. If not, halt.
--The piece may move to this square.

This also explains the use of capture: It marks the piece on the current square (whether friend or enemy) to be removed from the board when the move is made. It is used to capture pieces which are not on the destination square of the move. (Example: 9 Men's Morris)

A word on brackets: As a rule of thumb, everything that belongs together goes into one set of brackets. This is especially true for arguments that belong to condition-tests.
Examples:
(verify empty?)
(verify (empty? a1) )

and, not and or have a bracket before them, the conditions which follow do not require extra brackets by themselves, only if they have arguments.
Examples:
(verify (and not-friend? attacked?) )
(verify (and (not-friend? a1) attacked?) )
(verify (and (not-friend? a1) (attacked? b2) ) )


3. Basic concepts

BOARD DEFINITION

A lot of boards can be conveniently described using a grid statement. However, when the board is irregular, or you wish to establish a special notation system not supported by grid, you need to use lots of position/links statements. It is alot of work to specify all squares manually; you should instead consider writing a program in Pascal or C that creates all the necessary data as an output.

For a smart use of grid , have a look at Chinese Checkers. It utilizes a large "diagonal" grid (diamond-shaped) in which the ranks have an x-offset (similar to the "3D" effect in 3D-TicTacToe). To give the correct star-shape to the board, the unwanted positions are deleted with kill-positions.
With this method it is easy to define a hexagonal board on which each cell has 6 neighbours. The Chinese Checkers board is basically hexagonal, too.

a1  b1  c1  d1  e1  f1  g1
  a2  b2  c2  d2  e2  f2  g2
    a3  b3  c3  d3  e3  f3  g3
      a4  b4  c4  d4  e4  f4  g4
        a5  b5  c5  d5  e5  f5  g5
          a6  b6  c6  d6  e6  f6  g6
            a7  b7  c7  d7  e7  f7  g7
 
 
 

  (grid
     (start-rectangle -48 8 -20 36)  ; note that the starting square is offboard. Having it onboard creates an unnecessary and ugly empty border
     (dimensions
      ("a/b/c/d/e/f/g/h/i" (28 0) )
      ("9/8/7/6/5/4/3/2/1" (14 28) )  ; the "14" is the x-offset
     )
     (directions                                             ; each square is linked with six neighbours
      (nw 0 -1) (ne 1 -1)
      (sw -1 1) (se 0 1)
      (e 1 0) (w -1 0)
  )

When designing new boards for games you wish to share with other people, you should always consider the actual size the board has in your Zillions window.  Other people might use a different Windows resolution, and your board might not fit on their screen. In this case, try to make the board as small as possible retaining a playable size. Now users with small screens can keep it like that, all others can simply 'enlarge' the image (Ctrl-E).
 
 


FROM-TO

"From" points to the square where the move of the piece starts. Go to it with (go from). At the beginning of the move, from is the square the piece currently stands on. If you change the from square without saving the old one via cascade, your piece actually enables another piece to move, but does not move itself. This behaviour can be found in some chess variants (though not in one that comes with Zillions).

"To" points to the destination of a piece's move, and is reached by (go to). When used without cascade , its only purpose is to save time and keep the code compact. You can set the to pointer to a square, then continue to verify other conditions and still halt the move before adding. Of course mark and back can accomplish the same.

The commands to and from set the pointers to the current square, and as usual are activated once add is used, which generates the move.
Note that these two lines do exactly the same:
(n e to w e s s add)
(n e add)

NOTE: To be honest, these two commands only do the same in the middle of the board, for if you use a <direction> which does not exist from your current position, the generation of the whole move is halted. It is the same effect you would get with (verify (on-board? <direction>)). Accidently leaving the board like that and thus halting the move is a common source of error.
Conclusion: In order to make sure the move generation does not halt, use (if (on-board? <direction>) ...) if you are unsure whether a step is possible from your current position.
 
 

WHILE

While is the only loop command in ZRF. As long as the condition given is true, the command list is executed. Be sure that the loop stops at some point, or you will get the eternal loop error. Note that it is possible that the command is not executed at all, not even once! This can happen if the condition is false from the beginning.
There are various examples for while in Zillion-ZRFs, many of them for making pieces slide like Rooks.

A common pitfall is a wrong assumption about the 'terminal case'. Look at this:
( (while (on-board? next) (if empty? add) next ) )

This is supposed to add moves to all empty squares on the board, assuming they are all interconnected by the "next" direction. But what happens once the current square is the last square on the board? (on-board? next) becomes false, and the last square in the chain is never checked nor added! To avoid this, create a "dummy" last square off screen. It is usually called 'end', and is linked to be behind the last 'real' square, so tests like the above one will stop on and ignore 'end'.
 
 

CASCADE

Cascade is used to move more than one piece in one turn. Set the first piece's destination with to, go to the square where the second piece stands, and use cascade. This saves the "to" and "from" pointers, and you can use them again. All moves take place at once when you do an "add".
Make sure (via verify) a piece is present on the square you use subsequent froms on. Else you will get an error, or even a crash!

Example:
(n (verify enemy?) to cascade from s to add)

This exchanges a piece with an enemy north of it. The to before the cascade is optional. If not set explicitly, to is set to the current square when cascade is used.

[Note to the beginner: Remember that (to cascade from s to add) is simply a sequence of commands. Do not read something like "from south" or "add to". Read "to, cascade, from, south, to, add".]
 

Some chess variants have pieces that may push other pieces, capturing them by pushing them of the board.
This is the way to script it:
(n to (while (and not-empty? (on-board? n)) cascade from n to) add)
The piece who makes a move like that will cause all pieces in its line to move one step north, except the last one, which will not move, and thus is 'overwritten' by its predecessor.

Cascade in a while loop is a kind of 'snowball effect': Every piece causes the next one in the chain to move. Anything else would be difficult to implement because you lose the original from-square (i.e. the piece that moved first) with every cascade.

Another important thing to note is that add does not reset the cascade-list, and there is no other way to do it manually, either. The list resets automatically at the beginning of every action block.
This means that every cascade in one move block makes one additional piece move. Even when you use add in between, the list of pieces that move gets longer and longer. Remember this when using cascade in a loop.

During a drop, cascade works differently than you might expect. As no from exists during a drop, each cascade creates a new drop with the off-board pool as the point of origin. Drops and moves may not be combined.
 
 

FLAGS / ATTRIBUTES

Flags (set-flag ...) are global. Be sure to use unique flag names in your script so you won't mix them up. Note that unlike piece attributes, flags change instantly once the (set-flag ... ) is encountered. They do not wait for an add , neither do they wait for the move they are in to be actually played. Flags need no further initialization.

Positional flags (set-position-flag ...) are associated with positions and reset to false at the beginning of each action block in the move block of a piece. Because of this, you might want to have move blocks that consist of only one action block in certain games, so your position-flags do not reset. Have a look at Ultima as an example for this. It uses position-flags to create 'lines' originating from the Coordinators and locate their intersections.
If you combine several action-blocks to retain the position-flags, you must not use verify. It would normally halt one action block, but here it would halt the entire move block and the piece could not move at all. Instead you must use cascaded if checks, opening a new level for each verify that you replace with an if. This can get confusing. Don't forget to (go from) after each move generated this way.
See GROUPS for another example for the use of positional-flags.

Attributes are associated with pieces. Their initial value can be specified with the (attribute ... ) command when defining a piece. Attributes are initialized at the beginning of a game. Pieces that enter the board also start with their initial attributes. Note that drops are the only way a new piece can enter a game (where add-copy works like a drop)! That means if a piece promotes, it retains its old attributes and is not initialized as a new piece.
Note that when checking attributes, you don't need a special keyword, instead you just give the attribute name. This checks the piece on the current square for the attribute. If a piece does not possess this attribute, it is assumed to be false.

Examples for testing:
Flag: (verify (flag? test) )
Position-Flag: (verify (position-flag? test) )
Attribute: (verify test)

To put these three kind of indicators to a good use, you must know about their respective advantages and disadvantages.
Position-flags are very short lived. Their scope is limited to the current action block. This means you cannot use them to control game flow at a larger scape. Instead, mark special board positions of the current move with them. For example, they can be used to identify the from-square, as there is no command like (verify from?) in ZRF. You need to know the from-square when scanning through the board presuming the current move has already been made, so you treat from as empty and to as friend.
Flags on the other hand, live very long. In fact, they never reset and change their state as soon as a set-flag command is encountered. They can be very useful to remember whether a certain event during move generation has occured. However, you must be careful to reset them at the right time, usually at the beginning of one action block. It is possible to use flags even beyond one single action block, though it can be problematic. Always keep in mind that they don't require the move they are in to be played in order to change state; they will change even when the move is only generated.
Because of that, you need attributes. They are better suited for storing information that must be available beyond the current turn, because they only change state when a move is actually played. That however, is also their disadvantage: They are too slow to be written and read out during the same turn. If you want that behaviour, use flags.
 
 

ABSOLUTE-CONFIG / RELATIVE-CONFIG

A piece-type must be specified with (absolute-config) and (relative-config) winning conditions. The simple declaration <any-piece> is missing from ZRF.
However, it is possible to specify more than one piece type.

(win-condition (White Black) (absolute-config Knight King (b2 b3 b4)) )

It means that a Knight or a King must be present on all of the given squares.

CONCLUSION: Instead of <any-piece>, just list all piece types here.

You can also specify a zone instead a list of positions, but then just one of the squares it contains must be filled, not all.
 
 

PARTIAL MOVES AND MOVETYPES

Partial moves are used in many games, usually in the form (add-partial <move-type>). This means the player's turn is not yet over after this move, he may (or must, depending on the state of (pass-partial) ) make another move with the same piece.
add-partial (no brackets!) allows the player to make a move of any type. If you give a <move-type>-argument, the player is limited to this kind of move.
Example: Checkers. There a two move-types for pieces, jumptype and nonjumptype. When his turn starts, the player normally has the choice which move to make, but after he has captured, he is limited to more capturing moves with (add-partial jumptype). If this limitation not existed, he could first capture and then step with his pieces.
Checkers is also a good example of move-priorities. As you know, you must capture a piece in Checkers if you can. This is simply explained to Zillions in the line (move-priorities jumptype nonjumptype), meaning if there are moves of the first type available, they must be made. If not, moves of the second type must be made, and so on.

Sometimes, you might want to check whether you are currently within a partial move or just starting a regular one. To do so, remember that within a partial move, from equals last-to .
(Where last-to is always the to of the last move, whether this was yours or your enemy's, whether it was partial or not.)

In a cascade, the first pieced in the chain usually receives the next partial move. In unusual cases, though, where a piece caused a move without moving itself, the next piece moves again.
 
 


4. Advanced Concepts

 RECYCLE

The recycle commands mean the following:

(recycle-captures false): All pieces captured disappear from the game.
(recycle-captures true ): All pieces captured go to the off-board pool. This is the same pool where (off...) pieces start in the (board-setup...) section.

These commands are not useful for games like Shogi because pieces return to the pool of their own colour, i.e. White pieces captured by Black go to the White pool. In Shogi, pieces captured must be converted to the colour which captured them. That is why the Shogi-ZRF needs a quite large section to manage the prison manually!

(recycle-promotions false): Pieces added to the board by promotion, for example with (add Queen) or (change-type Queen), are generated and appear from nowhere, as they do in Chess.
(recycle-promotions true): Pieces added to the board by promotion, for example with (add Queen)  or (change-type Queen), come from the player's off-board pool, where they must be present or the promotion cannot take place.

When both recycle flags are true, and the pool is empty at the beginning, pieces can of course only be promoted to pieces already captured. This is the case in Burmese Chess or in C. Freeling's Grand Chess (the latter not included with Zillions).
 
 


SIMULATED PASSING

With (pass-turn ...) or (pass-partial ...), it is not possible to allow passing only under certain conditions found on the board. You can circumvent this limitation by generating null-moves, i.e. a move to the starting square of a piece. However, you cannot simply give

(moves (add))

as a move block, because Zillions does not generate moves which do not change the board configuration. Instead, give the piece which can make a null-move a dummy attribute and the move block

(moves ((set-attribute whatever true) add))

You can now effectively pass by picking this piece up and putting it back on its starting square. Don't forget to turn off the 'real' passing and to inform the player of this special passing move, as it is only visible on the board when picking up a piece.
 
 


DOUBLE MOVES

It is generally possible in Zillions to generate moves which lead to the same square.
Example:
(n ne (verify empty?) add)
(ne n (verify empty?) add)
Both move blocks generate a Knight's move to the same square. Zillions v1.0.2+ will notice this, and will not bother you with a menu asking to select one move.
However, there are two things to note about double moves:

First, it is still possible that a menu with identical choices appears.
(n ne (verify empty?) (set-attribute nevermoved? true) add)
(n ne (verify empty?) add)

Both moves end on the same square, but one changes a piece attribute, the other does not. The menu will appear, the entries are unfortunately identical, and the only way to tell the difference between them might be to guess by their order. You will generally want to avoid this situation. Note that if in the above example (set-attribute nevermoved? true) would be replaced by (set-flag nevermoved? true)  the move menu would not appear. The flag is set anyway, so these two moves are in fact identical.

The second thing to remember about double moves is a bit more subtle and concerns the Zillions AI. Zillions assigns point values to the pieces which you can see under the description when right-clicking on them. This value is based on the number of possible moves for this piece, not on the number of accessible squares. This means a double move counts twice in Zillions' eyes, thus leading to Zillions assigning an artificially high value to this piece. If this happens by accident, the AI will probably play weaker with this wrong value. However, you can deliberately give a piece double moves if you notice that Zillions assigns too little points to it. Double moves then become a way of slightly tweaking the evaluation of the AI.
 
 


COMPLEX WINNING CONDITIONS

With a trick, it is possible to define very uncommon and complex winning conditions, involving many (verify...) or (if...) checks that are not possible in a simple (win-condition...)-goal.
Try the following: Make the check after every move, for example by defining a macro that is called before every add. Should you find out that the game is won during your macro, place/take away a certain piece on a certain dummy square off-screen, using this piece as a "flag". The actual (win-condition...) would then be the so created (absolute-config...) on this square, or the appropriate (pieces-remaining ...) condition created by this action.

This works, and the AI recognizes it, too; however, it is possible that you notice a weaker performance of the AI if your conditions become too complex.

A possible implementation of a complex winning condition is shown in the GROUPS-example below.

Another example can be seen in Ninuki-Renju, which is a variant of Go-Moku and is on the CD.
In this game, all players start with a dummy piece called Capture-10 on a dummy square. Once five pairs of stones are captured, this dummy piece is removed, which causes the side it belonged to to win because of the (pieces-remaining 0 Capture-10) goal.
This method is probably even better than the one demonstrated in the GROUPS-example because it does not involve a neutral player, but note that it requires as many dummy squares/pieces as players present in the game.
 
 

GROUPS

Several board games make use of the concept of groups. A group is a number of interconnected pieces, i.e. not separated by empty squares.
An example for a game which uses groups is Go. Fortunately, MiniGo comes with Zillions, meaning it is indeed possible to check for the presence of groups in ZRF.
However, it might be a bit difficult to understand this concept and vary it in order to use it in own creations, so I will present an example macro which 'counts' the number of groups present on the board. It makes the player win if he has joined his pieces together in one group.
(Actually, the macro is simplified, meaning it does not fulfill its purpose in really all cases, but it should be sufficient to explain how groups can be checked.) --> Example

The basic way how groups are formed is this: One or more position-flags are set on specific locations, then they are 'spread' to include the whole group. This is done by going through the board several times, until no further 'spreads' are possible.
On a second approach, the so created groups can then be checked for a lot of purposes, for example 'counting' them as shown, or checking which enemies are captured by surrounding as in MiniGo.

In MiniGo, this is accomplished in these basic steps:

1. Initialize p-flag 'safe': Go through whole board - square is safe if empty
1a. (to is not safe)
2. Initialize p-flag 'danger': Enemy is in danger if adjacent to to
3. Spread safe / danger to orthogonal adjacent enemies as long as possible on whole board
4. Go through whole board - capture all enemies which are in danger but not safe
 
 


RANDOM/REGULAR EVENTS

It is possible to use random elements in games. This can be seen in Senat.
It uses a player called ?Dice-Roll, and the important thing here is the question mark. All players whose names start with a question mark do not appear in the "Choose Side" dialog and automatically make a random move when it is their turn. As in Senat, this is probably best used to throw a dice. The drawback is that ZRF has no counting whatsoever, so every possible dice value must be checked manually.
ATTENTION: The frequency of the "dice-rolls" in Senat is not that of a normal dice! You can see that some numbers are "added" more often than others. This is done to simulate the throwing of rods in this ancient egyptian game. When you add the appearence of the value "4", for example, twice, it will appear twice as often as a value added once.
Conclusion: For a normal dice, add each value only once!

You can also have a 'Referee' player perform the same move regularly (e.g. to reset a counter after each move). Using a random player for this task has the advantage that no confusing entry appears in the 'Choose Players' dialog.
 
 

WHICH COLOUR AM I?

Perhaps you might want to know at some point during the move generation whose turn is currently being generated. You can see an example for that in Shogi, although the test is never actually used.
It is simply a zone test which is helpful here, as a zone can have the same name but different positions for each player. So, if (in-zone? promotion-zone a8) is true in Chess, you are White, if not, you are black.

Another way to differentiate between colours is used in Ninuki Renju (variant of Go-Moku). When checking the complex win-condition, the correct dummy square for the colour currently moving must be found.
Note how this is accomplished:
- The direction right leads from one dummy square to the next.
- We go to the first dummy square and say  (while enemy? right) , which moves us to 'our' square. (of course this only works with appropriate pieces on the squares)
 
 

COUNTING

Zillions does not support any integer counting.
But still, some goals involving that something is counted can be simulated. Have a look at Ninuki Renju (variant of Go Moku) to see how it counts to five with booleans only. It uses five flags and five piece-attributes. The flags store the number of captures the current move caused and are reset to false at the beginning of every move. The attributes store the values for the whole game and cause a win once five is reached by capturing the counter (see COMPLEX WINNING CONDITIONS for more).

Macro bump-attributes increases flag-count by one and is called after every capture of a pair of stones.
Macro count-caps increases the piece-attribute-counter by the number currently stored in the flags.
 
 

CAPTURE WITHOUT MOVING

The usual way of capturing an enemy without moving one of your pieces is this:

(n (verify enemy?) capture add-copy)

  This does the following: It creates a copy of your piece on the target square, capturing the enemy there. Then the copy is captured itself. This move has always priority during selection because it is internally considered a drop. I suggest you turn (animate-captures) off, else you'll see your own piece fly away.

Together with partial moves, this can also be used creatively to remember where captures took place. Sometimes, it can be a problem that you have no way of telling where pieces were taken away via capture last turn. However, when you capture an enemy by first giving your piece a partial move in which it copies itself onto the enemy, and simultaneously captures its copy, the square on which this capture occured will be last-to!
 
 

SPLITTING PIECES

With a little work, it is possible to have "combined" pieces in Zillions, i.e. pieces which are actually "composed" of more than one piece. Though this feature is not directly supported, it can be simulated with a trick: You just define a new piece-type for the combined piece.
Combining pieces should be easy: Just have one of the partial pieces capture another, and change its type on the destination square. The simplest way to do this is the promotion syntax (add <combined-piece-type>).
Splitting the pieces is a bit more difficult. You have to change the piece type on both the from -square and drop a new piece on the to-square using add-copy. Have a look on this example macro, which changes the move of a Chess Queen, so that it may split into its components, Rook and Bishop. In this example, it spawns a Bishop and leaves a Rook behind.

(define qslide
(
$1
(while empty?
  to
  (go from)
  (change-type Rook)
  (go to)
  (add-copy Bishop)
  $1)
(verify not-friend?)
to
(go from)
(change-type Rook)
(go to)
(add-copy Bishop)
))

(Note that this macro will only work correctly with ZoG versions v1.1.1+)

For a practical example for combined pieces, have a look at my beta implementation of Vyremorn Chess, where the mounting and dismounting of Disks is handled this way.
 
 

MULTIPLAYER GAMES

Games with more than two players introduce several new problems to ZoG.
The first is a problem of the AI. The AI has no other choice as to treat all other players as enemies, assuming the worst case scenario that they unite against the current player. Possible and perhaps temporal alliances between players (aka the "petty diplomacy problem") cannot be taken into account for ZoGs calculations.
This has the side effect that Zillions' board evalution is always negative, and it will wholeheartedly welcome all draws; you should concerning turning draws into losses for multiplayer games.

A second unfortunate effect is that there is no longer a way to identify which player a piece belongs to. In two player games, you can identify the current player with a zone test; all enemies must then belong to the other player. In multiplayer games however, an enemy might belong to any player; you cannot tell the difference.
One way to work around it is to use different piece names for every player: Player Red gets RedPawn, player Green gets GreenPawn. The pieces just differ in their names, otherwise they are identical. However, this method is quite unelegant and can get tedious if your game has many piece types.
You can also try to structure your ZRF in such a way that every player can perform all necessary tests for himself, avoiding the need to identify the exact player of an enemy piece.

Another problem of multiplayer games has to do with loss-conditions. Zillions will terminate a game once a loss-condition is met, displaying player X has lost. However, you sometimes want the game to continue; player X is out, but the others might still compete for the win. To achieve this, don't use loss-conditions. Instead, have a dummy piece which indicates whether the player is still in the game. The piece can capture itself once the player is out. I tried such a concept in King's Palace v1.1.
Another issue to take care of is to pass the turn for the player who is out. An automatic pass can be achieved via (pass-turn forced) ; of course you need to disable all moves when a player's indicator is no longer present.
 
 

PARAMETERS

Remember that in macros placeholders like $1 are simply replaced with the parameters the macro is called with. This can be used in several creative ways.
For example, you can distinguish between two different cases in the same macro by handing true or false over as a parameter. This is used in Hasami Shogi. In the macro jump-or-slide, parameter $4 determines whether a capture around the corner is allowed.
Another interesting method is to use parameters as part of flag or attribute names. For example, the command (set-flag searchfor$1 true) can refer to different flags depending on what piece type you specify when calling the macro it is used in. Call it with (macro-name Rook), and the command will be (set-flag searchforRook true).
 
 

MAXIMIZING SPEED

It's a good thing when only correct moves are generated by your ZRF, but it's even better when they are generated fast. The less board positions the AI can generate during it's turn, the worse it will play. Very slow ZRFs might even produce a noticeable delay before every single turn. Get some hints in this sections how to avoid that.


ONCE PER PIECE TESTS

When testing for something expensive applicable before the move of a piece, be sure to do it only once per move block, not once per action block. Do all your once-per-piece-things in the topmost action block in your move section. This topmost action block generates no moves, it just sets persistent flags for the rest of the move block to check.
Although it is not really guaranteed by the program that all action blocks are executed in the order you give them, this trick relies on it nonetheless. It usually works. If it doesn't, don't say you were not warned, and have a watchful eye for bugs that might be caused by the blocks being executed in the wrong order.

ONCE PER TURN TESTS

Sometimes it is even too expensive to scan the board once per piece, and you want some things checked only at the very beginning of your turn; you want a certain action block to be executed before all others.
As the order pieces generate their moves in is undetermined, you cannot just put a dummy piece with this move above the other pieces in your ZRF, you need another way to distinguish your block from the others. Move-priorities can serve you here; action blocks that belong to move-type with priority will be generated first, so you assign a special 'dummy' top-priority to your action block.
You need to make sure your 'pre move generation' block doesn't add moves itself; their faked priority would prevent your normal moves. However, you are free to perform all sorts of checks and (especially important) you can set and reset persistent flags for all the action blocks to follow as a kind of initialization.
It doesn't really matter which piece gets this special action block, as long as every player is guaranteed to have exactly one of this kind. Royal pieces lend themselves to this purpose, but dummies work as well.

A WORD OF WARNING

Always remember: Neither the priority technique, nor flags persistent between action blocks, nor some other tricks described here are officially supported by Zillions. They have been observed to work at least once, but nobody guarantees they will work for your game. Just experiment. The worst thing that can happen is a program crash. [Not really. The worst that can happen is that everything appears to work fine, although a bug is lurking deep deep down in your ZRF!]
 
 

COMMON ERRORS


Comments? Questions? Additions? Write to LordX@gmx.net

This text is provided without warranty. You use it at your own risk.
© Jens Markmann