X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=moves.c;h=a6b066b5fbdbe4bdeb488e74ff53c8a02e68a6a0;hb=v4.9.x;hp=30f94b583f40bb1c9e5a3faada9cb1668a0f2a9e;hpb=9d4c9573bfe66d04681f0bc4553ec379e7ac1fc4;p=xboard.git diff --git a/moves.c b/moves.c index 30f94b5..3a07137 100644 --- a/moves.c +++ b/moves.c @@ -122,6 +122,7 @@ unsigned char pieceToChar[EmptySquare+1] = { 'x' }; unsigned char pieceNickName[EmptySquare]; int promoPartner[EmptySquare]; +unsigned char autoProm[EmptySquare]; char PieceToChar (ChessSquare p) @@ -170,7 +171,7 @@ CopyBoard (Board to, Board from) { int i, j; - for (i = 0; i < BOARD_HEIGHT; i++) + for (i = 0; i < handSize; i++) for (j = 0; j < BOARD_WIDTH; j++) to[i][j] = from[i][j]; for (j = 0; j < BOARD_FILES; j++) // [HGM] gamestate: copy castling rights and ep status @@ -212,7 +213,7 @@ CollectPieceDescriptors () if(!pieceDefs) return ""; if(gameInfo.variant == VariantChu) return ""; // for now don't do this for Chu Shogi if(gameInfo.variant == VariantShogi) pieceName = shogiName; - if(gameInfo.variant == VariantXiangqi) pieceName = xqName; + if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) pieceName = xqName; for(p=WhitePawn; p> 8-angle) & 255; // re-orient direction system + if(dx && dx != dy) break; // oblique continuation ds2 = dirSet & 0xAA; // extract diagonal directions if(dirSet &= 0x55) // start with orthogonal moves, if present retry = 1, dx = 0; // and schedule the diagonal moves for later @@ -393,10 +399,12 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle } if(mine == 2 && tx < 0) dirSet = dirSet >> 4 | dirSet << 4 & 255; // invert black moves mode = 0; // build mode mask + if(*desc == 'u') put++, desc++; // unload stuff at start of leg if(*desc == 'm') mode |= 4, desc++; // move to empty if(*desc == 'c') mode |= his, desc++; // capture foe if(*desc == 'd') mode |= mine, desc++; // destroy (capture friend) if(*desc == 'e') mode |= 8, desc++; // e.p. capture last mover + if(*desc == 'x') mode = mine|1<<12, desc++; // induction step if(*desc == 't') mode |= 16, desc++; // exclude enemies as hop platform ('test') if(*desc == 'p') mode |= 32, desc++; // hop over occupied if(*desc == 'g') mode |= 64, desc++; // hop and toggle range @@ -408,6 +416,9 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle if(isdigit(*++p)) expo = atoi(p++); // read exponent if(expo > 9) p++; // allow double-digit desc = p; // this is start of next move + if(tx == -2 && mode & 1<<12) continue; // prevent recursive move borrowing (flaky) + epFlag = // flags initial orthogonal and diagonal pawn non-capture multi-pushes (which have legacy meaning) + (initial && promo != NormalMove && !cont && mode == 4 && (!dx || dx == dy) && (dy > 1 ? !jump : expo > 1)); if(initial == 2) { if(board[r][f] != initialPosition[r-2*his+3][f]) continue; initial = 0; } else if(initial && !range) { if( (board[r][f] != initialPosition[r][f] || @@ -424,6 +435,8 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle } else { strncpy(buf, cont, 80); cont = buf; // copy next leg(s), so we can modify atom = buf; while(islower(*atom)) atom++; // skip to atom + for(q=buf; q!=atom && *q != 'a'; q++) // test whether next leg unloads + if(*q == 'u') u = 1; if(mode & 32) mode ^= 256 + 32; // in non-final legs 'p' means 'pass through' if(mode & 64 + 512) { mode |= 256; // and 'g' too, but converts leaper <-> slider @@ -449,7 +462,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle if(y < 0 || y >= BOARD_HEIGHT) break; // vertically off-board: always done if(x < BOARD_LEFT) { if(mode & 128) x += BOARD_RGHT - BOARD_LEFT, loop++; else break; } if(x >= BOARD_RGHT) { if(mode & 128) x -= BOARD_RGHT - BOARD_LEFT, loop++; else break; } - if(j) { j--; continue; } // skip irrespective of occupation + if(j > 0) { j--; continue; } // skip irrespective of occupation if(board[y][x] == DarkSquare) break; // black squares are supposed to be off board if(!jump && board[y - vy + vy/2][x - vx + vx/2] != EmptySquare) break; // blocked if(jump > 1 && board[y - vy + vy/2][x - vx + vx/2] == EmptySquare) break; // no hop @@ -460,13 +473,23 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle if(initial && expo - i + 1 != range) { if(occup == 4) continue; else break; } if(cont) { // non-final leg if(mode&16 && his&occup) occup &= 3;// suppress hopping foe in t-mode + if(skip < 0) mode |= 4; // 'n' = 'm' + rights creation in non-final step leg if(occup & mode) { // valid intermediate square, do continuation char origAtom = *atom; - int rg = (expo != 1 ? expo - i + 1 : range); // pass length of last *slider* leg + int rg = (expo != 1 ? expo - i + 1 : range); // pass length of last *slider* leg + int transp = (occup | 1<<12) & mode & 0x1104; // no side effect on intermediate square if(!(bit & all)) *atom = rotate[*atom - 'A']; // orth-diag interconversion to make direction valid - if(occup & mode & 0x104) // no side effects, merge legs to one move + if(transp && !u) { // no side effects, merge legs to one move + if(skip < 0 && occup == 4) { // create e.p. rights on this square + if(viaX != 100) { // second e.p. square! + if(viaX == x && viaY == y - vy) viaY = y | 128; // flag it when we can handle it + } else viaX = x, viaY = y; + } + if(mode & 1<<12) MovesFromString(board, flags, x, y, x, y, dir, rg, cont, cb, cl), occup = 0; else MovesFromString(board, flags, f, r, x, y, dir, rg, cont, cb, cl); - if(occup & mode & 3 && (killX < 0 || kill2X < 0 && (legNr > 1 || killX == x && killY == y) || + if(viaY & 128) viaY = y - vy; else viaX = viaY = 100; + } + if((occup & mode & 3 || transp && u) && (killX < 0 || kill2X < 0 && (legNr > 1 || killX == x && killY == y) || (legNr == 1 ? kill2X == x && kill2Y == y : killX == x && killY == y))) { // destructive first leg int cnt = 0; legNr <<= 1; @@ -511,7 +534,13 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle break; } if(mode & 16 && (board[y][x] == WhiteKing || board[y][x] == BlackKing)) break; // tame piece, cannot capture royal - if(occup & mode) cb(board, flags, y == promoRank ? promo : NormalMove, r, f, y, x, cl); // allowed, generate + if(occup & mode) { + if(mode & 1<<12) { + ChessSquare neighbor = board[y][x]; + char *borrow = (neighbor == pc ? NULL : pieceDesc[neighbor]); // do not borrow from equal type + if(borrow) MovesFromString(board, flags, f, r, -2, -2, dir, range, borrow, cb, cl); // borrow moves from neighbor + } else cb(board, flags, y == promoRank ? promo : put ? Swap : NormalMove, r, f, y, x, cl); // allowed, generate + } if(occup != 4) break; // not valid transit square } while(--i); } @@ -729,6 +758,42 @@ Knight (Board board, int flags, int rf, int ff, MoveCallback callback, VOIDSTAR } } +void +Zebra (Board board, int flags, int rf, int ff, MoveCallback callback, VOIDSTAR closure) +{ + int i, j, s, rt, ft; + for (i = -1; i <= 1; i += 2) + for (j = -1; j <= 1; j += 2) + for (s = 2; s <= 3; s++) { + rt = rf + i*s; + ft = ff + j*(5-s); + if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) + && ( gameInfo.variant != VariantJanggi || board[rf+i*(s-2)][ff+j*(3-s)] == EmptySquare && board[rf+i*(s-1)][ff+j*(4-s)] == EmptySquare) + && !SameColor(board[rf][ff], board[rt][ft])) + callback(board, flags, NormalMove, rf, ff, rt, ft, closure); + } +} + +void +PalaceDiags (Board board, int flags, int rf, int ff, int isRook, MoveCallback callback, VOIDSTAR closure) +{ // Janggi diagonal palace moves + int piece = board[rf][ff]; + int middle = BOARD_WIDTH/2; + int palace = (rf < 3 ? 1 : BOARD_HEIGHT-2); + if(ff == middle) { + if(rf == palace && isRook) Ferz(board, flags, rf, ff, callback, closure); + } else if((ff == middle+1 || ff == middle-1) && (rf == palace+1 || rf == palace-1)) { // Palace corner + int rt = 2*palace - rf, ft = 2*middle - ff; // reflect + ChessSquare center = board[palace][middle]; + if(isRook && !SameColor(piece, center)) + callback(board, flags, NormalMove, rf, ff, palace, middle, closure); + if(center == WhiteCannon || center == BlackCannon) return; + if((center == EmptySquare) == isRook && !SameColor(piece, board[rt][ft]) + && piece + board[rt][ft] != WhiteCannon + BlackCannon) + callback(board, flags, NormalMove, rf, ff, rt, ft, closure); + } +} + /* Call callback once for each pseudo-legal move in the given position, except castling moves. A move is pseudo-legal if it is legal, or if it would be legal except that it leaves the king in @@ -743,10 +808,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, { int rf, ff; int i, j, d, s, fs, rs, rt, ft, m; + int vari = gameInfo.variant; int epfile = (signed char)board[EP_STATUS]; // [HGM] gamestate: extract ep status from board - int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1; + int dead = (vari == VariantSChess && !gameInfo.holdingsSize ? 1 : 0); + int promoRank = vari == VariantMakruk || vari == VariantGrand || vari == VariantChuChess ? 3 : 1; - for (rf = 0; rf < BOARD_HEIGHT; rf++) + if(vari == VariantSChess && !gameInfo.holdingsSize) promoRank = 2; + for (rf = dead; rf < BOARD_HEIGHT - dead; rf++) for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) { ChessSquare piece; @@ -760,7 +828,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, MovesFromString(board, flags, ff, rf, -1, -1, 0, 0, pieceDesc[piece], callback, closure); continue; } - if(IS_SHOGI(gameInfo.variant)) + if(IS_SHOGI(vari)) piece = (ChessSquare) ( SHOGI piece ); switch ((int)piece) { @@ -770,7 +838,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case WhitePawn: - if(gameInfo.variant == VariantXiangqi) { + if(vari != VariantNormal && (vari == VariantXiangqi || vari == VariantJanggi)) { /* [HGM] capture and move straight ahead in Xiangqi */ if (rf < BOARD_HEIGHT-1 && !SameColor(board[rf][ff], board[rf + 1][ff]) ) { @@ -779,23 +847,32 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, } /* and move sideways when across the river */ for (s = -1; s <= 1; s += 2) { - if (rf >= BOARD_HEIGHT>>1 && + if ((rf >= BOARD_HEIGHT>>1 || vari == VariantJanggi) && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && !WhitePiece(board[rf][ff+s]) ) { callback(board, flags, NormalMove, rf, ff, rf, ff+s, closure); } } + if(vari == VariantJanggi) { // diagonal moves in palace + int d = BOARD_HEIGHT - rf; + if(d == 3 || d == 2) { + if(ff == BOARD_WIDTH/2 - d + 2 && !WhitePiece(board[rf+1][ff+1])) + callback(board, flags, NormalMove, rf, ff, rf+1, ff+1, closure); + if(ff == BOARD_WIDTH/2 + d - 2 && !WhitePiece(board[rf+1][ff-1])) + callback(board, flags, NormalMove, rf, ff, rf+1, ff-1, closure); + } + } break; } if (rf < BOARD_HEIGHT-1 && board[rf + 1][ff] == EmptySquare) { callback(board, flags, - rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove, + rf >= BOARD_HEIGHT-1-promoRank && !autoProm[WhitePawn] ? WhitePromotion : NormalMove, rf, ff, rf + 1, ff, closure); } if (rf <= (BOARD_HEIGHT>>1)-3 && board[rf+1][ff] == EmptySquare && // [HGM] grand: also on 3rd rank on 10-board - gameInfo.variant != VariantShatranj && /* [HGM] */ - gameInfo.variant != VariantCourier && /* [HGM] */ + vari != VariantShatranj && /* [HGM] */ + vari != VariantCourier && /* [HGM] */ board[rf+2][ff] == EmptySquare ) { callback(board, flags, NormalMove, rf, ff, rf+2, ff, closure); @@ -805,11 +882,11 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, ((flags & F_KRIEGSPIEL_CAPTURE) || BlackPiece(board[rf + 1][ff + s]))) { callback(board, flags, - rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove, + rf >= BOARD_HEIGHT-1-promoRank && !autoProm[WhitePawn] ? WhitePromotion : NormalMove, rf, ff, rf + 1, ff + s, closure); } if (rf >= BOARD_HEIGHT+1>>1) {// [HGM] grand: 4th & 5th rank on 10-board - int victimFile = (board[LAST_FILE] == 100 ? ff + s : board[LAST_FILE]); + int victimFile = (board[LAST_TO] & 0x40 ? ff + s : board[LAST_TO] & 255); if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && (board[EP_FILE] == ff + s || epfile == EP_UNKNOWN) && rf < BOARD_HEIGHT-3 && (board[rf][victimFile] == BlackPawn || board[rf][victimFile] == BlackLance) && @@ -822,7 +899,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case BlackPawn: - if(gameInfo.variant == VariantXiangqi) { + if(vari != VariantNormal && (vari == VariantXiangqi || vari == VariantJanggi)) { /* [HGM] capture straight ahead in Xiangqi */ if (rf > 0 && !SameColor(board[rf][ff], board[rf - 1][ff]) ) { callback(board, flags, NormalMove, @@ -830,23 +907,31 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, } /* and move sideways when across the river */ for (s = -1; s <= 1; s += 2) { - if (rf < BOARD_HEIGHT>>1 && + if ((rf < BOARD_HEIGHT>>1 || vari == VariantJanggi) && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && !BlackPiece(board[rf][ff+s]) ) { callback(board, flags, NormalMove, rf, ff, rf, ff+s, closure); } } + if(vari == VariantJanggi) { // diagonal moves in palace + if(rf == 1 || rf == 2) { + if(ff == BOARD_WIDTH/2 - rf + 1 && !BlackPiece(board[rf-1][ff+1])) + callback(board, flags, NormalMove, rf, ff, rf-1, ff+1, closure); + if(ff == BOARD_WIDTH/2 + rf - 1 && !BlackPiece(board[rf-1][ff-1])) + callback(board, flags, NormalMove, rf, ff, rf-1, ff-1, closure); + } + } break; } if (rf > 0 && board[rf - 1][ff] == EmptySquare) { callback(board, flags, - rf <= promoRank ? BlackPromotion : NormalMove, + rf <= promoRank && !autoProm[BlackPawn] ? BlackPromotion : NormalMove, rf, ff, rf - 1, ff, closure); } if (rf >= (BOARD_HEIGHT+1>>1)+2 && board[rf-1][ff] == EmptySquare && // [HGM] grand - gameInfo.variant != VariantShatranj && /* [HGM] */ - gameInfo.variant != VariantCourier && /* [HGM] */ + vari != VariantShatranj && /* [HGM] */ + vari != VariantCourier && /* [HGM] */ board[rf-2][ff] == EmptySquare) { callback(board, flags, NormalMove, rf, ff, rf-2, ff, closure); @@ -856,11 +941,11 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, ((flags & F_KRIEGSPIEL_CAPTURE) || WhitePiece(board[rf - 1][ff + s]))) { callback(board, flags, - rf <= promoRank ? BlackPromotion : NormalMove, + rf <= promoRank && !autoProm[BlackPawn] ? BlackPromotion : NormalMove, rf, ff, rf - 1, ff + s, closure); } if (rf < BOARD_HEIGHT>>1) { - int victimFile = (board[LAST_FILE] == 100 ? ff + s : board[LAST_FILE]); + int victimFile = (board[LAST_TO] & 0x40 ? ff + s : board[LAST_TO] & 255); if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && (board[EP_FILE] == ff + s || epfile == EP_UNKNOWN) && rf > 2 && (board[rf][victimFile] == WhitePawn || board[rf][victimFile] == WhiteLance) && @@ -882,7 +967,8 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, rt = rf + i*s; ft = ff + j*(3-s); if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) - && ( gameInfo.variant != VariantXiangqi || board[rf+i*(s-1)][ff+j*(2-s)] == EmptySquare) + && ( vari == VariantNormal || + vari != VariantJanggi && vari != VariantXiangqi || board[rf+i*(s-1)][ff+j*(2-s)] == EmptySquare) && !SameColor(board[rf][ff], board[rt][ft])) callback(board, flags, NormalMove, rf, ff, rt, ft, closure); @@ -911,6 +997,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case WhiteCannon: case BlackCannon: + if(vari == VariantJanggi) PalaceDiags(board, flags, rf, ff, FALSE, callback, closure); for (d = 0; d <= 1; d++) for (s = -1; s <= 1; s += 2) { m = 0; @@ -918,10 +1005,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, rt = rf + (i * s) * d; ft = ff + (i * s) * (1 - d); if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break; + if(vari == VariantJanggi) { + if(board[rt][ft] == WhiteCannon || board[rt][ft] == BlackCannon) break; + } else if (m == 0 && board[rt][ft] == EmptySquare) callback(board, flags, NormalMove, rf, ff, rt, ft, closure); - if (m == 1 && board[rt][ft] != EmptySquare && + if (m == 1 && (board[rt][ft] != EmptySquare || vari == VariantJanggi) && !SameColor(board[rf][ff], board[rt][ft]) ) callback(board, flags, NormalMove, rf, ff, rt, ft, closure); @@ -933,16 +1023,16 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, /* Gold General (and all its promoted versions) . First do the */ /* diagonal forward steps, then proceed as normal Wazir */ case SHOGI (PROMO WhitePawn): - if(gameInfo.variant == VariantShogi) goto WhiteGold; + if(vari == VariantShogi) goto WhiteGold; case SHOGI (PROMO BlackPawn): - if(gameInfo.variant == VariantShogi) goto BlackGold; + if(vari == VariantShogi) goto BlackGold; case SHOGI WhiteAxe: case SHOGI BlackAxe: SlideVertical(board, flags, rf, ff, callback, closure); break; case SHOGI (PROMO WhiteKnight): - if(gameInfo.variant == VariantShogi) goto WhiteGold; + if(vari == VariantShogi) goto WhiteGold; case SHOGI WhiteClaw: case SHOGI BlackDrunk: case SHOGI BlackAlfil: @@ -952,7 +1042,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case SHOGI (PROMO BlackKnight): - if(gameInfo.variant == VariantShogi) goto BlackGold; + if(vari == VariantShogi) goto BlackGold; case SHOGI BlackClaw: case SHOGI WhiteDrunk: case SHOGI WhiteAlfil: @@ -964,7 +1054,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case SHOGI WhiteGnu: case SHOGI BlackGnu: - if(gameInfo.variant == VariantShogi) goto BlackGold; + if(vari == VariantShogi) goto BlackGold; SlideVertical(board, flags, rf, ff, callback, closure); Ferz(board, flags, rf, ff, callback, closure); StepSideways(board, flags, rf, ff, callback, closure); @@ -988,12 +1078,20 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case WhiteWazir: case BlackWazir: - if(gameInfo.variant == VariantXiangqi) { - int palace = (piece == WhiteWazir ? 1 : BOARD_HEIGHT-2); // Palace center - if(ff <= BOARD_WIDTH/2 && !SameColor(board[rf][ff+1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff+1, closure); - if(ff >= BOARD_WIDTH/2 && !SameColor(board[rf][ff-1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff-1, closure); + janggi: + if(vari == VariantXiangqi || vari == VariantJanggi) { + int palace = (piece < BlackPawn ? 1 : BOARD_HEIGHT-2); // Palace center + int middle = BOARD_WIDTH/2; + if(ff <= middle && !SameColor(board[rf][ff+1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff+1, closure); + if(ff >= middle && !SameColor(board[rf][ff-1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff-1, closure); if(rf >= palace && !SameColor(board[rf-1][ff], piece)) callback(board, flags, NormalMove, rf, ff, rf-1, ff, closure); if(rf <= palace && !SameColor(board[rf+1][ff], piece)) callback(board, flags, NormalMove, rf, ff, rf+1, ff, closure); + if(vari == VariantJanggi) { + if(ff == middle) { + if(rf == palace) Ferz(board, flags, rf, ff, callback, closure); + } else if(rf != palace && !SameColor(board[palace][middle], piece)) + callback(board, flags, NormalMove, rf, ff, palace, middle, closure); + } break; } Wazir(board, flags, rf, ff, callback, closure); @@ -1018,20 +1116,21 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case WhiteAlfil: case BlackAlfil: + if(vari == VariantJanggi) { Zebra(board, flags, rf, ff, callback, closure); break; } /* [HGM] support Shatranj pieces */ for (rs = -1; rs <= 1; rs += 2) for (fs = -1; fs <= 1; fs += 2) { rt = rf + 2 * rs; ft = ff + 2 * fs; if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) - && ( gameInfo.variant != VariantXiangqi || + && ( vari != VariantXiangqi || board[rf+rs][ff+fs] == EmptySquare && (2*rf < BOARD_HEIGHT) == (2*rt < BOARD_HEIGHT) ) && !SameColor(board[rf][ff], board[rt][ft])) callback(board, flags, NormalMove, rf, ff, rt, ft, closure); - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantChu || gameInfo.variant == VariantXiangqi) continue; // classical Alfil + if(vari == VariantShatranj || vari == VariantCourier || + vari == VariantChu || vari == VariantXiangqi) continue; // classical Alfil rt = rf + rs; // in unknown variant we assume Modern Elephant, which can also do one step ft = ff + fs; if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) @@ -1039,7 +1138,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, callback(board, flags, NormalMove, rf, ff, rt, ft, closure); } - if(gameInfo.variant == VariantSpartan) + if(vari == VariantSpartan) for(fs = -1; fs <= 1; fs += 2) { ft = ff + fs; if (!(ft < BOARD_LEFT || ft >= BOARD_RGHT) && board[rf][ft] == EmptySquare) @@ -1050,7 +1149,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, /* Make Dragon-Horse also do Dababba moves outside Shogi, for better disambiguation in variant Fairy */ case WhiteCardinal: case BlackCardinal: - if(gameInfo.variant == VariantChuChess) goto DragonHorse; + if(vari == VariantChuChess) goto DragonHorse; for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do for (s = -2; s <= 2; s += 4) { rt = rf + s * d; @@ -1087,7 +1186,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, /* Shogi Lance is unlike anything, and asymmetric at that */ case SHOGI WhiteQueen: - if(gameInfo.variant == VariantChu) goto doQueen; + if(vari == VariantChu) goto doQueen; for(i = 1;; i++) { rt = rf + i; ft = ff; @@ -1100,7 +1199,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case SHOGI BlackQueen: - if(gameInfo.variant == VariantChu) goto doQueen; + if(vari == VariantChu) goto doQueen; for(i = 1;; i++) { rt = rf - i; ft = ff; @@ -1115,7 +1214,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, /* Make Dragon-King Dababba & Rook-like outside Shogi, for better disambiguation in variant Fairy */ case WhiteDragon: case BlackDragon: - if(gameInfo.variant == VariantChuChess || gameInfo.variant == VariantSpartan) goto DragonKing; + if(vari == VariantChuChess || vari == VariantSpartan) goto DragonKing; for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do for (s = -2; s <= 2; s += 4) { rt = rf + s * d; @@ -1143,7 +1242,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case WhiteMarshall: case BlackMarshall: Rook(board, flags, rf, ff, callback, closure); - if(gameInfo.variant == VariantSpartan) // in Spartan Chess Chancellor is used for Dragon King. + if(vari == VariantSpartan) // in Spartan Chess Chancellor is used for Dragon King. Ferz(board, flags, rf, ff, callback, closure); else Knight(board, flags, rf, ff, callback, closure); @@ -1163,12 +1262,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; /* Shogi Rooks are ordinary Rooks */ + case WhiteRook: + case BlackRook: + if(vari == VariantJanggi) PalaceDiags(board, flags, rf, ff, TRUE, callback, closure); case SHOGI WhiteRook: case SHOGI BlackRook: case SHOGI WhitePRook: case SHOGI BlackPRook: - case WhiteRook: - case BlackRook: Rook(board, flags, rf, ff, callback, closure); break; @@ -1190,20 +1290,21 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case WhiteMan: - if(gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN) goto commoner; + if(vari != VariantMakruk && vari != VariantASEAN) goto commoner; case SHOGI WhiteFerz: Ferz(board, flags, rf, ff, callback, closure); StepForward(board, flags, rf, ff, callback, closure); break; case BlackMan: - if(gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN) goto commoner; + if(vari != VariantMakruk && vari != VariantASEAN) goto commoner; case SHOGI BlackFerz: StepBackward(board, flags, rf, ff, callback, closure); case WhiteFerz: case BlackFerz: - if(gameInfo.variant == VariantXiangqi && ff != BOARD_WIDTH>>1) { + if(vari == VariantJanggi) goto janggi; + if(vari == VariantXiangqi && ff != BOARD_WIDTH>>1) { int rt = (piece == BlackFerz ? BOARD_HEIGHT-2 : 1); int ft = BOARD_WIDTH>>1; if(!SameColor(board[rf][ff], board[rt][ft])) @@ -1253,7 +1354,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, // Use Lance as Berolina / Spartan Pawn. case WhiteLance: - if(gameInfo.variant == VariantSuper) goto Amazon; + if(vari == VariantSuper) goto Amazon; if (rf < BOARD_HEIGHT-1 && BlackPiece(board[rf + 1][ff])) callback(board, flags, rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove, @@ -1269,7 +1370,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case BlackLance: - if(gameInfo.variant == VariantSuper) goto Amazon; + if(vari == VariantSuper) goto Amazon; if (rf > 0 && WhitePiece(board[rf - 1][ff])) callback(board, flags, rf <= promoRank ? BlackPromotion : NormalMove, @@ -1314,9 +1415,9 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; case SHOGI (PROMO WhiteFerz): - if(gameInfo.variant == VariantShogi) goto WhiteGold; + if(vari == VariantShogi) goto WhiteGold; case SHOGI (PROMO BlackFerz): - if(gameInfo.variant == VariantShogi) goto BlackGold; + if(vari == VariantShogi) goto BlackGold; case SHOGI WhiteSword: case SHOGI BlackSword: case SHOGI WhitePSword: @@ -1722,7 +1823,7 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant int saveKill = killX; /* Suppress warnings on uninitialized variables */ - if(gameInfo.variant == VariantXiangqi) + if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) king = flags & F_WHITE_ON_MOVE ? WhiteWazir : BlackWazir; if(gameInfo.variant == VariantKnightmate) king = flags & F_WHITE_ON_MOVE ? WhiteUnicorn : BlackUnicorn; @@ -1809,6 +1910,29 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant return cl.fking < BOARD_RGHT ? cl.check : (gameInfo.variant == VariantAtomic)*1000; // [HGM] atomic: return 1000 if we have no king } +typedef struct { int rt, ft, ep; } EnPassantClosure; + +void +EnPassantCallback (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure) +{ + EnPassantClosure *cl = (EnPassantClosure *) closure; + if(rt == cl->rt && ft == cl->ft) { + if(kind = WhiteCapturesEnPassant || kind == BlackCapturesEnPassant) cl->ep = 2; // is e.p. capture + if(epFlag) cl->ep = 0; // for backward compatibility lets any imn pawn multi-push handle by old code + if(viaX != 100) board[EP_FILE] = viaX, board[EP_RANK] = viaY, cl->ep = 1; // generates e.p. rights + } +} + +int +EnPassantTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoChar) +{ // sets e.p. square in board if move generated rights, returns whether move is an e.p. capture + EnPassantClosure cl; + ChessSquare piece = board[rf][ff]; + cl.rt = rt; cl.ft = ft; cl.ep = 0; + MovesFromString(board, flags, ff, rf, -1, -1, 0, 0, pieceDesc[piece], EnPassantCallback, (void *) &cl); + return cl.ep; +} + int HasLion (Board board, int flags) { @@ -1822,14 +1946,15 @@ HasLion (Board board, int flags) ChessMove LegalDrop (Board board, int flags, ChessSquare piece, int rt, int ft) { // [HGM] put drop legality testing in separate routine for clarity - int n; + int n, p = piece; if(appData.debugMode) fprintf(debugFP, "LegalDrop: %d @ %d,%d)\n", piece, ft, rt); if(board[rt][ft] != EmptySquare) return ImpossibleMove; // must drop to empty square - n = PieceToNumber(piece); - if((gameInfo.holdingsWidth == 0 || (flags & F_WHITE_ON_MOVE ? board[n][BOARD_WIDTH-1] : board[BOARD_HEIGHT-1-n][0]) != piece) + if(PieceToChar(piece) == '+') p = CHUDEMOTED(p); + n = PieceToNumber(p); + if((gameInfo.holdingsWidth == 0 || (flags & F_WHITE_ON_MOVE ? board[n][BOARD_WIDTH-1] : board[handSize-1-n][0]) != p) && gameInfo.variant != VariantBughouse) // in bughouse we don't check for availability, because ICS doesn't always tell us return ImpossibleMove; // piece not available - if(gameInfo.variant == VariantShogi) { // in Shogi lots of drops are forbidden! + if(gameInfo.variant == VariantShogi && !autoProm[piece]) { // in Shogi lots of drops are forbidden! (but not in Kyoto/micro-) if((piece == WhitePawn || piece == WhiteQueen) && rt == BOARD_HEIGHT-1 || (piece == BlackPawn || piece == BlackQueen) && rt == 0 || piece == WhiteKnight && rt > BOARD_HEIGHT-3 || @@ -1897,7 +2022,25 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC return(IllegalMove); // [HGM] losers: if there are legal captures, non-capts are illegal if(promoChar == 'x') promoChar = NULLCHAR; // [HGM] is this ever the case? - if(gameInfo.variant == VariantSChess && promoChar && promoChar != '=' && board[rf][ff] != WhitePawn && board[rf][ff] != BlackPawn) { + if(autoProm[piece]) promoChar = NULLCHAR; // ignore promotion characters on auto-promoting pieces + if(gameInfo.variant == VariantSChess) { + if(!gameInfo.holdingsSize) { // holdingless Seirawan + if(promoChar == NULLCHAR) { // gating indicator could be missing + if(flags & F_WHITE_ON_MOVE) { + if(rf == 1) { + if(board[0][ff] != DarkSquare) return WhitePromotion; // mandatory gating, report it as promotion + if(cl.kind == WhiteQueenSideCastle && board[0][0] != DarkSquare) return WhitePromotion; + if(cl.kind == WhiteKingSideCastle && board[0][BOARD_RGHT-1] != DarkSquare) return WhitePromotion; + } + } else { + if(rf == BOARD_HEIGHT-2) { + if(board[BOARD_HEIGHT-1][ff] != DarkSquare) return BlackPromotion; + if(cl.kind == BlackQueenSideCastle && board[BOARD_HEIGHT-1][0] != DarkSquare) return BlackPromotion; + if(cl.kind == BlackKingSideCastle && board[BOARD_HEIGHT-1][BOARD_RGHT-1] != DarkSquare) return BlackPromotion; + } + } + } + } else if(promoChar && promoChar != '=' && board[rf][ff] != WhitePawn && board[rf][ff] != BlackPawn) { if(board[rf][ff] < BlackPawn) { // white if(rf != 0) return IllegalMove; // must be on back rank if(!(board[VIRGIN][ff] & VIRGIN_W)) return IllegalMove; // non-virgin @@ -1911,6 +2054,7 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC if(cl.kind == BlackHSideCastleFR && (ff == BOARD_RGHT-2 || ff == BOARD_RGHT-3)) return ImpossibleMove; if(cl.kind == BlackASideCastleFR && (ff == BOARD_LEFT+2 || ff == BOARD_LEFT+3)) return ImpossibleMove; } + } } else if(gameInfo.variant == VariantChu) { if(cl.kind != NormalMove || promoChar == NULLCHAR || promoChar == '=') return cl.kind; @@ -2058,7 +2202,8 @@ MateTest (Board board, int flags) return myPieces == hisPieces ? MT_STALEMATE : myPieces > hisPieces ? MT_STAINMATE : MT_STEALMATE; else if(gameInfo.variant == VariantLosers) return inCheck ? MT_TRICKMATE : MT_STEALMATE; - else if(gameInfo.variant == VariantGiveaway) return MT_STEALMATE; // no check exists, stalemated = win + else if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantDuck) return MT_STEALMATE; // no check exists, stalemated = win + else if(gameInfo.variant == VariantJanggi && !inCheck) return MT_NONE; // in Janggi turn passing is always an option return inCheck ? MT_CHECKMATE : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj || IS_SHOGI(gameInfo.variant)) ? @@ -2165,33 +2310,40 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) return; } } - } else if(pieceDefs && closure->count > 1 && closure->rtIn >=0) { // [HGM] gen: move is ambiguous under engine-defined rules (and not one-click) - DisambiguateClosure spare = *closure; - if(gameInfo.variant == VariantXiangqi && closure->pieceIn == EmptySquare && closure->ffIn < 0) { + } else if(closure->count > 1 && closure->rtIn >=0) { // [HGM] gen: move is ambiguous under engine-defined rules (and not one-click) + if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) && + (closure->pieceIn == WhitePawn || closure->pieceIn == BlackPawn) && closure->ffIn < 0) { closure->ffIn = closure->ftIn; //closure->pieceIn = (flags & 1 ? BlackPawn : WhitePawn); // forward Pawn push has priority Disambiguate(board, flags, closure); return; } + if(pieceDefs) { + DisambiguateClosure spare = *closure; pieceDefs = FALSE; spare.count = 0; // See if the (erroneous) built-in rules would resolve that GenLegal(board, flags, DisambiguateCallback, (VOIDSTAR) &spare, closure->pieceIn); if(spare.count == 1) *closure = spare; // It does, so use those in stead (game from file saved before gen patch?) pieceDefs = TRUE; + } } if (c == 'x') c = NULLCHAR; // get rid of any 'x' (which should never happen?) if(gameInfo.variant == VariantSChess && c && c != '=' && closure->piece != WhitePawn && closure->piece != BlackPawn) { if(closure->piece < BlackPawn) { // white - if(closure->rf != 0) closure->kind = IllegalMove; // must be on back rank - if(!(board[VIRGIN][closure->ff] & VIRGIN_W)) closure->kind = IllegalMove; // non-virgin + if(closure->rf != !gameInfo.holdingsSize) closure->kind = IllegalMove; // must be on back rank + if(!(board[VIRGIN][closure->ff] & VIRGIN_W)) closure->kind = IllegalMove; // non-virgin + if(gameInfo.holdingsSize) { if(board[PieceToNumber(CharToPiece(ToUpper(c)))][BOARD_WIDTH-2] == 0) closure->kind = ImpossibleMove;// must be in stock if(closure->kind == WhiteHSideCastleFR && (closure->ff == BOARD_RGHT-2 || closure->ff == BOARD_RGHT-3)) closure->kind = ImpossibleMove; if(closure->kind == WhiteASideCastleFR && (closure->ff == BOARD_LEFT+2 || closure->ff == BOARD_LEFT+3)) closure->kind = ImpossibleMove; + } } else { - if(closure->rf != BOARD_HEIGHT-1) closure->kind = IllegalMove; - if(!(board[VIRGIN][closure->ff] & VIRGIN_B)) closure->kind = IllegalMove; // non-virgin + if(closure->rf != BOARD_HEIGHT-1-!gameInfo.holdingsSize) closure->kind = IllegalMove; + if(!(board[VIRGIN][closure->ff] & VIRGIN_B)) closure->kind = IllegalMove; // non-virgin + if(gameInfo.holdingsSize) { if(board[BOARD_HEIGHT-1-PieceToNumber(CharToPiece(ToLower(c)))][1] == 0) closure->kind = ImpossibleMove; if(closure->kind == BlackHSideCastleFR && (closure->ff == BOARD_RGHT-2 || closure->ff == BOARD_RGHT-3)) closure->kind = ImpossibleMove; if(closure->kind == BlackASideCastleFR && (closure->ff == BOARD_LEFT+2 || closure->ff == BOARD_LEFT+3)) closure->kind = ImpossibleMove; + } } } else if(gameInfo.variant == VariantChu) { @@ -2319,11 +2471,14 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p ChessMove kind; char *outp = out, c, capture; CoordsToAlgebraicClosure cl; + int d; if (rf == DROP_RANK) { if(ff == EmptySquare) { strncpy(outp, "--",3); return NormalMove; } // [HGM] pass /* Bughouse piece drop */ - *outp++ = ToUpper(PieceToChar((ChessSquare) ff)); + c = PieceToChar((ChessSquare) ff); + if(c == '+') c = pieceNickName[ff]; // must have nickname for promote drop + *outp++ = ToUpper(c); *outp++ = '@'; *outp++ = ft + AAA; if(rt+ONE <= '9') @@ -2358,6 +2513,9 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; } } else { /* Capture; use style "exd5" */ + if(gameInfo.variant == VariantJanggi && (rt < 2 || rt > BOARD_HEIGHT-3) && ft > BOARD_WIDTH/2 - 2 && ft < BOARD_WIDTH/2 + 2) { + *outp++ = rf + ONE; + } if(capture) *outp++ = 'x'; /* [HGM] Xiangqi has sideway noncaptures across river! */ *outp++ = ft + AAA; @@ -2382,6 +2540,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p case WhiteKing: case BlackKing: + d = (gameInfo.variant == VariantSChess && !gameInfo.holdingsSize); /* Fabien moved code: FRC castling first (if KxR), wild castling second */ /* Code added by Tord: FRC castling. */ if((piece == WhiteKing && board[rt][ft] == WhiteRook) || @@ -2396,14 +2555,14 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p /* Test for castling or ICS wild castling */ /* Use style "O-O" (oh-oh) for PGN compatibility */ else if (rf == rt && - rf == ((piece == WhiteKing) ? 0 : BOARD_HEIGHT-1) && + rf == ((piece == WhiteKing) ? d : BOARD_HEIGHT-1-d) && (ft - ff > 1 || ff - ft > 1) && // No castling if legal King move (on narrow boards!) ((ff == BOARD_WIDTH>>1 && (ft == BOARD_LEFT+2 || ft == BOARD_RGHT-2)) || (ff == (BOARD_WIDTH-1)>>1 && (ft == BOARD_LEFT+1 || ft == BOARD_RGHT-3)))) { if(ft==BOARD_LEFT+1 || ft==BOARD_RGHT-2) - snprintf(out, MOVE_LEN, "O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar)); + snprintf(out, MOVE_LEN, "O-O%c%c%c", promoChar ? '/' : 0, ToUpper(promoChar), ff + AAA); else - snprintf(out, MOVE_LEN, "O-O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar)); + snprintf(out, MOVE_LEN, "O-O-O%c%c%c", promoChar ? '/' : 0, ToUpper(promoChar), ff + AAA); /* This notation is always unambiguous, unless there are kings on both the d and e files, with "wild castling" @@ -2443,12 +2602,15 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p else "N1f3" or "N5xf7", else "Ng1f3" or "Ng5xf7". */ + if(c=='+') { + c = pieceNickName[piece]; // prefer any nick over +X notation + if(c < 'A') *outp++ = c = '+'; + } if( c == '~' || c == '+') { /* [HGM] print nonexistent piece as its demoted version */ - piece = (ChessSquare) (CHUDEMOTED(piece)); + c = PieceToChar((ChessSquare) (CHUDEMOTED(piece))); } - if(c=='+') *outp++ = c; - *outp++ = ToUpper(PieceToChar(piece)); + *outp++ = ToUpper(c); if(*outp = PieceSuffix(piece)) outp++; if (cl.file || (cl.either && !cl.rank)) { @@ -2467,6 +2629,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p if(rt+ONE <= '9') *outp++ = rt + ONE; else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; } + if(autoProm[piece]) promoChar = 0; // no promotion suffix for implied promotions if (IS_SHOGI(gameInfo.variant)) { /* [HGM] in Shogi non-pawns can promote */ *outp++ = promoChar; // Don't bother to correct move type, return value is never used! @@ -2478,8 +2641,15 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p *outp++ = ToUpper(promoChar); } else if (gameInfo.variant == VariantSChess && promoChar) { // and in S-Chess we have gating + ChessSquare victim = board[rt][ft]; + if(piece == WhiteRook && victim == WhiteKing || + piece == BlackRook && victim == BlackKing) { + strncpy(out, "O-O-O", MOVE_LEN); + outp = out + 3 + 2*(ft > ff); + } *outp++ = '/'; *outp++ = ToUpper(promoChar); + if(out[0] == 'O') *outp++ = ff + AAA; } *outp = NULLCHAR; return cl.kind;