// 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; // 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 // Restricting user input setsystem maxmove 2; ban commands allmoves; allow moves 1 captures 1 promotions 2; // 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. def P remove var ep and < rankname #1 var bpr and < rankname var ep rankname #1 and == filename var ep filename #1 and checkleap #0 #1 1 1 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; // 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 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 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; def @ false; // 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; // 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; 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 wpr; capture #ep; set ep false; elseif > distance #to #from 1: verify == rankname #from #bpr; verify checkride #from #to 0 1; set ep #to; else: verify checkleap #from #to 0 1; set ep false; endif; // Pawn Promotion 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; set nopvc 0; return true; endsub; // This subroutine is for evaluating actual moves by the White King. sub K from to: if not fn K #from #to: verify sub castle #from #to and match #to var wcastle; endif; set K #to; return true; endsub; // This subroutine is for evaluating actual moves by the Black King. sub k from to: if not fn k #from #to: verify sub castle #from #to and match #to var bcastle; endif; set k #to; return true; 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 from to: 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 set RDEST where #to neg #xdir 0; // unsetflag #RPOS; move #RPOS #RDEST; return true; endsub; 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; set safe not sub checked #to; restore; return #safe; 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 friend isupper #0; set cspaces var wcastle; else: def friends onlylower; 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; store; // Can any 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; // Castling moves are handled separately for to var cspaces: if sub castlepos #kingpos #to: setlegal #kingpos #to; endif; 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;