Program Games with the Fairychess Include File in Game Courier - A Tutorial
The fairychess include file has been designed to make it easier than ever to program games for Game Courier. Here are some of its advantages over earlier include files:
- It gives you full freedom to use the piece notation you want to use with your game.
- It has a large collection of piece definitions. So, instead of programming your pieces, you'll often be able to just identify the pieces you want to use.
- It lets you choose what names will be used for pieces in error messages.
- It has a variety of settings you can change for enforcing particular rules without rewriting functions or subroutines.
Pieces
Piece Identifiers
When you write the FEN code that defines the starting position of your game, you will use the array keys of the $pieces array as your piece notation. This is an associative array that associates short piece identifiers with the image files used to display the pieces. Game Courier will turn your FEN code into the $space array, which associates coordinates with pieces, empty spaces, or missing spaces. It will represent pieces in this array with the same piece identifiers used as array keys in the $pieces array. This allows Game Courier to turn the $space array into a graphic representation of the board.
Example of Abstract set file
A set file loads a piece set as an associative array. This consists of a series of key/value pairs. Each key/value pair is separated by a comma. The key appears to the left of the => symbol, and the value appears to the right. Each line includes two key/value pairs for the same piece. As a common convention, lowercase identifiers are used for Black's pieces, and uppercase are used for White's.
<? $dir = "http://www.chessvariants.com/graphics.dir/abstract/"; $pieces = array( "a" => "BKnightBishop.gif", "A" => "WKnightBishop.gif", "b" => "BBishop.gif", "B" => "WBishop.gif", "c" => "BCamel.gif", "C" => "WCamel.gif", "d" => "BWarmachine.gif", "D" => "WWarmachine.gif", "e" => "BElephant.gif", "E" => "WElephant.gif", "f" => "BFers.gif", "F" => "WFers.gif", "g" => "BGrasshopper.gif", "G" => "WGrasshopper.gif", "h" => "BHorse.gif", "H" => "WHorse.gif", "i" => "BNightrider.gif", "I" => "WNightrider.gif", "j" => "BGiraffe.gif", "J" => "WGiraffe.gif", "k" => "BKing.gif", "K" => "WKing.gif", "l" => "BLion.gif", "L" => "WLion.gif", "m" => "BKnightRook.gif", "M" => "WKnightRook.gif", "n" => "BKnight.gif", "N" => "WKnight.gif", "o" => "BKingRook.gif", "O" => "WKingRook.gif", "p" => "BPawn.gif", "P" => "WPawn.gif", "q" => "BQueen.gif", "Q" => "WQueen.gif", "r" => "BRook.gif", "R" => "WRook.gif", "s" => "BBerlinPawn.gif", "S" => "WBerlinPawn.gif", "t" => "BAmazon.gif", "T" => "WAmazon.gif", "u" => "BNightPrincess.gif", "U" => "WNightPrincess.gif", "v" => "BVao.gif", "V" => "WVao.gif", "w" => "BWazir.gif", "W" => "WWazir.gif", "x" => "BKnightKing.gif", "X" => "WKnightKing.gif", "y" => "BKingBishop.gif", "Y" => "WKingBishop.gif", "z" => "BZebra.gif", "Z" => "WZebra.gif" ); ?>
Piece Notation
Although the piece identifiers in most piece sets are selected for their suitability as notation, you may sometimes prefer to use different notation. Here are a few reasons you may want to do this:
- The piece identifiers were based on different piece names than the ones you're using.
- The piece set included multiple pieces whose names begin with the same letter but used alternate letters for some pieces.
- You want to use some kind of punctuation, such as a plus sign, to distinguish promoted pieces.
- You're using the "Alfaerie: Many" set, which is so huge, many piece identifiers use more than one letter or even include numbers or punctuation marks.
- You're using one of the automated sets, which are compiled from all the images in a directory and use modified file names for piece identifiers.
If the keys for the pieces you want to use already match the notation you want to use, you don't have to do anything further. But if any do not match the notation you want to use, you can use the alias command to change the notation used for a piece.
In some sets, the piece known as a Cardinal in Grand Chess is assigned to the letters a/A, which stand for Archbishop, another name for the same piece. To use the letters c/C for a Cardinal, you could use the following line:
alias c a C A;
This line makes c an alias for a and C an alias for A. When built-in functions return the internal identifier for this piece, they will return a or A, but instead of interpreting this as a raw value, code within this include file will apply the alias function to it to get the alias assigned to it. When an alias has not been assigned, its alias will be itself. So, the alias function can be used with any piece key to return the notation used for it in the game.
Moves will be entered with the notation instead of with the raw piece keys, because Game Courier has been programmed to translate aliases back into raw piece keys. Unless something is done to stop it, though, a player could enter raw piece keys and have them translated into themselves. To stop this, you should turn the raw piece keys you don't want players to enter into aliases for unassigned notation you won't be using in your game. For example:
alias a nn A NN
With this done, a move with the a or A piece will translate to nn or NN. As long as you do not use nn/NN as notation, entering a move with the a/A piece will result in an error.
Piece Definitions
Unlike earlier include files, this one has a large set of piece definitions. It's able to do this by changing the naming convention used for the functions and subroutines that define piece movement. Instead of naming them after raw piece keys or piece notation, it names them after common names for the pieces. Because of this, you will have to connect your notation to the codenames used to name the functions and subroutines used for different pieces. Here's an example for Grand Chess, which includes a Cardinal and Marshall in addition to the usual Chess pieces. This code connects the piece notation to the codenames used in piece definitions by creating constants. Each constant's name is the notation for a particular piece, and each one's value is the codename used in naming the code that defines how the piece moves.
setconst k King; setconst K King; setconst q Queen; setconst Q Queen; setconst c Cardinal; setconst C Cardinal; setconst m Marshall; setconst M Marshall; setconst r Rook; setconst R Rook; setconst b Bishop; setconst B Bishop; setconst n Knight; setconst N Knight; setconst p Black_Pawn; setconst P White_Pawn;
Why not use aliases to associate notation with codenames?
Since an unassigned alias will be itself, any notation that matches the internal piece identifier will already have itself as an alias. So, if aliases were used again for codenames, that would seriously mess things up. Also, for reverse lookup purposes, aliases have a two-way relationship with what they are aliases for. Reusing aliases to assign codenames would mess up the two-way relationship between notation and piece identifiers.
Why use constants to associate notation with codenames?
- Constants are normally supposed to keep the same values, and the relationship between notation and codenames is one that should stay fixed throughout the game.
- Since constants are in a different namespace than variables, using constants avoids conflicts with variables that have the same name.
- By using constants, the isconst function can be used to test whether notation for a piece actually matches a piece used in the game.
Despite this extra step, doing it this way has some advantages:
- For most pieces, a function or subroutine has to be defined only once.
- You get full control over which functions and subroutines can be called in your preset.
To identify the codenames to use for the pieces in your game, you should start by searching the include file for a name the piece is commonly known by. If nothing comes up, you can try a different name. Once you find something, you should verify that is the piece you need by either examining its code or reading the piece description for it. If you don't find anything, you can still use the code or the piece descriptions to help you identify the right codename to use.
Piece Names
If the codenames used in the include file match what you call the pieces in your game, you don't have to do anything more. But if you want a different name to show up in error messages and piece descriptions, you may specify it by making it an alias for the codename. In the example above, each Pawn has its own codename, but you may prefer for the name of Pawn to be used. Here's how to do that:
alias Pawn White_Pawn; alias Pawn Black_Pawn;
As in this example, it is okay to use the same alias for different codenames. While reverse lookup is important for converting piece notation to piece identifiers, there isn't the same need to convert display names to codenames.
Aside from this example, the pieces in Grand Chess all match the codenames used in the fairychess include file. So, let's switch to Grotesque Chess, which uses the same pieces but calls the Cardinal and Marshall the Equerry and the Guard. Here is how to handle that:
alias Equerry Cardinal; alias Guard Marshall;
Proper and Improper Use of the alias command
You may use the alias command to change the notation used for a label. This example is borrowed from Omega Chess. Since the piece used for the Champion has the piece label of s or S, it creates an alias of c or C to use in the notation for this piece. Since the four corner squares are identified as w1, w2, w3, and w4 in the game, but none of them appear in ranks 1-4, these are given as aliases for the invisible ranks and files used to locate them in the grid of the board.
alias c s C S w1 !x!1 w2 !y!1 w3 !y!2 w4 !x!2;
When you create an alias for a label, you can use another alias to keep the original label from being interchangeable with its alias in the notation. In this example, we want to keep Omega Chess players from using s or S for the Champion. Note that nn is a non-existent label, and this or another one may be used repeatedly when turning labels you don't intend to use into aliases that point nowhere.
alias s nn S nn
You can use aliases to provide a piece with a different display name than you would get from its code name. In some cases, the code will use a different codename for a piece than what it is called in your game. For example, Cardinal is the codename for a piece sometimes called an Archbishop. If your game uses the latter name, this code is appropriate:
alias Archbishop Cardinal;
In other cases, you may need different codenames for the same piece of each color but just want to refer to the piece by its usual name in text. This is true for Pawns in Chess, and this code makes Pawn an alias for both White_Pawn and Black_Pawn. This may seem counterintuitive, because the second alias will overwrite the first. This would matter if you entered the alias in your code or notation, because Pawn would return only Black_Pawn and not White_Pawn. In this case, though, the code will look up the alias for White_Pawn or Black_Pawn, and in each case, it will return Pawn.
alias Pawn White_Pawn; alias Pawn Black_Pawn;
You should not use aliases to connect your notation to your codenames. You should use constants for that. You should also not use constants or aliases to labels with names, labels with codenames, or notation with names. These relations are all taken case of by stringing together different relations.
Summary
- Piece identifier
- A key to the $pieces array, used internally by Game Courier to identify a piece. This value will be returned by some built-in functions, such as space, and by some system variables, such as $moved, which identifies the last piece moved, and $old, which identifies what used to be on the space just moved to.
- Code example:
$moved
- Piece notation
- The short string used to identify a piece in the moves that players enter. This value can be had by taking the alias of a piece identifier. When an alias has not been assigned, it will match the original piece identifier.
- Code example:
alias $moved
- Codename
- A unique designator for identifying the functions or subroutines used to define and describe a particular piece. Because a series of constants will be set that link notation to codenames, the codename for a piece can be returned using the const function on the notation.
- Code example:
const alias $moved
- Display name
- The name to be used for a piece in written text about it. This will normally be defined as the alias of a codename. When an alias has not been defined for a codename, the display name will match the codename.
- Code example:
alias const alias $moved
Pawn Settings
There are some Pawn settings that affect the Pawn's initial move, en passant, and promotion.
- bpr
- Black's Pawn Rank - 7 in Chess
- bprom
- An array of the pieces a Black Pawn may promote to. This will usually be set once in the Pre-Game code, but it may be set dynamically for games that allow promotion only to captured pieces.
- fps
- First Pawn Step. How far ahead a Pawn may move on its first move. 2 in Chess; 3 in Omega Chess. For values larger than 2, it will allow en passant capture on any passed over space that a Pawn could have captured it on.
- pzs
- Promotion Zone Size - measured in ranks. 1 in Chess; 3 in Grand Chess
- wpr
Checking the Legality of Moves
When a player makes a move, the move is normally made after the Pre-Move code and before the Post-Move code. Typically, for presets that use the fairychess include file, the Pre-Move code is left empty, and the Post-Move code is used to evaluate the legality of the move. This means that the move is made first, and then the code checks whether it was legal.
To help with this, the following system variables get set when a move is made.
- $dest
- The coordinate of the last space moved to.
- $moved
- The piece identifier for the last piece moved.
- $old
- The piece identifier for what was previously on the space last moved to. This will be @ if it was empty.
- $origin
- The coordinate of the last space moved from.
Using the values of $moved, $origin, and $dest, we can call a function or subroutine that checks the legality of a move. The value of $moved will direct us to the function or subroutine to use, and the values of $origin and $dest will be its arguments. Here is some sample code that handles this:
set codename const alias $moved; if sub #codename $origin $dest and issub #codename: elseif fn #codename $origin $dest and not issub #codename: else: set name alias #codename; set errmsg list "You may not move your" #name "from" $origin "to" join $dest ".
"; set desc join #codename "-Desc"; set errmsg str_replace "_" " " join #errmsg str_replace "%s" #name var #desc; die #errmsg; endif;
This code begins by retrieving the codename for the piece that moved. If this codename belongs to a subroutine, it calls it to evaluate the legality of the move. Subroutines are normally more complex and powerful than functions. So, when it comes to evaluating the legality of an actual move, subroutines take priority over functions. If the codename did not match a subroutine, then a function is used to evaluate the legality of the move. If the move was illegal, it exits with an error message that includes a description of how the piece moves.
Screening Out Generic Types of Illegal Moves
The functions and subroutines used for checking the legality of a move do not exclude all types of illegal moves. They are used mainly to tell whether a move is in line with the powers of movement of a piece. They do not concern themselves with whether you moved your opponent's piece, whether you tried to capture one of your own pieces, whether you entered GAME Code commands, or whether you entered multiple moves on one turn. All of this stuff has to be excluded separately. The include file handles some of this with these lines:
setsystem maxmove 2; ban commands allmoves; allow moves 1 captures 1 promotions 2;
The first line stops more than two moves from being entered. The second completely bans commands and all types of moves. And the third line creates some exceptions for moves and captures on the first move and for promotions on the second. This prevents you from entering two full moves unless you are promoting a piece. But there is still more to be done. The rest gets handled in the Post-Move sections. This code can appear at the beginning of Post-Move 1:
set piece alias $moved; if not isconst #piece: die The piece #piece is undefined.; elseif isupper alias $old: die You may not capture your own pieces.; elseif islower #piece: die You may not move one of your opponent's pieces.; endif;
This code screens out undefined pieces, moves that capture your own pieces, and attempts to move your opponent's pieces. So that it uses notation rather than piece identifiers, it assigns the notation to the #piece variable and uses that in place of $moved.
Since each piece must have a function defined for it, this code makes sure that. This is probably more useful for preventing bugs than it is for preventing illegal moves, but it is good practice to include it:
set codename const #piece; if not isfunc #codename: die The #piece piece is assigned to an undefined codename: #codename; endif;
In many Chess variants, it's also important to screen out promotions by pieces that are not Pawns. This code does that job:
if != #codename White_Pawn: set ep false; if != space $dest $moved: die You may not change the type of this piece.; elseif capture: set nopvc 0; else: inc nopvc; endif; endif;
The highlighted section shows the code that prevents promotions of non-Pawn pieces. Besides doing that, this code handles some other things that should be done for all non-Pawn moves. It sets ep to false to indicate that en passant capture is not possible, and it updates a variable that is used for enforcing the 50 moves rule.
If you wish, you may combine these last two blocks of code into a single if-elseif block.
Keeping the King from Moving Into Check
Another thing that makes a move illegal is moving the King into check. To check whether a move would place the King in check, the first step is to keep track of the King's position. This is done with the variables Kpos for White's King and kpos for Black's King. It is best to set these dynamically in case a preset is used with a fairy chess problem. Here's how to do this in the Post-Game code:
set kpos findpiece k spaces; set Kpos findpiece K spaces;
The next step is to test the King's position with the checked subroutine. This will return true if any piece checks the King, and it will otherwise return false.
if sub checked var Kpos: die You may not move into check.; endif;
Castling
To enable castling, you should first flag the spaces of any piece that may castle. In Chess, these would be the spaces that the Rooks and Kings are on. Flagging a space indicates that no piece has moved from that space. Because a preset could be used with a fairy chess problem, it might not always be used with the original starting position. Therefore, these spaces should be flagged conditionally. Here is code that works for Chess. It would have to be adapted for games with different starting positions. This code goes in the Pre-Game section, and what it does is flag each space that has the original piece on it. This will allow castling in fairy chess problems that still have the pieces on the right positions, and it will set all the flags for the normal starting position.
if == K space e1: setflag e1; endif; if == K space e1: setflag e1; endif; if == R space a1: setflag a1; endif; if == R space h1: setflag h1; endif; if == k space e8: setflag e8; endif; if == r space a8: setflag a8; endif; if == r space h8: setflag h8; endif;
The castle and castlepos subroutines may be used to handle castling as a single King move. The castle subroutine, which handles an actual castling move after the King has moved, normally gets called from the King subroutine. The castlepos subroutine, which checks whether a potential castling move is possible, is called from the stalemated subroutine. These subroutines normally take two arguments: the origin position of the King's move and the destination of the King's move. Based on these, they determine a direction of movement and find the first piece that the King may castle with in that direction. This will be the first piece found in that direction, and it must be on a flagged space. If all the usual castling conditions apply, these subroutines will complete the castle with that piece by moving it to the space on the other side of the King.
To determine the spaces where it is legal to castle, a couple variables are used:
- bcastle
- The spaces Black's King may move to when castling. c8 g8 in Chess.
- wcastle
- The spaces White's King may move to when castling. c1 g1 in Chess.
It is normally assumed that the King may not reach the spaces it may castle to when making a regular move. This allows the King subroutine to call the castle subroutine only when the move is not a normal legal move by the King. If there is any overlap between a King's normal legal moves and its castling moves, then castling should be treated as a two-part move.
Allowing Castling to Extra Spaces
This has been done in Devingt Chess, which allows the King to move two or three spaces toward the Rook when castling. On a 10x10 board with Kings on the e file, the relevant code looks like this:
set wcastle b1 c1 g1 h1; set bcastle b10 c10 g10 h10;
Flexible Castling
The rule of flexible castling, introduced in Grotesque Chess, is that the King may move two or more spaces toward the Rook when castling, and the Rook moves to the space immediately on the other side of the King. On a 10x8 board with Kings on the e file, the relevant code looks like this:
set wcastle b1 c1 g1 h1 i1; set bcastle b8 c8 g8 h8 i8;
Castling as a Double Move
Previous include files enabled castling as a single move by the King, and as described above, this include file has subroutines for doing that. In addition to these, it has subroutines for handling castling as a two-part move, such as "K e1-c1; R a1-d1". This can be useful in the following situations:
- When writing the castling move as a single move would conflict with some other legal move.
- When there are multiple castling moves available for the same single King move.
- When you would prefer the notation to include both moves just to make it easier to read.
The new subroutines for handling castling as a double move are castle2 and castlepos2. Since the stalemated subroutine calls castlepos, there is a new stalemated2 subroutine that calls castlepos2 instead. If you want castling handled as a double move, you should use these three subroutines instead of the usual ones.
But there is more to it than that. The castle2 and castlepos2 subroutines take twice as many arguments. Instead of just being fed the origin and the destination of the King's move, they are fed that plus the origin and destination of the piece the king is castling with, which is typically, though not always, a Rook. This is handled automatically for castlepos2 within stalemated2, but to use castle2 properly, you will have to do some things differently in the Post-Move code.
To spot and handle double moves, you can make use of the variables $prevcaptured, $prevmoved, $prevdest, and $prevorigin. These contain the previous values of $old, $moved, $dest, and $origin. These will normally have values related to the other player's last move, but for a double move, they will contain values related to the first piece moved on that player's turn. So, to check for a double move, you can check whether $prevmoved is a piece belonging to the player moving. In GAME Code, it would look like this for White:
if isupper alias $prevmoved:
You then want to check for the specific double moves allowed. For castling, this will be a move in which the King moves first and the piece it is castling with moves second. Since stalemated2 writes castling moves in this order, we will not accept castling moves written in the other order. The first thing to check for is whether the two appropriate pieces have moved:
if isupper alias $prevmoved: if == $prevmoved K and == $moved R:
Once this is established, we need to check for specific moves. This can be done the long way or the short way. For the long way, we would fully check both moves:
if isupper alias $prevmoved: if == $prevmoved K and == $moved R: if == $prevorigin e1 and $prevdest c1 and == $origin a1 and == $dest d1: gosub castle2 e1 c1 a1 d1; if == $prevorigin e1 and $prevdest g1 and == $origin h1 and == $dest f1: gosub castle2 e1 g1 h1 f1; else: die $moves "is illegal. Go back and try again."; endif; elseif ... endif;
For the short way, we just include enough details to distinguish each move, but it should remain thorough enough to not let through any illegal moves. This lets us omit anything that will be the same for any possible combination of moves. For castling in Chess, this is just the origin position for the King. So, we may write this:
if isupper alias $prevmoved: if == $prevmoved K and == $moved R: if $prevdest c1 and == $origin a1 and == $dest d1: gosub castle2 e1 c1 a1 d1; elseif $prevdest g1 and == $origin h1 and == $dest f1: gosub castle2 e1 g1 h1 f1; else: die $moves "is illegal. Go back and try again."; endif; elseif ... endif;
After this block, we need to check for pawn promotions. We do not need to handle pawn promotions here, since the Pawn subroutines will do that, but we do need to let them through, as anything else that is not a pawn promotion should get an error message.
if islower alias $prevmoved: if == $prevmoved k and == $moved r: if $prevdest c8 and == $origin a8 and == $dest d8: gosub castle2 e8 c8 a8 d8; elseif $prevdest g8 and == $origin h8 and == $dest f8: gosub castle2 e8 g8 h8 f8; else: die $moves "is illegal. Go back and try again."; endif; elseif != $prevmoved p or != $moved p or != $prevdest $dest: die "Except for castling or pawn promotion, you may not move twice on the same turn."; endif; elseif ...
At this point, the code is done with handling double moves, and it can proceed to handle single moves. That code, which was already described under Checking the Legality of Moves, will be included as part of the same if-elseif-else block like so:
if islower alias $prevmoved: if == $prevmoved k and == $moved r: if $prevdest c8 and == $origin a8 and == $dest d8: gosub castle2 e8 c8 a8 d8; elseif $prevdest g8 and == $origin h8 and == $dest f8: gosub castle2 e8 g8 h8 f8; else: die $moves "is illegal. Go back and try again."; endif; elseif != $prevmoved p or != $moved p or != $prevdest $dest: die "Except for castling or pawn promotion, you may not move twice on the same turn."; endif; elseif sub #codename $origin $dest and issub #codename: elseif fn #codename $origin $dest and not issub #codename: else: set name alias #codename; set errmsg list "You may not move your" #name "from" $origin "to" join $dest ".
"; set desc join #codename "-Desc"; set errmsg str_replace "_" " " join #errmsg str_replace "%s" #name var #desc; die #errmsg; endif;
One more thing is that the usual subroutine for the King handles castling with the castle subroutine. So, to handle castling as a double move, you should rewrite this subroutine to not handle castling:
sub King from to: verify fn King #from #to; if isupper space #to: set Kpos #to; else: set kpos #to; endif; return true; endsub;
Extended Castling
The rule of extended castling, introduced in Aberg's variation of Capablanca's Chess, is that the King may move to any space between itself and the Rook, and the Rook may move to any space the King passed over or moved from. Since this includes a choice of where each piece goes, it has to have both parts of the move written out. On a 10x8 board with Kings on the f file, here is what the relevant code looks like:
Pre-Move Code
allow moves 2; set wcastle (f1 g1 j1 f1) (f1 h1 j1 f1) (f1 h1 j1 g1) (f1 i1 j1 f1) (f1 i1 j1 g1) (f1 i1 j1 h1) (f1 e1 a1 f1) (f1 d1 a1 f1) (f1 d1 a1 e1) (f1 c1 a1 f1) (f1 c1 a1 e1) (f1 c1 a1 d1) (f1 b1 a1 f1) (f1 b1 a1 e1) (f1 b1 a1 d1) (f1 b1 a1 c1); set bcastle (f8 g8 j8 f8) (f8 h8 j8 f8) (f8 h8 j8 g8) (f8 i8 j8 f8) (f8 i8 j8 g8) (f8 i8 j8 h8) (f8 e8 a8 f8) (f8 d8 a8 f8) (f8 d8 a8 e8) (f8 c8 a8 f8) (f8 c8 a8 e8) (f8 c8 a8 d8) (f8 b8 a8 f8) (f8 b8 a8 e8) (f8 b8 a8 d8) (f8 b8 a8 c8);
Post-Move 1 Code
if isupper alias $prevmoved: if != $prevdest $origin and == $prevmoved K and == $moved R: if < $prevdest f1 and == $origin a1 and $dest <= f1 and $dest > $prevdest: gosub castle2 f1 $prevdest $origin $dest; elseif > $prevdest f1 and == $origin j1 and $dest >= f1 and $dest < $prevdest: gosub castle2 f1 $prevdest $origin $dest; else: die $moves "is illegal. Go back and try again."; endif; elseif != $prevmoved P or != $moved P or != $prevdest $dest: die "Except for castling or pawn promotion, you may not move twice on the same turn."; endif; elseif ...
Post-Move 2 Code
if islower alias $prevmoved: if != $prevdest $origin and == $prevmoved k and == $moved r: if < $prevdest f8 and == $origin a8 and $dest <= f8 and $dest > $prevdest: gosub castle2 f8 $prevdest $origin $dest; elseif > $prevdest f8 and == $origin j8 and $dest >= f8 and $dest < $prevdest: gosub castle2 f8 $prevdest $origin $dest; else: die $moves "is illegal. Go back and try again."; endif; elseif != $prevmoved p or != $moved p or != $prevdest $dest: die "Except for castling or pawn promotion, you may not move twice on the same turn."; endif; elseif ...
The Post-Game code differs only by using stalemated 2 instead of stalemated.
Evaluating Checkmate and Stalemate
To keep things simple and general, checkmate is understood to happen when a player's King is checked, and he has no legal moves. This is normally handled in the Post-Game sections with code like this:
if sub stalemated var kpos: if sub checked var kpos: say Checkmate! White has won!; won; else: say Stalemate! The game is drawn.; drawn; endif; elseif sub checked var kpos: say Check!; endif;
The stalemated subroutine has the extra task of computing all legal moves. So, it goes through every legal move instead of stopping the first time it finds one. This produces a list of legal moves that can be used for displaying legal moves and populating the Moves field with values to select from.
Defining Your Own Pieces
With the many pieces already defined in the include file, you might not need to define your own pieces. But in case you wish to write your own, or in case you wish to modify some aspects of existing piece definitions, it will help to know what goes into defining a piece.
First, every piece should have a function that checks whether the move is legal. This function should return a Boolean value of true or false. All this function has to evaluate is whether the powers of movement available to a piece would permit a move. It does not have to evaluate whether it would be illegal for other reasons, such as leaving the King in check. So, it's really only evaluating pseudo-legality. However, it will be a step in the process of fully evaluating whether a move is legal.
Normally, this function should return the same result whether the move has already been made or is only a potential move being considered. For some pieces, it is appropriate to write a subroutine that will be used for actual moves. This is sometimes because actual moves may require extra actions. The King and Pawn are examples of pieces with both a subroutine and a function. In this case, the subroutine will be used for actual moves, and the function will be used only for evaluating potential moves.
Second, each piece should have a range function. It's name should be the same as the other function with "-Range" appended to the end. A range function should return an array of the spaces a piece might potentially move to. This cuts down on the spaces that need to be checked for legal moves when going through potential moves. The range function should not concern itself with the legality of a move. It should return the same range whether a piece is blocked or free to move. It's okay to return a larger range than is needed, but it will cause problems if it returns a narrower range than the piece can move.
Third, each piece should have a piece description. This is a written text description that is stored in a variable named after the piece with "-Desc" appended to the end of the name. The description should tell how the piece moves, you may use %s in place of the piece name. This will get filled in by the piece name when it is displayed. This may be displayed when a player makes an illegal move. Also, when you neglect to write a description of the rules, your piece descriptions will be displayed in place of the rules. Even if you write up rules later, this can be useful for debugging your code while writing your preset.
In some cases, it will be necessary to write separate definitions for the White and Black versions of the same piece. This is usually necessary for pieces that move in one direction, such as the Pawn in Chess. In that case, you need to give them different codenames. In the fairychess include file, you will find examples that prefix one with "White_" and the other with "Black_". You should consistently use the same codename for the main function, the subroutine if needed, the range function, and the text description.
Revision in progress. Currently stopped here. Text of earlier version follows:
Identifying and Defining Pieces
There are four ways of identifying a piece. While this gives you more flexibility, it also makes it easier to introduce bugs into your code. So, it is important to be aware of the four different ways and to use the right one. I will term these internal label, notation, codename, and display name.
Internal Label
At the most basic level is the internal piece label. The two main places these appear in the code are as the keys of the $pieces array and as values of the $space array. The $pieces array comes from a set file, and it associates piece labels with piece images. Here is an example:
<? $dir = "http://www.chessvariants.com/graphics.dir/abstract/"; $pieces = array( "a" => "BKnightBishop.gif", "A" => "WKnightBishop.gif", "b" => "BBishop.gif", "B" => "WBishop.gif", "c" => "BCamel.gif", "C" => "WCamel.gif", "d" => "BWarmachine.gif", "D" => "WWarmachine.gif", "e" => "BElephant.gif", "E" => "WElephant.gif", "f" => "BFers.gif", "F" => "WFers.gif", "g" => "BGrasshopper.gif", "G" => "WGrasshopper.gif", "h" => "BHorse.gif", "H" => "WHorse.gif", "i" => "BNightrider.gif", "I" => "WNightrider.gif", "j" => "BGiraffe.gif", "J" => "WGiraffe.gif", "k" => "BKing.gif", "K" => "WKing.gif", "l" => "BLion.gif", "L" => "WLion.gif", "m" => "BKnightRook.gif", "M" => "WKnightRook.gif", "n" => "BKnight.gif", "N" => "WKnight.gif", "o" => "BKingRook.gif", "O" => "WKingRook.gif", "p" => "BPawn.gif", "P" => "WPawn.gif", "q" => "BQueen.gif", "Q" => "WQueen.gif", "r" => "BRook.gif", "R" => "WRook.gif", "s" => "BBerlinPawn.gif", "S" => "WBerlinPawn.gif", "t" => "BAmazon.gif", "T" => "WAmazon.gif", "u" => "BNightPrincess.gif", "U" => "WNightPrincess.gif", "v" => "BVao.gif", "V" => "WVao.gif", "w" => "BWazir.gif", "W" => "WWazir.gif", "x" => "BKnightKing.gif", "X" => "WKnightKing.gif", "y" => "BKingBishop.gif", "Y" => "WKingBishop.gif", "z" => "BZebra.gif", "Z" => "WZebra.gif" ); ?>
The board is represented by a one-dimensional associative array called $space. Each key is a coordinate, and each value is what's on the space, which may be a piece label, the @ sign for an empty space, or a - sign for non-space. With this in mind, the space function returns the value of $space for a given coordinate. When you're designing your game, you will determine the initial contents of the $space array through entering a variant of Forsythe-Edwards Notation, which typically uses a single letter to represent each piece. Here is what the FEN code for Chess looks like like:
rnbqkbnrpppppppp32PPPPPPPPRNBQKBNR
Starting from the top left, this describes what you find on each space. It uses lowercase letters for Black pieces, uppercase letters for White pieces, and integers to indicate a span of empty spaces.
Notation
At one level up is the notation. The default behavior is to use the piece label for entering moves, but you can change this with the alias command. When you create an alias for a label, you can use the alias to enter piece moves. In fact, it will automatically use the alias in the TITLE, ALT, and ID text of the pieces displayed on the board, and the JavaScript that lets you move by clicking or tapping on a piece will enter the alias in the move notation it generates for you. If you have the piece label, you can get the notation from the alias function. This will return the alias if you have created one, and it will otherwise return the piece label.
The main reason for using a label alias in your notation is that the label might not suit the piece in your game. While I normally create sets that use the first letter of a piece name for its label, there are sometimes conflicts between pieces starting with the same letter, or your game may use a different name for the piece than I based the label on. Besides that, some of the editors have put together a huge piece set called "Alfaerie: Many", which uses a large collection of obscure and sometimes punctuation infested symbols for piece labels. It's too large to show you the whole set, but here's a short segment:
"~p~!" => "../alfaeriemisc/chushogi/flip/bTokin-PromotedPawn.gif", "~P~!" => "../alfaeriemisc/chushogi/wTokin-PromotedPawn.gif", "~w~!" => "../alfaeriemisc/chushogi/flip/bKeigei-Whale.gif", "~W~!" => "../alfaeriemisc/chushogi/wKeigei-Whale.gif", "~wh~!" => "../alfaeriemisc/chushogi/flip/bHakku-WhiteHorse.gif", "~WH~!" => "../alfaeriemisc/chushogi/wHakku-WhiteHorse.gif", // alfaerie-style creations, happy or otherwise "_AC_te" => "../alfaerie-plus/btemplar.gif", "_ac_te" => "../alfaerie-plus/btemplar.gif", "_AC_TE" => "../alfaerie-plus/wtemplar.gif", "_AS_nw" => "../alfaeriemisc/sibahi/bwarlock.gif", "_as_nw" => "../alfaeriemisc/sibahi/bwarlock.gif", "_AS_NW" => "../alfaeriemisc/sibahi/wwarlock.gif",
While this allows the set to include many pieces, it would be unnatural to use labels such as these for your game's notation. After all, most games have fewer pieces, and when you consider only the pieces in your game, you should usually be able to assign a single letter notation to each piece. I generally recommend avoiding this set, but at least the fairychess include file helps you bring its messiness under control. There are also some automatic sets generated from the pieces available in a particular style. To accomodate large numbers of pieces, these base piece labels on file names. This is a less messy approach than alfaerie-many, but you would definitely want to set up aliases for your notation when using such a set.
One example of using an alias to change the notation comes from Grand Chess. This uses two new pieces called the Marshall, which moves as a Rook or a Knight, and the Cardinal, which moves as a Bishop or a Knight. If you examine the piece image names in the set above, m and M are keyed to the images to use for the Marshall, which fits with its name, but the c and C labels are associated with the Camel, and the image for the Cardinal is associated with a and A. To use c and C for the notation of the Cardinal, you can assign them as aliases for a and A, like so:
alias c a C A;
Note that the alias comes before the label it is an alias for, and the line includes two alias assignments.
Codename
The codename is the unique designation used in the code for the function (and sometimes subroutine) that will be called to check whether a move by a particular piece is legal. The include file has functions and subroutines for several pieces, and each one has to have a unique name that distinguishes it from the others. These will be described in more detail later. For now, it's enough to know how codenames get associated with notation. This is done by setting constants. Here is some code that associates the codenames and notation for the pieces in Grand Chess:
setconst k King; setconst K King; setconst q Queen; setconst Q Queen; setconst c Cardinal; setconst C Cardinal; setconst m Marshall; setconst M Marshall; setconst r Rook; setconst R Rook; setconst b Bishop; setconst B Bishop; setconst n Knight; setconst N Knight; setconst p Black_Pawn; setconst P White_Pawn;
The name of each constant is the notation used for the piece, and the value for each constant is the codename used for any function or subroutine that might be called to check the legality of its movement. In most instances, the notation is the same as the piece label, though it is different for the Cardinal. For the Cardinal, this code associates it with the letters c and C, which are aliases for the labels a and A. If you know the notation used for a piece, you can retrieve its codename with the const fuction.
The functions and subroutines defined in the fairychess include file cannot count on a piece having a particular label or notation. All they can count on is that a specific piece will have a specific codename. But pieces are stored in the code with labels, not with codenames. To get the codename of the piece at a particular coordinate, this expression will work in a function that has received this coordinate as its first argument:
const alias space #0
This expression anticipates aliases in case you use them, and it will also work if you don't use them. For an actual example, the White_Pawn subroutine uses this line when a Pawn has reached the last rank to test whether it has promoted. This code returns true if it is still a Pawn, which means it has not promoted.
if == White_Pawn const alias space #to:
In this code, space #to returns the label on the space the Pawn is moving to. Since this is being called after the move has been made, this will be the label for the Pawn or the label for what it has promoted to. The expression alias space #to returns notation for that piece. This won't be any different from the label in Chess, but it could be another game. Finally, const alias space #to returns the codename used for the piece, and this is compared with the codename used for White's Pawn, which is White_Pawn. It has to be distinguished from Black_Pawn, because different functions and subroutines are used for the Pawns on each side. An underscore is used to keep it a single word.
Display Name
Within the context of your game, you may want to use different names for your pieces than those that the codenames are based on. You can do this by creating an alias. For example, Grand Chess and Capablanca's Chess use the same two extra pieces, but they call them by different names. Since the code in this include file uses the names from Grand Chess, here is how you would set aliases for Capablanca's Chess:
alias Archbishop Cardinal; alias Chancellor Marshall;
In these lines of code, the alias comes first, and what it is an alias for comes second. So, this is telling it to use the display names of Archbishop and Chancellor for the pieces that the include file knows by the codenames of Cardinal and Marshall. When you associate notation with codenames for this game, you would still need to use the codenames. So, the code for Capablanca's Chess would also contain something like the following:
setconst a Cardinal; setconst A Cardinal; setconst c Marshall; setconst C Marshall;
But thanks to using aliases for these pieces, it would use the names these pieces are known by in Capablanca's Chess when referring to them in error messages about making illegal moves. To get the display name from a codename, you would use the alias function on it.
Even Chess has an example where you would prefer to use a different display name. Because different code is used for White and Black Pawns, they have different codenames. But you may want to refer to them simply as Pawns. You can use Pawn as the display name for both White_Pawn and Black_Pawn with this code:
alias Pawn White_Pawn; alias Pawn Black_Pawn;
Note that the alias command is normally setup to create a reciprocal one-to-one relationship between an alias and what it is an alias for. This is critical for piece labels and their aliases, because it sometimes has to derive the piece label from the notation. But this is not critical for codenames and their aliases. The only consequence of using the same alias for two different codenames is that the realname function is no longer useful. Given the code above, for example, realname Pawn will return Black_Pawn. If we associated notation with display names instead of with codenames, and we used Pawn as the display name for both White_Pawn and Black_Pawn, it would call the Black_Pawn function or subroutine for White's Pawn, and that would be an error. In order to use the same display name with different codenames, we cannot associate notation with display names, and we cannot use the realname function to return codenames from display names. Instead of that, the display name should be the end of the line. You can get a display name from a codename, but not vice versa. To get the codename, you need the notation.
Actual vs Potential Moves
The fairychess include file has to handle the moves the players actually make, and it has to handle the moves they might potentially make. The latter is useful for evaluating check, checkmate, and stalemate. To know whether the King is in check, you need to know whether another piece could capture the King, and to know whether a player has any legal moves, you need to know which moves pieces may make.
Game Courier allows code to be included both before and after a move is made. It has been most common to run code for evaluating the legality of a move after the move has been made. The main reason for this is that the move is now known, and variables like $origin, $dest, $moved, and $old get filled with values based on what the move was. Although it could be done differently, the fairychess include file follows the convention of evaluating actual moves after they have been made. This requires evaluation of the move from the post-move position. The main difference this makes for most moves is that the value of $old (or internally $lastcaptured) is consulted to determine whether the moving piece captured a piece on the same side. This can be done globally instead of putting it into the code for each piece. For example, the following segment of code is used in White's Post-Move section for Chess:
elseif isupper $old: die You may not capture your own pieces.;
Potential moves are evaluated mainly in the stalemated subroutine, and it too can use a global restriction to recognize that it is illegal to capture your own pieces. In the displayed code for checking the legality of moves, the highlighted part prevents moves that capture one's own pieces from being marked as legal:
// Can any piece legally move? for (from piece) fn friends: for to fn join const alias #piece "-Range" #from: if fn const alias #piece #from #to and not fn friend space #to and onboard #to: move #from #to; if not sub checked cond == #from #kingpos #to #kingpos: setlegal #from #to; endif; endif; restore; next; next;
Although potential moves are also calculated when determining whether the King is in check, the checked moves are limited to those between pieces on opposite sides. So, in this case, the code for the pieces does not need to prevent pieces from capturing allied pieces. Because this difference can be handled globally whenever it comes up, the same code can frequently be used to evaluate the legality of an actual move and the legality of a potential move. Moves by pieces such as Knights, Bishops, and Rooks can be handled with code that checks whether movement from one space to the other conforms to how that piece is allowed to move. Whether the move is being considered or has actually been made, the same values can be fed into the same function for the same result.
But there are instances in which it does make a difference whether the move is evaluated after it has been made or before it has been made. For example, a divergent piece captures differently than it moves. So, the code has to know whether the move was a capture. For actual moves, this is done by consulting the value of $old or by consulting the value of the capture operator, which indidates whether a capture has just been made. For potential moves, this is done by checking whether the space it is considering moving to is occupied. In the following code for the Cannon from Chinese Chess, the section for determining whether the move is a capture is highlighted. Using cond, which works like ?: in C or PHP, it first tests whether #0 (the space it is moving from) is empty. It should be empty for an actual move but occupied for a potential move. So, for an actual move, it returns the value of capture, and for a potential move, it returns whether #1 (the destination space) is occupied (not empty). Depending on whether it is a capturing move, the outer cond directs it to capture by hopping over a piece or to move like a Rook.
def Cannon cond cond empty #0 capture (not empty #1) (checkhop #0 #1 0 1) (checkride #0 #1 0 1) and #1;
Besides this difference, actual moves sometimes have side effects that potential moves do not. For example, a Pawn moving to the last rank may promote, but this is an irrelevant detail when all you need to know is whether a potential move of a Pawn to the last rank is legal. For these reasons, and maybe for others I haven't thought of, different code is sometimes required for potential moves and actual moves. The normal way to handle this is to use functions by default but to use subroutines for actual moves that need to be handled differently. Briefly, a function is a one line expression that takes arguments and returns a value, while a subroutine is a partitioned block of code that program flow can be redirected to. Functions are normally faster, but subroutines can do more. Subsequent sections will cover functions and subroutines in more detail.
A Real Example from Chess
Before learning about functions and subroutines, let's illustrate some of the things you've just learned with an example from Chess. This code is used in the Post-Game sections for Chess to test the legality of an actual move and to display an error message if the move is illegal. It will help you to understand this code and to include it in your own presets:
set codename const alias $moved; if sub #codename $origin $dest and issub #codename: elseif fn #codename $origin $dest and isfunc #codename and not issub #codename: else: set name alias #codename; set errmsg list "You may not move your" #name "from" $origin "to" join $dest ".<BR>"; set desc join #codename "-Desc"; set errmsg str_replace "_" " " join #errmsg str_replace "%s" #name var #desc; die #errmsg; endif;
Here's a walkthrough of what's going on. The first line sets the variable codename, which will be used multiple times. This is the codename for the piece that has moved. The $moved variable contains the label of the piece that just moved. The alias operator returns the notation that may be used in place of the label. These might be the same, but they may sometimes be different. Then the const operator returns the codename used as the function and subroutine names for this piece.
The next line tests whether #codename is the name of a subroutine, and if it is, it calls the subroutine to test whether the move is legal. It will be a subroutine if the actual move of the piece requires different code than the potential move of the piece does. In Chess, the King and Pawn use subroutines for their actual moves. If the subroutine returns true for legal, it goes to the end of the block. Otherwise, it runs the next line.
The third line starts by making sure that #codename is not the name of a subroutine. If it is, this means that the previous line failed because it called the subroutine for the piece, and it returned false for illegal. In that case, the move is illegal. Assuming it is not a subroutine, it then checks if the name belongs to a function, and if it does, it calls that function to check whether the move is legal. If it is not legal, it goes to the else block.
The first line in the else block assigns the display name for the piece to the variable name. In Chess, these are the same except for the Pawns. For these, it will convert the codenames White_Pawn and Black_Pawn to the display name Pawn. It uses the display name to compose the first line of an error message reporting that this move may not be made. It then assigns the name of the description variable to the variable desc, and the line after that uses it as a template for the part of the error message that describes how the piece moves. This template has %s wherever the piece name goes, and it replaces that with the value of #name. Finally, it replaces underscores with spaces in case the piece's display name is longer than one word, and it displays the error message as it exits the program.
Piece Functions
Each piece should be described by two functions. The first one is for determining whether a move made by the piece is legal. This function normally bears the name of the piece, though, as mentioned above, it has to be a unique codename, which may not match a piece name. The second returns an array of all the spaces the piece might possibly reach with its powers of movement. This has the same name with "-Range" appended to the end. This function is used to generate the spaces that legal moves will be checked on when it is checking for legal moves in the stalemated subroutine. While not strictly necessary, the range function saves time and overhead by saving the code from checking for legal moves to every single space on the board, and the code has been designed to expect it. Here is an example of these two functions:
def Rook checkride #0 #1 1 0; def Rook-Range rays #0 1 0;
To learn more about how to write these functions, see the following tutorials. Note that these are earlier tutorials that follow different naming conventions than the fairychess include file does.
- GAME Code Tutorial
- How to Enforce Rules in Game Courier
- How to Make Your Game Display Legal Moves in Game Courier
- Programming Piece Movement in Game Courier
Piece Descriptions
Each piece is also given a description. Instead of making this a comment, which would be useful only to someone reading the code, I have made it a string, which may be displayed when someone makes an illegal move with a piece. Its name will be the codename for the piece with "-Desc" appended to the end. Here is an example:
set Rook-Desc "The %s may move any number of spaces in any vertical or horizontal direction until it reaches an occupied space.";
Note the %s used in the description in place of the piece name. This is used to make it easier to use aliases for piece names. When the description is dislayed, each %s gets replaced with the display name.
Piece Subroutines
In some instances, a piece's movement has side effects. For example, the King's castling move also moves the Rook, the Pawn's en passant move may capture a piece on another space, or a Pawn may promote to another piece. Side effects are best handled by subroutines rather than functions. The difference between these is that a function is a one-line expression that returns a value, and a subroutine is comprised of multiple lines of code in another part of the program. The subroutine is used for checking whether a move is legal and producing side effects. It bears the same name as the function. This works, because functions and subroutines occupy different namespaces. For the King and the Pawn, and selected other pieces, a subroutine is used for the piece's actual moves, while a function is still used for that piece's potential moves. The actual moves are the ones that players actually make. Subroutines for actual moves may include error messages for illegal moves, but functions may not, because functions are always used for potential moves. The stalemated subroutine goes through every potential move, checking whether it is legal. It does this to check whether there are any legal moves and to compile a list of all legal moves, which will be used for displaying legal moves. So, every piece needs a function, but not every piece needs a subroutine.
If you're just using pieces already defined in the include file, you don't have to pay much attention to whether they use subroutines or just functions. The fairychess settings file for Chess includes code that checks whether a piece has a subroutine defined for it, and it will use that code for actual moves if it is available. If it isn't, then it will use the function.
More details on writing subroutines are given in these tutorials. Because they were written earlier, they name the subroutines after pieces labels rather than after piece names.
Breaking Logic
This is not about making logic broken. It is about using logical operators that break out of a function early, similar to using verify or a conditional return in a subroutine. While the normal use of a logical operator is to evaluate a logical relation between two operands, you may also use a short-cut version of the logical operator that takes only a single argument. This is a general feature of the language, but I will illustrate it with some examples from the fairychess include file.
Here is one way to write a function for the Cardinal, a compound piece that can move as a Knight or as a Bishop:
def Cardinal or fn Bishop #0 #1 fn Knight #0 #1;
This function calculates whether the move is a legal Knight move, then it calculates whether it is a legal Bishop move, and finally it passes the values from these two calculations to the or operator, which returns true if one of the two arguments passed to it is true. This works, but it could be done more efficiently with breaking logic. Here is how:
def Cardinal fn Bishop #0 #1 or fn Knight #0 #1;
In this function, it first checks whether the move was a legal Knight move, and if it was, it passes a single value of true to or, which will then exit the function and return the value of true. It can do this, because once we know it's a legal Knight move, we know the move is legal, and we don't have to check anything else. Exiting early saves time and is more efficient.
When used with Boolean values and given a single argument, or will work the same as unless. Given this, the following code would also work for the Cardinal:
def Cardinal fn Bishop #0 #1 unless fn Knight #0 #1;
This code checks whether it is a legal Bishop move unless it has already confirmed that it is a legal Knight move. Other than this kind of case, though, or and unless work differently. The former normally functions as a logical operator, while the latter was designed for breaking out of a recursive function with a meaningful return value. The way that unless generally works is that it takes two arguments, and if the first one is true, it returns the value of the second. It otherwise discards everything to the right and lets the function continue. When it gets passed only one argument, it returns its value if it is true, and it discards this result and lets the function continue if it is false. In that case, it returns true if the expression to its right evaluates to true and there is no other. Likewise, or immediately returns true if the value to its right is true, and it discards everything to the right and lets the function continue if the value to the right is false.
Breaking logic can also be used to skip an operation that should not be performed. In the example from the previous section, this line appears:
if sub #codename $origin $dest and issub #codename:
This expression first checks whether the value of #codename is the name of a subroutine. If it's not, the expression issub #codename returns a false value to the and on its left, and that and exits the expression with a value of false. It is important to exit early here, because if #codename is not the name of a subroutine, then the code should not be trying to call a subroutine with the name stored in #codename. The line above works like the code below:
if issub #codename: if sub #codename $origin $dest:
This illustrates that breaking logic is about using conditionals in expressions despite the lack of an if statement in expressions. When onlyif is passed a single argument, and that argument is a Boolean value, it works the same as and with a single value. Thus, the following code will work the same as the code shown above:
if sub #codename $origin $dest onlyif issub #codename:
This code will call sub #codename only if #codename is the name of a subroutine. Like unless mentioned earlier, onlyif was designed for breaking out of recursive functions, and it otherwise operates differently than and, which is designed to be a logical operator. When onlyif has two arguments, it checks the value of the first one, and if it is false, it returns the value of the second. When it has only one argument, it exits the function and returns it if it is false. But if it is true, it discards everything to the right and lets the function continue. Likewise, and immediately returns false if the single value to its right is false, and it discards this result and allows the expression to continue if the value to the right is true.
The next line is a little more complicated, using two breaking ands:
elseif fn #codename $moved $origin $dest and isfunc #codename and not issub #codename:
The expression on this line exits early with a value of false if it's false that #codename is not a subroutine. Note that there are two negatives here. A double negative is true, and what this is really testing for is whether #codename is the name of a subroutine. If it is, it has already called the subroutine in the line above, and there is no need to call the function too. So, it exits early. It then has one more test before calling the function. It checks whether #codename is a function name. If it is not, then it exits with a value of false before getting to the code for calling the function. After all, you don't want to call a function that doesn't exist. Note that the following is equivalent:
elseif fn #codename $moved $origin $dest onlyif isfunc #codename onlyif not issub #codename:
Now that I've shown you some basic examples, let's look at a complex one that uses multiple breaking ors and ands. Here is the function used for the White Pawn:
def White_Pawn remove var ep and < rankname #1 var bpr and < rankname var ep rankname #1 and == filename var ep filename #1 and checkleap #0 #1 1 1 and var ep or and checkride #0 #1 0 1 == rankname #0 var wpr or checkleap #0 #1 0 1 and empty #1 and != var movetype CHECK or and islower space #1 checkleap #0 #1 1 1 and any onboard where #1 0 1 == var movetype CHECK count var wprom and <= distance #0 #1 var fps and > rank #1 rank #0;
To make it easier to follow the logic, this code is broken into multiple lines. The interpreter does not require this and could just as well read it as one long line. The way I have broken it into lines, each line but the first, which is evaluated last, passes a single value to and or or, giving each of these lines the opportunity to exit the function early. As I go over the lines of this function, bear in mind that it is more general-purpose than what is required for Chess. It allows initial moves that are longer than two spaces, it allows en passant capture after these longer moves, and it accomodates games like Grand Chess, which allow promotion only to captured pieces. Since GAME Code evaluates expressions from right to left, we will begin at the end.
and <= distance #0 #1 var fps and > rank #1 rank #0;
The last two lines both begin with and. Each one is testing for a condition that must be true for any Pawn move to be legal. It first tests the direction of the Pawn move. If it is not moving forward, it returns false and exits. It then tests the distance of the Pawn move. If it is further than the maximum distance allowed for its first move, which is in the variable fps, it is illegal and returns false right away. The and operator works similarly to the verify command available for subroutines, which will exit from a subroutine with a false return value if the expression it is given to evaluate is false. So, these two lines above are equivalent to these two lines from the White_Pawn subroutine:
verify > rank #to rank #from; verify <= distance #to #from #fps;
The next line it comes to also begins with and, but it has more going on. For games like Grand Chess, promotion is allowed only to captured pieces, and it is illegal to move to the last rank unless there is something to promote to, though checking the King on the last rank is still allowed. This line enforces that rule in a way that is compatible with games like Chess, which allow unlimited promotion. It uses any to check three conditions at once. It could use or twice, but it uses any, which returns true if anything to its right is true. This works, because everything past this line has already been discarded, and the only values left to the right of any are the three I have put on the same line with it.
and any onboard where #1 0 1 == var movetype CHECK count var wprom
The first one it checks is whether there are any pieces a Pawn may promote to. This is stored in wprom, which may be set dynamically for games that restrict promotion to captured pieces. For Chess, it is set once and remains the same throughout the game. So, for Chess, it will always be true. In case it is not true, it also checks whether the function is being used to test whether the Pawn can check a King, and it tests whether there is another rank beyond the destination. If it has not yet reached the last rank, no promotion is required, and the move could still be legal. And even when a Pawn can't move to the last rank, it can still check a King there. If either of those conditions hold, then the move could still be legal, and the function continues. But if they are all false, the and at the beginning gets passed a single false value, and it exits the function with a value of false.
or and islower space #1 checkleap #0 #1 1 1
We now come to a line beginning with or. This line also shows an example of an and being used with two arguments in a non-breaking way. If it was a diagonal move AND it was to a space occupied by an enemy piece, then it is legal, and or will immediately exit the function and return true. But if both of these conditions are false, it could be a regular non-capturing move, and if it's a diagonal move to an empty space, it could be an en passant capture. So, if this conjunction is false for any reason, it needs to check more conditions to determine whether the move is legal.
and empty #1 and != var movetype CHECK
These two lines test for two conditions that must be true for the function to continue. If the value of movetype is CHECK, the function is being used in the context of checking whether the Pawn is (or would be) checking an enemy King, and in that context, all the work it has to do is already done. If it has gotten this far, the Pawn is not checking the King or threatening check on its destination, and it can return false right away. Also, every subsequent type of move it will be checking for has to be to an empty space. If the move is not to an empty space, the function needs no more information to tell whether the move is illegal, and it can return false right away.
or checkleap #0 #1 0 1
It then comes to an or statement, which checks whether the Pawn's move is one space forward. If it is, it is legal and returns true right away.
or and checkride #0 #1 0 1 == rankname #0 var wpr
This next or statement shows another example of where and is used with two arguments in a non-breaking manner instead of with one argument in a breaking manner. This is checking whether the move is from White's Pawn rank (wpr) and whether the Pawn moves as a Rook. If both conditions are true, then it's a legal double move (or triple or whatever your particular game allows), and the function can return true right away. But if both conditions are false, it could be an en passant capture, which is what it will check for next if either one is false.
and var ep
This line checks the variable ep and returns false right away if it is false. When a Pawn makes a double move (or longer move in some games), the space it moved to gets stored in ep. Otherwise, ep gets set to false. So, when ep is false, there is no legal en passant move, and there is no point in checking for one.
and < rankname #1 var bpr and < rankname var ep rankname #1 and == filename var ep filename #1 and checkleap #0 #1 1 1
These four lines check for conditions that must be met by a legal en passant move. It first checks whether the move is a one-space diagonal move. If it is, it makes two comparisons between the coordinate stored in ep and the destination. If both coordinates are in the same file, it can proceed. Otherwise, it is not a legal en passant move, and it returns false right away. Assuming they are in the same file, it then checks whether the move is further ahead than the enemy Pawn. If it is not, it returns false right away. Finally, it makes sure that the move has not reached or gone past Black's Pawn rank (bpr). If it has gone that far, then it is not a legal en passant move, and it returns false.
remove var ep
If all conditions for en passant have been met, it reaches the first line. This line removes the Pawn at the location stored in ep. Even though this function is used only for potential moves, an en passant capture could reveal a check, and this has to be tested for when a Pawn can make an otherwise legal en passant capture. The stalemated subroutine will handle this, then restore the board to its original configuration.
Questions
Do you have any questions about using the fairychess include file? Ask them on this page.
Written by Fergus Duniho
WWW Page Created: 29 March 2020