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. It gets its name from fairy chess instead of from a particular Chess variant, because it includes a large collection of piece definitions. Let's get started with looking at how it works.
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.
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:
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.
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.
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.
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.
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
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.
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.
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.
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