Check out Janggi (Korean Chess), our featured variant for December, 2024.

How to Program Multi-Move Variants for Game Courier

This tutorial presumes that you already understand how to use GAME Code to enforce and display legal moves. These subjects are covered in greater depth in these earlier tutorials:

This tutorial is based on code from the Marseillais Chess preset and the marseillais2 include file and from the Extra Move Chess preset and the extramove2 include file. The former is the best known double move variant, and the latter is my own double move variant. Because of the differences in their rules, some details are more relevant to one than the other. The number 2 in the name of the include files refers to the chess2 include file, which both of them are based on. This include file was for this tutorial, because it was the latest include file for Chess at the time this tutorial was originally written. It has since been superceded by newer include files for programming Chess. These include chess3, which was referred to in How to Make Your Game Display Legal Moves in Game Courier and the fairychess include file, which is described in Program Games with the Fairychess Include File in Game Courier - A Tutorial. This tutorial continues to use the older include file, because I have not taken the time to rewrite the code or this tutorial.

Overview

Instead of separately evaluating each separate move a player makes on his turn, Game Courier normally plays all moves a player makes on one turn at once. While it will play them in sequential order, it will wait until it has played all of them before running the Post-Move code. So, what this tutorial describes is a way to get Game Courier to evaluate each part of a multipart move separately. The basic outline goes like this:

  1. Rewind the moves.
  2. Place the separate moves into an array.
  3. Loop through this array, playing each move individually, and evaluating the legality of each move before playing the next move.

Pre-Game Code

One of the main things to be done in the Pre-Move section is to put limits on what moves can be made. To avoid making the code more complicated, we want to ban outright multipart moves with too many parts or with the wrong parts. Here is the code for doing this in Marseillais Chess:

setsystem maxmove 4;
ban commands allmoves;
allow moves 1 captures 1 promotions 2 moves 2 captures 2 moves 3 captures 3 promotions 3 promotions 4;

Although this is a double-move variant, the maximum number of moves is set to four to account for up to two Pawn promotions. It then bans all user-input and selectively allows certain types of moves for the moves allowed to a player. Any of the first three moves may be a regular move or a capture, and any of the last three may be a promotion.

The same section for Extra Move Chess is similar but different. It looks like this:

setsystem maxmove 4;
ban commands allmoves;
allow moves 1 captures 1 promotions 2 moves 2 moves 3 promotions 3 promotions 4 pass 2 pass 3;

The first two lines are the same, but because captures are not allowed on the second move in this game, they are not allowed past the first move, and because a player may pass a second move, a pass move is allowed on the second or third move. The lesson here is to not blindly copy the code for one of these games without understanding it. You need to think about what is going on with your game and set the allowed moves accordingly.

Pre-Move Code

Normally, the Pre-Move code sections are left unused when programming a game. In general, it makes more sense to evaluate the legality of a player's move after they are made instead of before. One reason for this is that making the move populates some variables with values that can be used for evaluating the move's legality. The other reason is that a move may have side effects that are not included in the move notation. In Chess, en passant and castling have side effects. When evaluating the legality of a move after it is made, it is a simple matter to make some adjustments to the position to handle a move's side effects. And in multimove games like the ones considered here, it will make sense to play each move one at a time before evaluating the next one. If this was all done in the Pre-Move section, it would be trying to replay moves it had already made, which would be in error.

However, the Pre-Move section does have a special use in double-move variants like Marseillais Chess and Extra Move Chess. To rewind the position in the Post-Move section, it has to first store the current position before it makes the move. It does this with a single line:

store;

Technically, it will be restoring the position instead of rewinding it. But conceptually, you can think of it as rewinding.

Post-Move Code

Then, the Post-Move sections for both games begin with the following two lines:

set mvs explode chr 59 thismove;
restore;

