|
This document is meant as an addition to those guidelines
given in the help file at ftp.zillions-of-games.com.
| (game
(board (grid (dimensions ... ) );end grid );end board (win-condition ...)
(piece
);end game |
]------------------
<-- move block/line
]------------------
|
]
] ]-move section ] ] ] |
(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"
section 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
section.
Macro calls along with their arguments are 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 has 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 a move 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.
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 section.
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 move 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) ) )
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).
"To" points to the destination of a piece's move (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. Of course mark and back 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>)).
Conclusion: In order to make sure the move generation
does not halt, use (if (on-board? <direction>)
...)
(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: 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)
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.
Positional flags (set-position-flag
...) are associated with positions and reset to false at the beginning
of each line in the move section of a piece. Because of this, you might
want to have move sections that consist of only one 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 have one-line move sections, you must not use
verify. It would normally
halt the generation of one move line, but here it would halt the entire
section and the piece could not move at all. Instead you must use multiple-level
if checks, opening
a new level for each verify
that you replace with an if.
(This is definitely an advanced concept.) Don't forget to (go
from) each move generated this way.
See GROUPS for another example
for the use of positional-flags.
Attributes are associated
with pieces. They must be initialized with the (attribute
... ) command when defining a piece. Attributes
are never reset, but they disappear when the piece they belong to is captured
or changes type.
Note that when checking attributes,
you don't need a special keyword, instead you just give the attribute name.
Examples for testing:
Flag: (verify
(flag? test) )
Position-Flag: (verify
(position-flag? test) )
Attribute: (verify
test)
(win-condition (White Black) (absolute-config Knight King (b2 b3 b4)) )
It means that a Knight or a King must be present on the given squares.
CONCLUSION: Instead of <any-piece>,
just list all piece types here.
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
= 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.)
Currently, partial moves seem to
be a bit difficult to handle for the Zillions-AI. It sometimes has troubles
foreseeing the consequences of such moves. Thus use them sparsely.
(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 the game Shogi because pieces return to the pool of their own colour, ie 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), 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), 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 (not included with Zillions).
((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
( (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 not visible on the board.
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 moves counts twice in Zillions' eyes, thus leading to a misjudgement and weaker play of the AI.
CONCLUSION: Avoid generating double
moves.
The basic way how groups are formed is this: One or more
positional-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
here, or checking which enemies are captured by surrounding as in MiniGo
(see below).
The following example macro is called after the move of
a piece has been set with to ,
but before the add.
(define check-for-group
(go to) mark
; This macro is used before every add
to check for the victory condition.
; Unfortunately, this means that for Zillions this very move
has not yet taken place.
; The piece is still presumed to be on its from-square,
and its to-square is believed to
; be empty. Therefore, we will mark the to-square
and treat it as friend.
; Theoretically, we would have to do the same with the from-square,
too, in order
; to treat it as empty. However, there is only one 'mark'
in Zillions, which means
; we would need additional positional-flags
if we want to manually manage both squares.
; It would make this example unnecassarily complicated, which
is only there to illustrate
; the concept.
; MiniGo addresses this problem by manually setting the to-square's
empty (safe) flag to false
; after the initialization. If Go pieces didn't drop and thus
had a from square, its empty
; flag would have to be set to true.
a1
(while (and not-marked? not-friend?) next)
; find the first friend.
; Remember we need to treat 'marked square' as friend.
(set-position-flag firstgroup true)
; mark the first friend
; Note: Mark is this context of course not refers to the ZRF-command
'mark'. We use
; positional-flags to
mark groups.
a1
(set-flag changed true)
; in order make outer while start
(while (flag? changed)
; as long as something on the board changes...
(set-flag changed false)
; nothing has been changed yet
a1
(while (not-position? end)
; go through the whole boards
(if (and
(or marked? friend?)
(not-position-flag? firstgroup) ; if we find an unmarked friend...
(or
; ... who is adjacent to a piece in the first group...
(position-flag? firstgroup n)
(position-flag? firstgroup s)
(position-flag? firstgroup e)
(position-flag? firstgroup w)
)
)
(set-position-flag
firstgroup true) ; ... this piece also belongs to the first group
(set-flag changed
true) ; something has changed on the board
) ; end if
next
) ; next position
) ; next run
; Our first group on the board has been marked with the position
flag firstgroup.
; Obviously, if we find one of our pieces without this flag,
it must be part of the second group.
a1
(set-flag wehavewon true) ;
presumes we have won until the opposite is proven
(while (not-position? end) ;
go through the whole board
(if (and
(or marked? friend?)
(not-position-flag?
firstgroup)) ; if there is an unmarked friend ...
(set-flag wehavewon false)
; ...we haven't won
) ; end if
next
); end while
; Now the flag wehavewon indicates whether we have won.
; To make Zillions recognize this as an actual win-condition,
; we can convert the dummy piece Goal on indicator to our side.
(if (flag? wehavewon) (change-owner indicator) )
)
(win-condition (White Black) (absolute-config Goal (indicator) ))
... assuming the game starts with a neutral dummy piece
called 'Goal' on the dummy square 'indicator' (a complex winning condition).
This macro requires a normal, chess-like board, whose squares are all connected
with each other via the next direction, beginning
with a1 and ending with end
(dummy off-board).
Capturing groups by surrounding in MiniGo is done 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': Go through whole board
-> 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
This can also be used to have a 'Referee' player perform
the same move regularly (ie to reset a counter after each move). You only
need to give this random player exactly one possible move.
1. (n (verify enemy?) capture (go from) add)
This move is only selectable with Smart Moves On. If another of your pieces can also capture the same enemy in the same turn, this move is not selectable. You better use method 2 in this case.
2. (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.
WARNING: In versions 1.0.1 and
below, the animation code does not support this move. You must turn
(animate-drops false), or
ZoG will crash. This has been fixed in 1.0.2.
- It is sometimes difficult to follow which piece is where during the creation of a move. Note, for example, that the (set-attribute ...) command refers to the piece on the current square after the move is made, and that the to square remains empty before the add.