How to Make Your Game Display Legal Moves in Game Courier - A Tutorial
- setlegal from to
- setlegal from to1 to2 ... ton
- setlegal from1 (to1-1 to1-2 ... to1-n) from2 (to2-1 ... to2-n)
- setlegal from (expression)
- Given a pair of coordinates, setlegal will store a legal move made from the first coordinate to the second. Given more than two coordinates, setlegal will store multiple legal moves from the first coordinate to each of the following coordinates. Given a coordinate and an array of coordinates, setlegal will store multiple legal moves from the first coordinate to each coordinate in the array, and anything past the array will be evaluated separately. Given a coordinate and an array with a GAME Code expression in it, it will evaluate that expression, and if it is a single coordinate or an array of coordinates, it will behave as though it had been passed the value of the expression directly.
Prior to adding the ability to display legal moves, I had already written subroutines that checked for checkmate and stalemate in games. These were already designed to potentially go through every possible move, for checkmate and stalemate could be identified only by determining that there were no legal moves, but they had been optimized to exit as soon as any legal move was found. Instead of writing new subroutines for recording legal moves, I rewrote these to go through all legal moves, building a record of any they found. If your game is sufficiently similar to Chess, you may include these subroutines in your own code without revising them. While some games do use separate subroutines for checkmate and stalemate, the code we will be looking at in this tutorial uses a single stalemated subroutine for both. To check for checkmate, you just need to check for check plus stalemate.
The code in this tutorial comes from the chess3 include file. I wrote this include file while writing the tutorial I wrote after this one, How to Enforce Rules in Game Courier. It is very similar to the code I originally used in this tutorial, and I had to make only minor fixes to update this tutorial to use the new code. When you wish to modify the code for Chess to program another game, I recommend using the chess3 include file and the Chess3 settings file rather than any earlier code, which is being kept around only to provide legacy support to games that are using it. This code is less optimized, but this is to give it the advantage of being more easily adaptable to other games. Another advantage of the new code is that I rewrote it to make it easier to follow and understand.
The stalemated Subroutine
The stalemated subroutine is very generalized. It cycles through legal moves in two main steps. First, it cycles through all possible moves except castling moves, then it handles castling separately. The code for going through regular piece moves looks like this:
store; for (from piece) fn friends: for to fn join #piece L #from: if fn #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;
First, it uses the
store command to store details about the current position. This tells the
restore command, used a little later on, what position to restore the board to. The reason for using these two commands is that the code will move pieces, check whether the King is checked in the new position, then return to the original position to check for more legal moves.
To go all through all possible regular moves, it nests two for-next loops. The outer loop goes through the elements of an associative array created by the function
friends. Game Courier represents the entire board with the
$space array, each key being a coordinate, and each value designating what is on the space. The
friends function returns the portion of this array with pieces from the same side on it. So that it can use both the key and the value of each array element, it puts the variables
piece in parentheses. It passes the key to
from and the value to
piece. The inner loop cycles through all the spaces a piece may move to. It needs only the values of the array, which it passes to the variable
to. To determine which spaces a piece may move to, it calls a function partially named for the piece, and it gets the function name by using the
join keyword to concatentate the piece label with a capital L. The functions used for the pieces in Chess are these:
def PL array where #0 0 2 where #0 0 1 where #0 -1 1 where #0 1 1; def pL array where #0 0 -2 where #0 0 -1 where #0 -1 -1 where #0 1 -1; def NL leaps #0 1 2; def BL rays #0 1 1; def RL rays #0 1 0; def VL rays #0 1 1; def CL rays #0 1 0; def QL merge rays #0 1 0 rays #0 1 1; def KL merge leaps #0 1 0 leaps #0 1 1; def nL leaps #0 1 2; def bL rays #0 1 1; def rL rays #0 1 0; def vL rays #0 1 1; def cL rays #0 1 0; def qL merge rays #0 1 0 rays #0 1 1; def kL merge leaps #0 1 0 leaps #0 1 1;
Although most of these functions duplicate each other for both black and white pieces, different names are used for each side because the Pawn functions are different from each other, and this streamlines the process of picking the right function for each piece. There are three main built-in functions used to calculate these positions.
where function returns the coordinate of the space that can be reached with a step of so many ranks and files away from the space designated by #0. The number of files moved is given first, and the number of ranks second. Negative numbers are used for moving so many files to the left or so many ranks backwards. In Chess, this is used only for Pawns, because Pawn movement is not symmetrical. For pieces with fully symmetrical movement, there are shortcuts to calculating all the spaces they may move to.
leaps function returns all the spaces a piece may reach from a given position by making a single leap as many ranks and files away as the two numbers following the coordinate. It will alternately use each number for ranks and files, and it will alternately make each negative or positive. So you don't have to enter negative values or concern yourself with the order that the numbers are entered. So,
leaps #0 1 0 returns all the spaces that may be reached by moving one space vertically (plus none horizontally) or one space horizontally (plus none vertically). These are the four spaces that can be reached by moving one space orthogonally. The
leaps #0 1 1 expression returns all the spaces that can be reached with a one-space diagonal move. The
merge keyword merges two arrays together, returning their union. So,
merge leaps #0 1 0 leaps #0 1 1 returns all the spaces adjacent to a space.
rays function works much like the
leaps function except that it returns every space that might be reached through a series of consecutive leaps in the same direction. For Bishops, Rooks, and Queens, this returns every space in the ranks, files, or diagonals these pieces may move through.
The first line inside the inner for-next loop is an if-statement. This if-statement tests for three conditions. Note that GAME Code evaluates expressions backwards. So, the first thing it checks for is whether the space is on the board. Although the functions I mentioned will not return coordinates that do not exist at all, they may return coordinates that have been removed from the board grid by placing a hyphen in the FEN code. The
onboard operator will return true for any space that is occupied or empty but not for any that has been removed from play.
The next thing it checks for is whether the space is occupied by a piece belonging to the same side. It does this with the expression
not fn friend space #to. The
friend function has been defined in the subroutine depending upon whose turn it is. This expression returns true if the space is empty or is occupied by an enemy piece.
The last thing this line checks for is whether the move can be legally made. This is done with
fn #piece #from #to. The
#piece variable names the function to use. Functions for testing the legality of individual moves have been named for the piece labels. Here are the functions for the pieces used in Chess:
def N checkleap #0 #1 1 2; def B checkride #0 #1 1 1; def R checkride #0 #1 1 0; def Q fn B #0 #1 or fn R #0 #1; def n checkleap #0 #1 1 2; def b checkride #0 #1 1 1; def r checkride #0 #1 1 0; def q fn B #0 #1 or fn R #0 #1; // PAWNS // These are for checking the legality of possible Pawn moves. // Subroutines by the same name are used for actual moves. // This function may remove a Pawn captured by en passant, // but it will never do so unless it finds a legal en passant move. // Variables should be designated with var to use the current value. def P remove var ep and < rankname #1 var bpr and < rankname var ep rankname #1 and == filename var ep filename #1 or and checkride #0 #1 0 1 == rankname #0 var wpr or checkleap #0 #1 0 1 and empty #1 or and islower space #1 checkleap #0 #1 1 1 and <= distance #0 #1 var fps and > rank #1 rank #0; def p remove var ep and > rankname #1 var wpr and > rankname var ep rankname #1 and == filename var ep filename #1 and checkleap #0 #1 1 1 or and checkride #0 #1 0 1 == rankname #0 var bpr or checkleap #0 #1 0 1 and empty #1 or and isupper space #1 checkleap #0 #1 1 1 and <= distance #0 #1 var fps and < rank #1 rank #0;
Most of these functions are simple, making use of checkleap or checkride. The former checks whether the move from the piece's position to the new position is made with a single leap whose distance can be measured as so many ranks and files away. This works fully symmetrically, so that, for example,
checkleap #0 #1 1 2 will check whether a move is any of the eight legal moves available to a Knight. Because it is symmetrical, it doesn't matter which order you put the numbers in, and there is no need to use negative numbers for left or backwards movement. The
checkride function works just like
checkleap except it checks for any move that can made through a series of consecutive leaps in a straight line, so long as each leap but the last is to an empty space. This makes it useful for checking Bishop, Rook, and Queen moves. The Queen's function is defined in terms of the functions for the Bishop and Rook, since it can move as either piece.
The Pawn functions are more complicated, because Pawn movement is more complicated, and they differ from each other, because each side's Pawns move in different directions. Each one checks a series of conditions, beginning with the last condition in the definition. When passed a single argument, the
and keyword will immediately return false if it is false, and the
or keyword will immediately return true if it is true. Otherwise, it will continue to the next condition. The first thing it checks is whether the move is forward. It then checks if it is an ordinary capturing move, returning true if it is. If not, it verifies that the space is empty, returning false if it is not. It then checks if it is a one space forward move, and if not a double move. Finally, it checks if it is a legal en passant capture, removing the captured Pawn if it is.
If these three conditions are met, it then makes the move, checks whether the King is in check in the resulting position, and stores it as a legal move if the King is not in check. To check whether the King is not in check, it uses the expression
not sub checked cond == #from #kingpos #to #kingpos. This calls a subroutine that takes the King's position as a parameter. The expression
cond == #from #kingpos #to #kingpos is used to determine the King's current location. The
cond operator works like ? and : do in other languages. It takes three expressions, returning the second expression if the first expression is true, otherwise returning the third. So, this tests whether the piece moved from the King's position, and if it did, the King is the piece that moved, and it passes the position the King has just moved to. Otherwise, it wasn't the King that moved, and it passes the position recorded for the King. The
checked subroutine cycles through all enemy pieces, checking whether any of them can move to the King's position. When the piece can make a legal move without putting the King in check,
setlegal is used to record it. After checking whether the King is in check in the new position, the board position gets restored. The subroutine for checking whether the King is in check looks like this:
sub checked king: my from piece; if isupper cond empty #king moved space #king: def enemies onlylower; else: def enemies onlyupper; endif; for (from piece) fn enemies: if fn #piece #from #king: return #from; endif; next; return false; endsub;
For this subroutine,
king is the position of the King. First, it checks whether the king is white (uppercase) or black (lowercase), and it defines the enemies function to whichever is the other side. Just in case this subroutine is being used for an empty space, it checks the case of the last piece moved, using the
moved keyword. Otherwise, it uses the case of the piece at the specified position. The
moved keyword returns the identity of the piece last moved, and the
space #king expression returns the value of the $space array whose key matches the coordinate given by #king. The $space array is used to represent the board, and its values indicate what is on each space.
With the proper
enemies function defined, it uses a for-next loop to go through all enemy pieces. The pieces are stored in an associative array, where the coordinate is the key, and the piece label is the value. To get both key and value from the array, variables for both are put in parentheses following
for. The key goes in
from, and the value goes in
piece. It then uses the value of
piece as a function name, calling that function to check whether the piece can move from its current location to the King's location. If it finds one that can, it returns its coordinate, which will be interpreted as a true value. If it cannot find one, it will return false.
Let's now return to the
stalemated subroutine. After checking for regular legal moves, it checks whether the King can castle. Here is what the code for this looks like:
for to var cspaces: if sub castlepos #kingpos #to: if not sub checked #to: setlegal #kingpos #to; endif; endif; restore; next;
cspaces variable is set within the subroutine to match the value of
bcastle, depending upon which side the King belongs to. These variables each contain an array of the spaces a King may move to when castling. For games with different setups or board sizes, these variables can be changed to match the castling rules for those games. For each position in the
cspaces array, it checks whether the King can castle there, making use of the
castlepos subroutine. This name is short for castling is possible. Since this subroutine makes the castling move, it is important to use restore after calling it and checking whether the King is checked in the resulting position.
One last thing to do is to plug the stalemated subroutine into the program Game Courier will build for your game. This subroutine should go into the Post-Game sections, and it should be used like so:
if sub stalemated #k: if sub checked #k: say Checkmate! White has won!; won; else: say Stalemate! The game is drawn.; drawn; endif; elseif sub checked #k: say Check!; endif;
To display legal moves, it is critical that the stalemated function be called every time this code is run. This is why it is called first. Prior to writing code for displaying legal moves, I used code like this, and it even got into an earlier version of this tutorial:
if sub checked #k: if sub stalemated #k: say Checkmate! White has won!; won; else: say Check!; endif; elseif sub stalemated #k: say Stalemate! The game is drawn.; drawn; endif;
When you're not calculating all legal moves, this code is more optimized. But when your aim is to find all legal moves, this will fail whenever a King is checked but not checkmated.
What I have described here is really all you need to do to get Game Courier to display legal moves in your game. If you include extra pieces, you may have to write new functions for them, or you could borrow code from games that already use these pieces. Besides making your game display legal moves, you may want to make it enforce legal moves. What you've learned here will help you with that, but there is more involved than just displaying legal moves. This is covered in the tutorial How to Enforce Rules in Game Courier.
Written by Fergus Duniho
WWW Page Created: 27 November 2015