The first line here creates an array of all the moves made. It does this with the explode function, using a semicolon as a separator. Since the semicolon is used to end a line of code, it identifies it with the expression chr 59, because the ASCII code for a semicolon is 59. In the second line, it undoes all the moves just made by using the restore command. This restores the position to what it was when the store command was used to store it. What is going on here is that the code needs to handle each move separately. Instead of relying on the usual mechanisms of getting information about a move after it is made, it has to undo it all, then make and evaluate each move individually.

In Marseillais Chess, these lines are followed by this code:

set i 0;
set firstpart true;
eval join "MOVE: " trim elem var i mvs;

The variable i is the counter used to access the right element of the mvs array.

The firstpart variable is used in the post-game section to determine whether the current player has completed his moves. Making multiple moves works by letting the player make a single move first, showing the new position, and then letting the same player make another move. So, the moves to be evaluated might be all the moves made in a turn or just the first part of a move, and the code needs to tell the difference.

In the third line here, it makes the first move stored in the mvs array. When written in the code, moves are normally preceded with MOVE: . This line creates a string consisting of "MOVE: " concatenated with the move string, then uses eval to execute the string as though it were a line of code.

Extra Move Chess uses the same code for Black's Post-Move code, but because it allows only one move on White's first turn, its corresponding code for White's Post-Move code looks like this:

set i 0;
if var startofgame:
  if > count var mvs 1:
    die You may not make more than one move at the start of the game.;
  endif;
  set startofgame false;
else:
  set firstpart true;
  eval join "MOVE: " trim elem var i mvs;

The startofgame variable has been set to true in the Pre-Game section. If White makes more than one move on his first turn, this will exit with an error message. Also note that firstpart is not set to true for White's first move. In Extra Move Chess, White's first move is treated as though it were White's second move, which allows White to begin with a Pawn's two-space move, something that is usually only allowed on the second part.

Now that the move is made, it proceeds to evaluate its legality. These lines, used for both games, ensure that White hasn't captured one of his own pieces:

if isupper old:
  die You may not capture your own pieces.;
endif;

This next line, also used in both games, stores the value of dest in case it changes:

set dst dest;

These lines, also used in both games, check for Pawn promotions:

if == moved P and == rankname dest 8:
  inc i;
  if < var i count var mvs:
    set ori origin;
    eval join "MOVE: " trim elem var i mvs;
    if != moved P or != var dst dest or != var ori origin:
      die Only a promotion can immediately follow a Pawn moving to the last rank.;
    endif;
  endif;
endif;

If a White Pawn has moved to the last rank, it increments i and makes the next move in mvs if there is one. If the next move is not a Pawn promotion, it uses die to exit with an error message. To check whether the move was a Pawn promotion, it first copies the value of origin to ori before it actually makes the move. After it makes the move, it recognizes the next move as something other than a Pawn promotion if it changes the values of dest or origin or if the piece moved is not a Pawn.

With the next block of code, it checks the legality of piece moves. This will not be covered in detail, since enforcing legal moves is already covered in an earlier tutorial. For Extra Move Chess, the code could just as easily be used for Chess.

  set legal false;
  switch moved:
    case P K:
      gosub moved origin dest;
      if == moved K:
       set K dest;
      endif;
      break;
    case Q R B N:
      set legal fn moved origin dest;
      if == moved R:
        unsetflag origin;
      endif;
      break;
    default:
      die You may not move a moved;
  endswitch;
  if not var legal;
    die You may not move a moved from origin to dest;
  endif;
  if sub checked #K:
    die You may not move into check.;
  endif;

For Marseillais Chess, which allows up to two double Pawn moves or up to two en passant captures per turn, en passant is being handled with two variables instead of a single ep variable. Since this is the first part of the move, it sets both to false if it is anything but a Pawn move. If the second move is to be an en passant capture, the first one has to be as well.

  
set legal false;
switch moved:
  case P:
    gosub P origin dest;
    break;
  case K:
    gosub K origin dest;
    set K dest;
    set ep1 false;
    set ep2 false;
    break;
  case Q R B N:
    set legal fn moved origin dest;
    if == moved R:
      unsetflag origin;
    endif;
    set ep1 false;
    set ep2 false;
    break;
  default:
    die You may not move a moved;
