// This alternate GAME Code include file for Chess meets these design goals: // (1) It is general enough for hopping pieces like Cannons. The chess include file is not. // (2) It allows for non-displacement captures, such as the Pawn's en passant move. The xiangqi include file does not. // (3) It is general enough for its subroutines to easily be reused for a wide variety of Chess variants. // It includes these features: // (1) The movement of a piece can be defined by a function. // (2) There is no checkmated subroutine. Checkmate can be identified by determining that a checked King is stalemated. // (3) You may specify which rank Pawns may make their double move from by setting wpr and bpr; // (4) Its stalemated subroutine can be used for royal pieces besides the King, but not royal Pawns. // Overall, the code here is simpler than the code in chess.txt. // Instead of using stalemated, checkmated, checkedthru, and checks subroutines and ATTACKEDBYW and ATTACKEDBYB functions, // it uses only the stalemated and checked subroutines. Checkmate is just check plus stalemate. // This allows the functions defining piece movement to also be used for checking for check, which // eliminates the need to write separate functions for this purpose. // Set some variables to reuse some subroutines for different games. 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; // Number of ranks in promotion zone set wcastle c1 g1; set bcastle c8 g8; // Restricting user input setsystem maxmove 2; ban commands allmoves; allow moves 1 captures 1 promotions 2; // This uses the piecekeys variable to determine what a Pawn may promote to. // wprom and bprom are global variables used in the P and p subroutines. for x piecekeys: if match #x P K p k: continue; elseif isupper #x: push wprom #x; elseif islower #x: push bprom #x; endif; next; // These functions are used for checking the legality of a piece's move. Their names match // the piece labels so that the piece moved quickly identifies which function to call. // For the sake of efficiency, separate functions are given for every piece for each side. // The #0 and #1 placeholders are for the origin and destination spaces of a move. Every // placeholder must appear outside of parentheses at least once. 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 K checkleap #0 #1 1 1 or checkleap #0 #1 1 0; def M fn N #0 #1 or fn R #0 #1; def A fn N #0 #1 or fn B #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; def k checkleap #0 #1 1 1 or checkleap #0 #1 1 0; def m fn N #0 #1 or fn R #0 #1; def a fn N #0 #1 or fn B #0 #1; // DIVERGENT PIECES: CANNONS & VAOS // Since divergent pieces capture differently than they otherwise move, their functions must test whether a move is a // capture. This is done in these functions with the outer "cond" expression: "cond cond empty #0 capture (not empty #1)". // Since these functions are used for both actual moves and potential moves, and an actual capture cannot be identified in // the same way as a potential capture, they must first test whether an actual move has been made. This is done with the // inner "cond" expression: "cond empty #0". The origin square (#0) will be empty for an actual move, because the piece will // have just vacated it, but it will be occupied for a potential move. When an actual move has been made, the "capture" // operator tells whether a piece got captured. When a potential move is being considered, it has to check whether the // destination is occupied to tell whether the move would be a capture. These functions do not have to distinguish between // friend and foe, because that gets done separately. The "and #1" at the end of each function serves no purpose except to // place the #1 marker outside of parentheses at least once, which is a requirement for it to be used in the function. def C cond cond empty #0 capture (not empty #1) (checkhop #0 #1 0 1) (checkride #0 #1 0 1) and #1; def V cond cond empty #0 capture (not empty #1) (checkhop #0 #1 1 1) (checkride #0 #1 1 1) and #1; def c cond cond empty #0 capture (not empty #1) (checkhop #0 #1 0 1) (checkride #0 #1 0 1) and #1; def v cond cond empty #0 capture (not empty #1) (checkhop #0 #1 1 1) (checkride #0 #1 1 1) and #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. // var ep must be used instead of #ep to use the current value of ep. // #wpr and #bpr are okay, because their values never change. def P remove var ep and checkleap #0 #1 1 1 and == var ep join filename #1 rankname #0 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 checkleap #0 #1 1 1 and == var ep join filename #1 rankname #0 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; // Given that this code is not optimized to specially handle revealed checks, the stalemated subroutine must be able // to test every possible move any piece could make. This is the cost in efficiency for using this simpler code. In most // cases, the cost isn't high, since the stalemated subroutine won't usually have to test many moves before it finds a legal // one. These functions are used to narrow down the legal moves of a piece, so that the stalemated function doesn't have to // waste time testing moves a piece could never possibly make, such as a Rook moving diagonally. Separate functions are // given for each side for the sake of efficiency. Note that all these functions end with a capital L. def PL filter lambda (onboard #1) mergeall where #ori -1 1 where #ori 1 1 values lambda (where #ori 0 #1) range 1 var fps =ori; def pL filter lambda (onboard #1) mergeall where #ori -1 -1 where #ori 1 -1 values lambda (where #ori 0 neg #1) range 1 var fps =ori; setflag rules; 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 AL merge leaps #0 1 2 rays #0 1 1; def ML merge rays #0 1 0 leaps #0 1 2; 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 aL merge leaps #0 1 2 rays #0 1 1; def mL merge rays #0 1 0 leaps #0 1 2; // This subroutine checks whether a King is in check. 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; // These two subroutines are for actual Pawn moves. They are unsuitable for evaluating // possible Pawn moves, because they exit the whole program with an error message on // finding an illegal move, they concern themselves with promotion, and they handle the // capture of Pawns taken by en passant. // These routines have been generalized to accomodate the first moves of Pawns on boards of different sizes // and to allow for initial moves longer than the double move, such as the triple move allowed in Omega Chess, // and the extended powers of en passant capture that go along with an extended first move. // The variables wpr and bpr define the ranks that White's and Black's Pawns start on. They assume all Pawns // start on the same rank. The variable fps tells how far the first Pawn step can be. It defaults to 2 for Chess. 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; set ep #to; endif; set epc false; elseif capture or #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 #pzs: 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; sub p from to; 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 #bpr; set ep #to; endif; set epc false; elseif capture or #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 #wpr 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 neg #pzs: die You may not promote a Pawn until it reaches the promotion zone.; endif; if not onboard where #to 0 neg #pzs; if == p space #to: askpromote #bprom; elseif not match space #to var bprom: set np space #to; die You may not promote your Pawn to a #np; endif; endif; return true; endsub; // This subroutine is for evaluating actual moves by the White King. sub K from to; set legal fn K #from #to; if match #to where #from 2 0 where #from -2 0: set legal sub castle; endif; set K #to; unsetflag #from; endsub; // This subroutine is for evaluating actual moves by the Black King. sub k from to; set legal fn K #from #to; if match #to where #from 2 0 where #from -2 0: set legal sub castle; endif; set k #to; unsetflag #from; endsub; // This is a generic castling subroutine that handles both regular castling and free castling. // Castling is handled as a King's move, and the only argument that ever needs to be passed // to the subroutine is an alternate destination for the piece the King is castling with. // As long as the piece is just leaping to a space adjacent to the King on the other side, // no arguments need to be given. The subroutine will find the piece the King may castle // with and move it to the appropriate location if castling proves legal. // This subroutine presumes that the positions of the King and any piece it may castle // with are flagged at the beginning of the game, that they will be unflagged when the // piece moves, that castling involves movement only along a rank, that the checked // subroutine has been created for telling when a space is attacked, that it is not used // for castling to spaces it is never legal for a King to castle to, and that #from // and #to were set in a previous function. sub castle; local c RPOS RDEST xdir; if not flag #from: die A King may not castle after it moves.; endif; if capture: die A King may not castle to an occupied space.; endif; set xdir sign minus file #to file #from; if not checkaride #from #to #xdir 0: die A King may not castle across any occupied space.; endif; set c #to; do: set c where #c #xdir 0; if flag #c: break; elseif not onboard #c: die No piece was found to castle with.; elseif not empty #c: die The King cannot castle with the piece at #c; endif; loop; set RPOS #c; move #to #from; // Temporarily undo King move if sub checked #from: die A King may not castle out of check.; endif; store; for c path #from #to: move #from #c; if sub checked #c: die A King may not castle through check.; endif; restore; next; move #from #to; // Redo King move if == count var subargs 0: set RDEST where #to neg #xdir 0; else: set RDEST elem 0 subargs; endif; unsetflag #RPOS; move #RPOS #RDEST; return true; endsub; // Used to evaluate possible castling moves without producing error messages. sub castlepos from to: local c RPOS RDEST xdir safe; verify flag #from; verify empty #to; set xdir sign minus file #to file #from; verify checkaride #from #to #xdir 0; verify not sub checked #from; set c #to; do: set c where #c #xdir 0; if flag #c: break; endif; verify onboard #c; verify empty #c; loop; verify flag #c; set RPOS #c; store; for c path #from #to: move #from #c; set safe not sub checked #c; restore; verify #safe; next; move #from #to; // Redo King move set RDEST where #to neg #xdir 0; move #RPOS #RDEST; return true; endsub; // Goes through all possible moves, putting all legal moves into the array $legalmoves // Returns size of $legalmoves array sub stalemated 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; // Can the king move? // The King is done separately for two reasons. // (1) Efficiency: The King is always on the board and can usually move. // (2) Accuracy: For a King's potential move, the King's new location must be checked for check. // But for other pieces, the King's actual position must be checked for check. store; // Can another piece legally move? 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; 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; // LEGACY CODE // This subroutine is used to complete an en passant capture made by a potential Pawn move // that has already been confirmed, except for matters of placing the King in check, to be legal. // The purpose for completing the move is to allow the test for whether it places the King in check. sub enpassant piece from to: local pe; verify not capture and #ep; verify == P toupper #piece; set pe join filename #to rankname #from; verify == #ep #pe; capture #ep; endsub;