How to Enforce Rules in Game Courier - A Tutorial
When you play games on Game Courier, you may notice that some games enforce the rules, and some don't. This is because rule enforcement has to be programmed on a game-by-game basis. After all, different games use different rules, boards, and pieces. What may discourage some from programming rules for games is the idea that you have to do everything from scratch. This isn't true. I have already done much of the programming for you, and all you need to do for most Chess variants is put together some already written pieces of code.
In this tutorial, I will go over what some of these are and explain what goes into programming the rules for a game. Rule enforcement is written in the GAME Code language. This is a Turing-complete, interpreted, server-side language I designed for Game Courier. It includes all the math, logic, control structures, and whatnot that you are familiar with from other languages, except that it will not let you read or write files, it is not object-oriented, and it is focused on controlling a board game environment, not the general contents of a webpage. Unlike PHP, which it is written in, it is designed to be safe for visitors to the site to use without compromising its contents.
To enforce the rules of a game, Game Courier builds and runs a computer program that evaluates all the moves and the current position. This program is built from code that you insert in up to seven main areas of a game's preset. These sections are run at different points in the game. The Pre-Game code is run first. The Pre-Move and Post-Move code for a particular side is run everytime that side moves. Each side has its own Pre-Move and Post-Move sections. Finally, the Post-Game section for the last side to move is run after the completion of all moves. Although there are seven possible places to put code, there is usually no need to use all seven. For most of the games I've programmed, I just use Pre-Game, Post-Move, and Post-Game sections. Since the two Post-Move sections and the two Post-Game sections use very similar code, it is mainly a matter of coding three sections.
The Pre-Game Section
The Pre-Game section is where you would set flags and variables and define functions and subroutines. It is also where you would include files. One of the most important things to know is that I have written several include files for various games, and instead of writing all your code from scratch, you can include files that already contain much of the code you need in them. In this tutorial, we will be examining code in the chess3 include file. This is a modification I made to chess2 while writing this tutorial. I decided to streamline chess2 to make it even more logical and easier to understand, but since some of my changes would break compatibility with games making use of it, I decided to increment the version number. I had originally written chess2 to provide an include file for Chess that could more easily be reused for more variants than the more optimized include file I had previously written for Chess. Like its predecessor, chess3 is designed for general use with multiple variants. Anyway, here is the code that goes into the Pre-Game section for the Chess preset that uses it. As you go through this tutorial, it will help to keep these in separate tabs or windows for comparison.
if == thismove null: say This preset enforces the rules and displays legal moves.; endif; echo Please report any bugs or errors to Fergus Duniho; setflag a1 a8 h1 h8 e1 e8; set k findpiece k spaces; set K findpiece K spaces; set ep false; include chess3;
First, there is a message to potential players. This message is shown with the say command, which displays to the players the last message given to it. So, if I were to add another say
command saying something else, it would override this one. The say
command is enclosed in a conditional block. This begins with if
and ends with endif
. The condition tests whether thismove is equal to the null string. If it is, that means no one has moved yet. So this conditional is set up to display the message at the beginning of the game but not after it starts. Since there is no meta-code that can analyze whether a preset enforces the rules, I figured it is helpful to say so explicitly. The best Game Courier can do on its own is detect and report presets that are clearly not programmed, because no code has been written in the relevant places.
Next, there is a message written with the echo command. This will be visible in the source code, but it will not be visible to the player unless there is a problem causing the GAME Code program to exit prematurely. In general, you can use echo
to print multiple debugging messages in your code in case it is not working right. Unlike say
, this command is not intended for sending messages to the player.
The setflag command sets flags for the spaces that the Kings and Rooks begin on. This is to mark that these pieces have not moved yet, which is important information for determining whether castling is legal. If any Kings or Rooks move later in the game, or any Rooks get captured without moving, the flags on their spaces will be unset.
Values for k
and K
are set by finding where the Black King (k
) and the White King (K
) are on the board. This is done with the built-in function findpiece, which takes two parameters, the name of the piece and the area to search through. The area mentioned here is spaces, which returns the $space
array. This is an associative array representing the entire board. Each key is a coordinate, and each value is the contents of a space, either a piece label, an @
for an empty space, or a -
for a deleted space. The program will use the variables k
and K
to keep track of the Kings, so that it can check whether a King is in check. Although I could have just assigned these variables to e8 and e1, using findpiece
is essential for using this code with Fairy Chess problems, in which the Kings could be anywhere on the board.
The ep
variable is used to record the coordinate of the space where there is a Pawn that may be captured by en passant. When there isn't one, it is set to false.
The include command is then used to insert the code contained in the chess3 include file. This will be appended to the end of the program, and execution of the program will continue there. Execution will resume here after it goes through the code in the include file. This includes some subroutines and functions that I'll describe as we get around to using them. For now, I will mention a few things it does at the beginning. First, it sets some variables with the set command:
set wpr 2; // White's Pawn Rank set bpr 7; // Black's Pawn Rank set fps 2; // How many spaces a Pawn may move on its first turn set pzs 1; // Promotion Zone Size - Number of ranks in promotion zone set wcastle c1 g1; // Where White's King can move when castling set bcastle c8 g8; // Where Black's King can move when castling set wprom Q R B N; // Pieces White Pawns may promote to set bprom q r b n; // Pieces Black Pawns may promote to
These variables are used in some of the functions or subroutines. You can change their values after you include the include file. If the Pawns in your game began on different ranks, or the promotion zone was larger than one rank, or Pawns promote to different pieces, or the King moved to different spaces when castling, you could accommodate these differences simply by changing the appropriate variables.
Next are some commands for restricting user input. Game Courier allows a player to enter a move by entering a number of lines in the GAME Code language, each separated by a semicolon. But the code for rule enforcement does not take all such possibilities into account. So that the code for enforcing rules doesn't have to get overly complicated, the code players are allowed to enter for making moves should be restricted. See Restricting User Input.
setsystem maxmove 2; ban commands allmoves; allow moves 1 captures 1 promotions 2;
The first line here restricts the length of the move. One way to make an illegal move would be to enter several moves at once, such as "P e2-e4; P d2-d4; P b2-b4;
". This limits this to, at most two moves. It does this by changing the value of one of the PHP variables Game Courier makes use of itself. The setsystem command allows you to change the values of selected variables used in the code for Game Courier.
The next line uses the ban command to ban GAME Code commands from user input. This prevents players from making moves by writing computer programs. This line also bans every type of move. Between banning commands and banning all moves, it bans all user input.
But that's okay, because the last line makes a few exceptions with the allow command. It allows moves and captures on the first part of the move, and it allows only promotions on the second part. So, a player could enter something like "P e7-e8; Q-e8" for advancing a Pawn to the eighth rank and promoting it to a Queen, but he could not move a piece twice, promote a piece then move it, or move two different pieces. Although you might expect that castling and en passant would be written in two parts, they will each be written as a single move by one piece. So no moves or captures need to be allowed for the second part of the move.
The Post-Move Sections
The main job of rule enforcement happens in the Post-Move sections. After a new move has been made, the code in this section should be used to evaluate that particular move. This will involve testing whether the piece moved according to its legal powers of movement and whether the new position puts the King in check. Some other things we could do after each move, such as testing whether anyone has won, will be saved for the Post-Game sections, which we'll cover later. As we learn how to enforce legal piece movement in the Post-Move section, I will also fill in what is needed in the Pre-Game code. The first part of the Post-Move code for White looks like this:
if isupper old: die You may not capture your own pieces.; elseif islower moved: die You may not move one of your opponent's pieces.; endif;
This begins with a conditional block. Any conditional starts with the if
command and is closed by the endif
command. In between, it may have multiple elseif
clauses, and at the end, though this example doesn't illustrate it, it may have one else
clause for any action to perform when none of the if
or elseif
conditions are met. The conditions get evaluated in the order they appear in, and as soon as one turns out to be true, it executes the code immediately following it, then skips ahead to endif
. As a convention, I end conditions with a colon :
, not with a semicolon ;
.
The old keyword returns the label of the piece that last occupied the space just moved to. The piece labels are normally single letters, uppercase being used for White and lowercase being used for Black. With this convention in mind, the expression isupper old
is checking whether the move was to a space that already had a White piece on it. If it is, then the move is illegal, and it uses the die command to exit the program with an explanation to the player.
The moved keyword returns the label of the last piece to move. So, the expression islower moved
returns true if the player moved a Black piece, which White should not be allowed to do.
The next piece of code in White's Post-Move section is this:
if != moved P: 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;
This is a conditional statement containing code that should be run if the piece moved is not a Pawn. The moved keyword returns the label of the last piece moved, and P
is the label used for the White Pawn. First, it sets ep
to false. This is the variable for noting where an en passant capture may take place. When the piece to move is not a Pawn, this information is no longer needed, and the variable may be set to false right away. On the next move, a false value for ep
will indicate that no Black Pawn may take a White Pawn by en passant. Next is another conditional.
if != space dest moved: die You may not change the type of this piece.;
This tests whether the piece currently on the space moved to (space dest
) matches the piece that moved there (moved
). It is possible for them to be different, because a complete move may consist of multiple individual piece movements. In particular, they would be different if the player tried to change the piece after moving it. For example, the notation "R a1-f1; Q-f1
" would have the Rook at a1 move to f1, then promote to a Queen. Something like this cannot be allowed. In the inner if expression, dest is a keyword giving the coordinate of the space moved to, and space is an unary operator that returns the contents of the $space
array for a given coordinate. So, in this context, it will be returning the piece on the space just moved to, which gets compared with the piece label returned by moved
.
If the piece has not illegally changed type, it then checks whether the move was a capture.
elseif capture: set nopvc 0; else: inc nopvc; endif;
If it was, it resets nopvc
to 0, and if it wasn't, it increments it. The nopvc
variable stands for No Pawn Move or Capture, the v being the or sign from symbolic logic. This is a counter for enforcing the 50 moves rule. If the game goes on for 50 turns without either player moving a Pawn or making a capture, the game is considered drawn.
The next section of code checks whether the piece moved according to its legal powers of movement:
if sub moved origin dest and match moved P K: elseif fn moved origin dest and match moved Q R B N: else: die You may not move a moved from origin to dest; endif;
This is an if-statement with an elseif clause and a final else clause. The if-clause and the elseif-clause have no content, but the else clause does. If either of the first two conditions are true, the program will continue, and if they're not, the move isn't legal, and the else clause will exit the program with an explanation to the player. Logically, this is equivalent to this:
if nor and sub moved origin dest match moved P K and fn moved origin dest match moved Q R B N: die You may not move a moved from origin to dest; endif;
But that longer expression is less efficient, because it evaluates everything without breaking out early, and it's harder to read. What's going on here is that, depending upon which piece has been moved, it calls a function or a subroutine by the name of the piece label to evaluate whether its move was legal. Pawn and King movement are handled by subroutines, while the movement of other pieces are handled by functions. The first thing each of these expressions checks is whether the label of the moved piece matches anything from a list of piece labels. If it doesn't, the and keyword has it return false without evaluating the rest of the expression. If it is a match, then the first expression calls a subroutine, and the second expression calls a function.
The difference between a function and a subroutine is that a function is defined in a single line, and a subroutine is a block of code that exists elsewhere in the program. While in languages like C and PHP, functions are essentially subroutines, GAME Code follows BASIC in its distinction between functions and subroutines. The advantage of a function is that it's quicker to evaluate than a subroutine, and it doesn't involve the overhead of moving to a different part of the program and back. The advantage of a subroutine is that it can execute commands and make use of control structures, and if the logic gets complex, it is easier to clearly express it in a subroutine than in a function. Since the functions will be simpler, let's look at them first.
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;
These functions check the legality of piece moves in Chess. The checkleap built-in function checks whether the distance between #0
to #1
is so many files and ranks away. These are places where the function plugs in the values passed to it as arguments. #0
is the first argument, #1
the second, and so on up to #9
. Looking at the code which calls the function, fn moved origin dest
, these are origin and dest. These are two keywords in GAME Code. The former returns the coordinate of the space moved from, and the latter returns the coordinate of the space moved to. The moved keyword, as you know, returns the label of the piece moved. This is used to specify which function to call.
Getting back to checkleap
, this checks whether the move from #0
to #1
is to any space that is so many ranks and files away. The two numbers following #0
and #1
specify how many ranks and files away. These numbers don't have to appear in any particular order. For the Knight move, the space might be 1 file and 2 ranks away or 1 rank and 2 files away. Also, checkleap concerns itself only with absolute distance, not with directional movement. So, negative numbers don't make any difference to it. At most, there are eight spaces that are 1 file and 2 ranks or 2 files and 1 rank away from any space, and the expression checkleap #0 #1 1 2
returns true for movement to any of them. In short, it is a fully symmetrical function. Using checkaleap, the asymmetrical alternative to checkleap, the definition for the Knight is equivalent to this:
def N checkaleap #0 #1 2 1 or checkaleap #0 #1 -2 1 or checkaleap #0 #1 2 -1 or checkaleap #0 #1 -2 -1 or checkaleap #0 #1 1 2 or checkaleap #0 #1 -1 2 or checkaleap #0 #1 1 -2 or checkaleap #0 #1 -1 -2;
The checkride function works just like checkleap
except that it returns true for any space that can be reached through consecutive unblocked leaps in the same direction. With the distances of 1 and 0, it returns true for any unblocked movement down a rank or file. With the distances of 1 and 1, it returns true for any unblocked movement down a diagonal. Since the Queen moves as a Rook or Bishop, it has been defined in terms of the B and R functions used for these pieces. The or operator will return true if the R function returns true, and otherwise it will evaluate the B function.
As you've seen, functions are very simple. When you can use a function to evalaute piece movement, that is best. In Chess, the King and Pawn have complicated moves that sometimes involve actions, not just an evaluation of whether the move is legal. So these are better handled by subroutines. First, here is the subroutine for the White King:
sub K from to: if not fn K #from #to: verify match #to var wcastle; gosub castle #from #to; endif; set K #to; return true; endsub;
The sub command is used here to define a subroutine. Its name is K
, and from
and to
are parameters. The parameters of a subroutine are normally treated as local variables. So, if you have variables by the same name outside of the subroutine, calling this subroutine will not change their values. On the first line is a conditional expression calling the K function.
If I also have a K function, what is the subroutine for? Sometimes the code has to evaluate actual moves, and sometimes it has to evaluate potential moves. The K function covers how a King can move without regard to castling, and it has its uses in evaluating potential King moves. Right now, we're looking at actual moves. When it is evaluating an actual move, it is appropriate to exit the program upon failure and display an explanation to the player, as the castle subroutine does. But when it is evaluating a potential move, it is essential that it fail silently without exiting the program. So it will be handling potential castling moves with a different subroutine.
If the call to the K function fails, it indicates that the move may be a castling move. First, it uses the verify command to check whether the move was made to one of the spaces the White King may castle to. These are stored in the array variable wcastle
, which should be set in the include file or the Pre-Game Code. If the move was not to one of these spaces, it exits the subroutine and returns a false value. Otherwise, the subroutine continues, and on the next line, it calls the castle
subroutine. It is not essential to use verify
with this subroutine, since it will exit the program with an error message if the move is illegal. At this point, if the subroutine is still running, the move is legal. So it then changes the value of the K variable to the King's new position. This variable is used when calculating whether the King is in check. Finally, it returns true, since, if it has gotten this far, the move is legal.
The castling subroutine is fairly complex and doesn't need to be covered in this tutorial. The main things to know about it are these:
- It treats castling as a move by the King, automatically moving the other piece adjacent to the King, but on its other side.
- It takes two arguments, the from and to coordinates of the move.
- It identifies pieces that can castle with flags, not by piece labels.
- It assumes that castling is a horizontal move on a single rank with nothing between the two castling pieces.
- It will not allow the King to castle from or through check.
- It will not bother to prevent the castling position from resulting in check, since that should be tested for after any move, not just castling.
Let's now look at the P
subroutine for the White Pawn. Here is the whole subroutine:
sub P from to; // Pawn Movement verify > rank #to rank #from; verify <= distance #to #from #fps; if capture: verify checkleap #from #to 1 1; set ep false; elseif checkleap #from #to 1 1 and #ep: verify == filename var ep filename #to; verify < rankname var ep rankname #to; verify < rankname #to var bpr; capture #ep; set ep false; elseif > distance #to #from 1: verify == rankname #from #wpr; verify checkride #from #to 0 1; set ep #to; else: verify checkleap #from #to 0 1; set ep false; endif; // Pawn Promotion if onboard where #to 0 #pzs: if != space #to moved: die "You may not promote a Pawn until it reaches the promotion zone."; endif; elseif == P space #to: askpromote #wprom; elseif not match space #to var wprom: set np space #to; die "You may not promote your Pawn to a" #np; endif; set nopvc 0; return true; endsub;
As you can see from the comments, it has two main parts. The first part checks whether the Pawn moved legally, and the second part handles promotion. In checking the legality of the move, it repeatedly uses the verify command, which lets the subroutine continue if the condition is true or exits the subroutine with a value of false if it isn't. These first two lines verify some general conditions about the move:
verify > rank #to rank #from; verify <= distance #to #from #fps;
The first line verifies that the move is in a forward direction, and the second verifies that the move was no longer than the maximum number of spaces a Pawn may move on its first move. This is represented by #fps
, which is 2 for Chess but is sometimes larger for games on larger boards, such Omega Chess or Gross Chess.
There is then a conditional block that checks which type of Pawn move was made and tests the legality of the move made:
if capture: verify checkleap #from #to 1 1; set ep false; elseif checkleap #from #to 1 1 and #ep: verify == filename var ep filename #to; verify < rankname var ep rankname #to; verify < rankname #to var bpr; capture #ep; set ep false; elseif > distance #to #from 1: verify == rankname #from #wpr; verify checkride #from #to 0 1; set ep #to; else: verify checkleap #from #to 0 1; set ep false; endif;
Since three out of four types of moves are non-capturing moves, it checks for a capturing move first. If it is a capturing move, it checks whether it was a diagonal move of one space. Since it has already verified that the move is forward, checkleap can be used just once to check whether it is either of the two forward diagonal moves a capturing Pawn may make.
If it is not a regular capturing move, the move is to an empty space, and it may be an en passant capture, a double move, or a regular one-space move. The next clause in this block checks whether it is an en passant capture:
elseif checkleap #from #to 1 1 and #ep: verify == filename var ep filename #to; verify < rankname var ep rankname #to; verify < rankname #to var bpr; capture #ep; set ep false;
It first checks the value of #ep
. If this is false, it cannot be an en passant capture, and it will proceed to check whether it is another kind of move. If it is true, then it checks whether the move was a diagonal one-space move. If it was, it cannot be anything but an en passant move. So, it then checks whether it was a legal en passant move. Since #ep
is the location of the piece that can be captured by en passant, checking whether the move was to #ep
will not work. Instead, it checks some relationships between #ep
and the space it moves to. First, it verifies that the move was to the same file as #ep
. If it was, it next verifies that the move was to a space that was in the enemy Pawn's path of movement. To do this, it first verifies that the move was to a space behind the Pawn, and then it verifies that the move was to a space after the rank where Pawns start. In Chess itself, these three conditions could be replaced with one checking whether the Pawn moves to a space directly behind the enemy Pawn, but checking for them separately allows this subroutine to handle en passant capture of Pawns that can move more than two spaces on their initial move.
Now that a Pawn's diagonal moves are out of the way, the remaining legal moves for a Pawn must be vertical. The next clause handles the double move, triple move, or other long distance move a Pawn may make on its first move.
elseif > distance #to #from 1: verify == rankname #from #wpr; verify checkride #from #to 0 1; set ep #to;
The first thing it tests for is whether the move is more than one space. If it was, it cannot be a regular one-space move. So it then tests the legality of the move. To do this, it verifies that the move was from the rank on which Pawns start, and then it verifies that the move is one a Rook could make in the forward direction. Since it previously verified that the move wasn't too long, there is no need to do so here. The length of this move has already been capped. It then sets ep
to the coordinate of the space moved to. This marks that the Pawn may be captured by en passant on the next move, and it is the only time when ep
gets set to anything but false.
Finally, if it is not a capture by displacement or en passant, and it is not a double move or something longer, it can only be a normal one-space forward move, and the last clause checks the legality of that.
else: verify checkleap #from #to 0 1; set ep false; endif;
We now move to the section for handling promotion, which looks like this:
// Pawn Promotion if onboard where #to 0 #pzs: if != space #to moved: die "You may not promote a Pawn until it reaches the promotion zone."; endif; elseif == P space #to: askpromote #wprom; elseif not match space #to var wprom: set np space #to; die "You may not promote your Pawn to a" #np; endif;
This is an if-else block with three clauses. The first checks whether the Pawn has reached the promotion zone, which is understood to be one of more ranks at the end of the board. Using #pzs
, which is the number of ranks in the promotion zone, it uses onboard and where to check whether there is a space on the board this number of ranks ahead of the Pawn. If there is, it hasn't yet reached the promotion zone, which means promotion is illegal. In this case, it exits the program with an error message if the Pawn has promoted. For the next two clauses, promotion is legal. The second goes into effect if the Pawn has not promoted. This normally happens when a player makes a Pawn move with the mouse. In that case, it uses askpromote to ask the player which piece he would like to promote to, which will append the move after getting an answer. The last clause uses match to check whether the promotion was legal, exiting with an error message if it wasn't.
There are only two more lines in the P subroutine for Pawn movement:
set nopvc 0; return true;
The first one resets the value of nopvc
to 0. You may recall that this is for enforcing the 50-moves rule. Since nopvc
is a global variable, it can be set in a subroutine without being declared in it. This variable will not be interpreted as a local variable unless it appears as a parameter, as from
and to
do, or if it has been declared as a local variable. The last line returns a value of true, since it will not reach this line unless the move is fully legal.
This covers the subroutines for actual moves. Let's now return to the code in the Post-Move section for White. The next thing to check for is whether the King is in check. This is the code used:
if sub checked #K: die You may not move into check.; endif;
This calls a subroutine that checks whether any enemy piece can move to the King's position. It 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;
First, this checks which side the King belongs to, so that it knows which pieces are capable of checking it.
if isupper cond empty #king moved space #king: def enemies onlylower; else: def enemies onlyupper; endif;
Since this may be used for spaces the King is not on, the cond expression replaces the King with the last moved piece if the space at #king
is empty. The onlylower and onlyupper functions each return the array of spaces with only lowercase (Black) or uppercase (White) labels (pieces) on them. The next part goes through this array, checking whether any enemy piece can move to the King's location.
for (from piece) fn enemies: if fn #piece #from #king: return #from; endif; next;
For and foreach loops are the same thing in GAME Code. For each value in the array returned by the enemies
function, from
is the key, which is the coordinate, and piece
is the value, which is the piece label. For each piece, it simply calls its function to check whether it can reach the King's space from its location. If it finds one that can move there, it returns the piece's coordinate, which will be interpreted as a true value. If it goes through the entire array without finding a piece that can check the King, it returns false.
Note that this subroutine never calls subroutines to evaluate piece moves. It always calls functions. The functions I used for actual moves are also good for evaluating potential moves, since all they do is return a value without changing anything. The King and Pawn subroutines for actual moves are unsuitable for potential moves, because they may exit the program with error messages for the player or otherwise interact with the player. For simply evaluating the legality of King and Pawn moves, functions will do the job. Here are the K
and P
functions for White:
def K checkleap #0 #1 1 1 or checkleap #0 #1 1 0; 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;
The K
function just checks a King's regular moves. The checked
subroutine doesn't need to handle castling to know whether a move would put a King in check by the other King. The only place where castling comes up as a potential move is in the stalemated
subroutine, which makes a record of all legal moves, and there castling is just handled separately from regular piece moves.
The P
function takes care of checking whether a Pawn move is legal, and it will even make an en passant capture when that move is legal, but it doesn't handle promotion, since that has no bearing on where a Pawn may move. Its logic is similar to that used in the subroutine. To follow its logic, you should know GAME Code evaluates expressions backwards in Polish notation, and the and and or operators will break out of the function when given a single argument (or an expression evaluating to a single value), the former breaking out when the expression following it is false, and the latter breaking out when it is true. Like the subroutine, it first verifies that movement is forward and that the distance of movement is no larger than what is allowed on a Pawn's first move:
and <= distance #0 #1 var fps and > rank #1 rank #0;
Instead of identifying variables with the #
prefix, as was done in the subroutine, it uses the var keyword. This ensures that it uses the current value of the variable each time the function is called. If it used #fps
instead of var fps
, it would use the value fps
had when the function was first defined. This would be inappropriate if the value of fps
was changed after chess3 was included. In general, the best practice is to identify any variables in a function with var
, not with #
.
The next line checks whether it is a legal capturing move. Besides doing this first for the same reasons the subroutine does, it does it first because most uses of this function will be with the checked
subroutine, which is only checking whether the Pawn can make a legal capturing move. The rest of the function is useful only for computing all legal moves, which happens in the Post-Game section.
or and islower space #1 checkleap #0 #1 1 1
Since this is for a potential move, not for an actual move, the move has not actually been made, and the space it would potentially move to is unchanged. If this space has a lowercase label on it, and it is one space diagonally forward, then it is a legal capture move. Since direction of the move was already checked, it just checks for one space diagonal movement. At the beginning of this line is or and
. The and
returns true if both expressions following it are true. The or
exits the function with a value of true if the expression following it is true. The logical operators are dual unary/binary operators. With two expressions following them, they just return the value of the logical expression. With one expression following them, they will exit the function early if the one value following them is enough to determine the value of the whole expression. Since an or
statement with any true expression will be true, getting a true value here is enough to know that the whole function should return a value of true. If it doesn't get a true value, then it will discard the value after or
and proceed to check whether it could be another kind of legal move for the Pawn.
The next line, and empty #1
, just confirms that the move is to an empty space. If it isn't, the and
expression exits with a false value. Otherwise, it allows the function to proceed to the next line.
This next line, or checkleap #0 #1 0 1
, checks whether it is an ordinary one-step forward Pawn move. If it is, it exits with a value of true. Otherwise, it continues. You may notice that it is checking different types of Pawn moves in a different order than the subroutine did. Since it is allowed to exit as soon as it finds a legal move, it is able to do things differently. Also, en passant is saved for last, so that it will not capture a piece until it is ready to finish anyway. In general, functions are not supposed to change the board position, and when one does, care should be taken that it is not done improperly.
If it was not a regular one-space forward move, it uses the expression or and checkride #0 #1 0 1 == rankname #0 var wpr
to check whether it was a double, triple, or some other kind of long forward move from its initial starting rank. It has already been verified that the move isn't any longer than this. So it just checks whether it makes a forward Rook move from its initial rank. If it does, it exits with a value of true. Otherwise, it checks whether the move is a legal en passant move, using these lines:
remove var ep and < rankname #1 var bpr and < rankname var ep rankname #1 and == filename var ep filename #1
These check that the move is the same column as the Pawn that can be taken and that the move is to a space it just passed over. If any of these conditions fail, it exits with a value of false. If they all succeed, it removes the Pawn it could capture by en passant, an operation that both affects the board and returns true.
Let's return once again to the Post-Move Code. If the player has moved his own piece by its own powers of movement to a space not occupied by one of his own pieces, and the resulting position does not leave or put the King in check, then the move is legal. All that's left is a little housekeeping.
unsetflag origin dest;
The initial positions of the Kings and Rook are flagged at the beginning of the game. This indicates that they haven't moved yet, which lets the code know whether they can still castle. I could use if-statements to unset flags only when moving the King or moving to a Rook's space, but that would actually be less efficient that just unsetting both flags at once. Remember that origin
and dest
are both keywords that return coordinates. So this line is unsetting flags named for particular coordinates, not flags named "origin" or "dest".
Finally, there is this:
set posvar join "w" join fencode boardflags; inc #posvar;
First, it creates a variable name by concatenating w
for White with the Forsythe code representing the board and an ordered list of all flagged board positions. This variable name represents the board position. The next line increments the variable named by #posvar
, not the posvar
variable itself. By using inc #posvar
instead of inc posvar
, the value of posvar
is what gets passed to the inc command as the name of the variable to increment. This is to keep track of how many times each position is repeated, so that it can enforce the rule against three-times repetition.
The Post-Move code for Black should be very similar. You'll mainly be switching cases and directions around. You can compare the code I have explained here with the code for Black to see what I mean.
The Post-Game Sections
After all moves have been evaluated, it comes time to check whether the game has come to an end. This involves more labor-intensive calculations that would bog the program down if done after every single move. So, instead, a reasonable assumption is made that the game did not end during any previous move, and it waits until all moves have been made and evaluated before evaluating whether the game has now come to an end. This is handled in the Post-Game section. There is one for each side, depending upon who was the last to move. Here is what it looks like for White:
set posvar join "w" join fencode boardflags; if >= var #posvar 3: say Three Times Repetition! Drawn Game!; drawn; elseif sub stalemated #k: if sub checked #k: say Checkmate! White has won!; won; else: say Stalemate! The game is drawn.; drawn; endif; elseif >= #nopvc 100: say Fifty Moves Without Moving a Pawn or Capturing! Game Drawn!; drawn; elseif sub checked #k: say Check!; endif;
This tests for a series of conditions that can lead to the end of the game, and the order is carefully chosen. It begins with a test for three-times repetition. One reason for doing this first is that it puts the condition it tests right after it calculates which variable to check the value of. Another reason is that no repetition of a previous position will be a stalemate or a checkmate. So, there is no chance of missing a stalemate or checkmate by testing for this first. If there is a three-times repetition of an earlier position, it ends the game with the drawn command. Note that this should not be confused with the draw
command used for drawing cards.
It next tests for stalemate, and depending upon whether the King is checked, it determines whether this is checkmate or just stalemate. Even when the game doesn't end in stalemate or checkmate, it is important to call the stalemated
subroutine, because it is also used to compute an array of all legal moves, which is used to display legal moves when a player selects a piece. My tutorial on How to Display Legal Moves in Game Courier covers this in more detail and also goes over this subroutine in detail.
After this, it tests for the 50 moves rule. Since the number 50 actually applies to complete turns by both players, but moves are counted each time either player moves, it compares nopvc
to 100. Stalemate and checkmate are handled before this, because they should take priority. A move that delivers checkmate should not count as a draw because of the 50 moves rule.
Finally, if the game has not ended, it checks whether the player is in check. Although this code is executed at the end of White's move, it also gets executed for Black just before he moves. So Black will see any messages from here at the beginning of his turn, and the "Check!" message in particular is meant for Black. The code for Black's Post-Game section will look very similar, but you will want to switch sides and cases around.
For a complete understanding of how to use GAME Code to enforce the rules of a game, you should also read my tutorial on How to Display Legal Moves in Game Courier and review the User's Guide and the Developer's Guide. Don't worry if you can't memorize everything. I am repeatedly referring back to my own documentation when I program in GAME Code, and I routinely check the documentation when I write in other languages. With computer programming, you don't have to be as fluent as you are in your native tongue, because you are allowed to make a bunch of mistakes, slowly correct them, and through repeated trial-and-error get your program working right.
Written by Fergus Duniho
WWW Page Created: 27 January 2016