endswitch;
if not var legal;
  die You may not move a moved from origin to dest;
endif;
if sub checked #K:
  die You may not move into check.;
endif;

Because Marseillais Chess allows two double Pawn moves or two en passant captures on the same player's turn, its subroutines for Pawn movement have to take this into account. Here is its subroutine for White's Pawn:

sub P from to;
  local ydir;
  set legal false;
  verify <= distance #to #from #fps;
  verify > rank #to rank #from;
  if capture:
    echo capture;
    verify checkleap #from #to 1 1;
    if #firstpart:
      set ep1 false;
    endif;
    set ep2 false;
  elseif > distance #to #from 1:
    verify checkride #from #to 0 1;
    if #firstpart:
      set ep1 #to;
      set ep2 false;
    else:
      set ep2 #to;
    endif;
  elseif match join filename #to rankname #from #ep1 #ep2:
    verify checkleap #from #to 1 1 and not capture;
    set ep join filename #to rankname #from;
    capture #ep;
    if == #ep #ep1:
      set ep1 false;
    else:
      set ep2 false;
    endif;
    if not #firstpart:
      set ep1 false;
      set ep2 false;
    endif;
  else:
    verify checkleap #from #to 0 1;
    if #firstpart:
      set ep1 false;
    endif;
    set ep2 false;
  endif;
  if != space #to moved and onboard where #to 0 #pzs:
    die "You may not promote a Pawn until it reaches the promotion zone.";
  endif;
  if not onboard where #to 0 1:
    if == 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;
  endif;
  set legal true;
  return true;
endsub;

This is similar to the subroutine used for Chess. The main differences are that it has to determine which part of the turn it is, and it has to set the appropriate values for ep1 and ep2.

The Pawn subroutines for Extra Move Chess are more similar to those for Chess, but since two-space Pawn moves are allowed only on the second part of a player's turn, and en passant captures are allowed only on the first part, this has to be taken into account. So, here is the corresponding subroutine for Extra Move Chess with relevant parts highlighted:

sub P from to;
  local ydir;
  if == file #from file #to and not capture:
    set legal checkaleap #from #to 0 1;
    if var legal:
      set ep false;
    else:
      set legal checkaride #from #to 0 1 and <= distance #from #to #fps and == rankname #from #wpr and not var firstpart;
      set ep #to;
    endif;
    set epc false;
  elseif and var firstpart or capture #ep:
    set legal checkaleap #from #to -1 1 or checkaleap #from #to 1 1;
    set epc false;
    if not capture and var legal:
      set legal > rank #to rank #ep and < rankname #to #bpr and == file #to file #ep;
      if var legal:
          capture #ep;
        set epc #ep;
      endif;
    endif;
    set ep false;
  endif;
  if != space #to moved and onboard where #to 0 #pzs:
    die "You may not promote a Pawn until it reaches the promotion zone.";
  endif;
  if not onboard where #to 0 1:
    if == 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;
  endif;
  return true;
endsub;

Returning to the Post-Move code for enforcing legal moves on White's turn, the code for Marseillais Chess continues like this:

inc i;
do while < var i count var mvs:
  set firstpart false;
  if sub checked #k:
    break;
  endif;
  eval join "MOVE: " trim elem var i mvs;
  if isupper old;
    die You may not capture your own pieces.;
  endif;
  if == moved P and == rankname dest 8:
    inc i;
    if < var i count var mvs:
      set ori origin;
      set dst dest;
      eval join "MOVE: " trim elem var i mvs;
      if != moved P or != var dst dest or != var ori origin:
        die Only a promotion can immediately follow a Pawn moving to the last rank.;
      endif;
    endif;
  endif;
  if != moved P:
    set ep2 false;
  endif;
  set legal false;
  switch moved:
    case P K:
      gosub moved origin dest;
      if == moved K:
       set K dest;
      endif;
      break;
    case Q R B N:
      set legal fn moved origin dest;
      if == moved R:
        unsetflag origin;
      endif;
      break;
    default:
      die You may not move a moved;
  endswitch;
  if not var legal;
    die You may not move a moved from origin to dest;
  endif;
  if sub checked #K:
    die You may not move into check.;
  endif;
loop never;

This increments i and executes code within a do-while loop that executes only once. When a loop ends with loop never, it functions similarly to an if-block. I did it this way to make use of the break command, which will move execution to the end of a loop but not to the end of an if-block. Since this code is for the second part of White's turn, it begins by setting firstpart to false. This will signal Pawn subroutines or Post-Game code which part of the turn it is. After this, it checks whether the move puts the Black King in check, since in Marseillais Chess, a player may not make a second move if his first checks the opponent's King. Since Marseillais Chess simply allows you to make two regular Chess moves per turn, you can consult How to Enforce Rules in Game Courier for a further explanation of what is going on here.

Here is what the corresponding code looks like for Extra Move Chess. The first highlighted section skips to the end of the loop if the player has passed the second part of the move. The second highlighted section deals with restrictions Extra Move Chess places on the extra move. The rest is standard for enforcing legal moves in Chess or has been covered here already.

do while < var i count var mvs:
  set firstpart false;
  set mv trim elem var i mvs;
  if == var mv pass:
    break;
  endif;
  eval join "MOVE: " var mv;
  if capture:
    die You may not capture anything with your extra move.;
  elseif == var dst origin:
    die You may not move the same piece twice on the same turn.;
  endif;
  if == moved P and == rankname dest 8:
    inc i;
    if < var i count var mvs:
      set ori origin;
      set dst dest;
      eval join "MOVE: " trim elem var i mvs;
      if != moved P or != var dst dest or != var ori origin:
        die Only a promotion can immediately follow a Pawn moving to the last rank.;
      endif;
    endif;
  endif;
  if != moved P:
    set ep false;
  endif;
  set legal false;
  switch moved:
    case P K:
      gosub moved origin dest;
      if == moved K:
       set K dest;
      endif;
      break;
    case Q R B N:
      set legal fn moved origin dest;
      if == moved R:
        unsetflag origin;
      endif;
      break;
    default:
      die You may not move a moved;
  endswitch;
  if not var legal;
    die You may not move a moved from origin to dest;
  endif;
  if sub checked #K:
    die You may not move into check.;
  endif;
loop never;

After this, White's post-move code for both games ends like this:

inc i;
if < var i count var mvs:
  die Too many moves.;
endif;

These lines ensure that the player isn't trying to make more moves than is allowed. Although a maximum number of moves was previously set, it was set to four to allow for promotions. If a player tried to enter three distinct moves that didn't involve promotion, these last lines would prevent that. If you understand what has been going on so far, you will understand that a third move would have no effect. But without these lines, it could still be entered into the notation. So, these lines are really just a bit of housekeeping to ensure that the notation matches the moves actually made.

Post-Game Code

We may now turn to the Post-Game code. I didn't bother with the 50 moves rule or three-times repetition when writing this. Here is what the code looks like for White in Marseillais Chess:

if sub checked #k:
  if sub stalemated #k:
    say Checkmate! White has won!;
    won;
  else:
    say Check!;
  endif;
elseif var firstpart:
  gosub stalemated #K;
  continuemove;
elseif sub stalemated #k:
  say Stalemate! The game is drawn.;
  drawn;
endif;

Normally, the Post-Game section for a player is run after that player moves. This will happen right after the player moves, and it will happen for the other player just before he moves. So, White's Post-Game code gets run for White right after White moves, and it will also get run for Black just before Black moves. When it gets run for Black, the stalemated subroutine is also used to populate an array of legal moves, which then gets passed to JavaScript for displaying legal moves when a player clicks on a piece. See How to Make Your Game Display Legal Moves in Game Courier for further details on this.

However, in a double-move game, it will also get run after White makes the first part of his move, and so just before White is about to make the second part of his move. So, code for getting the legal moves for the second part of the move goes here. This is done with gosub stalemated #K;. This line uses a capital K for the location of White's King, and it doesn't require the value returned by this subroutine, because it is only using it for its side effect of populating a list of legal moves, and a player should not be allowed to draw the game simply by moving in a way to stalemate himself on his second move. If a second move becomes impossible, the player may go back and make a different move. However, the rules of Marseillais Chess do not really address this situation, and it's possible that this should be coded a little differently.

The continuemove command is used to give White a second move before finishing his turn.

The corresponding code for Extra Move Chess looks like this:

if sub checked #k:
  if sub stalemated #k:
    say Checkmate! White has won!;
    won;
  elseif var firstpart:
    gosub legalmoves2 #K;
    continuemove;
  else:
    say Check!;
  endif;
elseif var firstpart:
  gosub legalmoves2 #K;
  continuemove;
elseif sub stalemated #k:
  say Stalemate! The game is drawn.;
  drawn;
endif;

Since this is my game, I can speak authoritatively about its rules. Unlike Marseillais Chess, a player in Extra Move Chess may still make a second move after putting the opponent's King in check. This is because capture is forbidden on the second part of the move. So, the first highlighted section handles populating an array of legal moves and allowing a second move after placing the opponent's King in check. The second highlighted section corresponds to the highlighted section for Marsaillais Chess. In the case of Extra Move Chess, passing is allowed on the second part of a player's turn. So, there is no chance of a player drawing the game by moving into a stalemate position with his first move.

Note that instead of calling stalemated, these two sections call legalmoves2. Marsaillais Chess could use stalemated each time, because it allows a player to make a regular Chess move each time, but since Extra Move Chess restricts what can be done in the second part of the move, it uses a different subroutine to populate the array of legal moves for the second part of the turn. This is what code for this looks like:

sub legalmoves2 kingpos:
  store;
  local from piece to;

  if isupper space #kingpos:
    def friends onlyupper;
    def nofriends noupper;
    def friend isupper #0;
    set cspaces var wcastle;
  else:
    def friends onlylower;
    def nofriends nolower;
    def friend islower #0;
    set cspaces var bcastle;
  endif;
  
  // While the royal piece is called the King in these comments,
  // it may be any piece. These variables determine what the royal piece is.
  set royal space var kingpos;
  set oldto dest;
  store;
  
 // Can another piece legally move?
  for (from piece) fn friends:
    if != #from #oldto:
      for to fn join #piece L #from:
        if fn #piece #from #to and empty #to and onboard #to:
          move #from #to;
          if not sub checked cond == #from #kingpos #to #kingpos:
              setlegal #from #to;
          endif;
        endif;
        restore;
      next;
    endif;
  next;

  for to var cspaces:
    if sub castlepos #kingpos #to:
      if not sub checked #to:
        setlegal #kingpos #to;
      endif;
    endif;
    restore;
  next;
  
  // All done. Set $legalmoves and return;
  return cond count system legalmoves false true;
endsub;

The oldto variable keeps track of the location of the first piece to move, so that it will not record it as having any legal moves. The clause and empty #to prevents captures from being recorded as legal moves. Since castling is always to an empty space, and it never involves a piece that has already moved, it is allowed in the second part of a player's turn.

Although the examples here were double-move variants, the principles behind them can be extended to games that allow more moves per turn. For an example of a game allowing more than two moves, I'll refer you to my code for Chieftain Chess, which allows up to four moves per turn. It may include details that are relevant to that particular game rather than to multi-move variants in general.


Written by Fergus Duniho
WWW Page Created: 05 May 2016