'x' };
unsigned char pieceNickName[EmptySquare];
int promoPartner[EmptySquare];
+unsigned char autoProm[EmptySquare];
char
PieceToChar (ChessSquare p)
{
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
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<EmptySquare; p++) {
if((c = pieceToChar[p]) == '.' || c == '~') continue; // does not participate
m = pieceDesc[p]; d = (c == '+' ? pieceToChar[DEMOTED(p)] : c);
(*(int*)cl)++;
}
+static int viaX = 100, viaY = 100, epFlag;
+
void
MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle, int range, char *desc, MoveCallback cb, VOIDSTAR cl)
{
ChessMove promo= NormalMove; ChessSquare pc = board[r][f];
if(pc == DarkSquare) return; // this is not a piece, but a 'hole' in the board
if(flags & F_WHITE_ON_MOVE) his = 2, mine = 1; else his = 1, mine = 2;
- if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
- if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
+ if(gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi) {
+ if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
+ if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
+ }
while(*p) { // more moves to go
- int expo = -1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0;
- char *cont = NULL;
+ int expo = -1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0, put = 0, u = 0;
+ char *cont = NULL, *q;
while(*p == 'i') initial++, desc = ++p;
while(islower(*p)) p++; // skip prefixes
if(!isupper(*p)) return; // syntax error: no atom
if(!dirSet) dirSet = (tx < 0 ? 0xFF // default is all directions, but in continuation leg
: all == 0xFF ? 0xEF : 0x45); // omits backward, and for 4-fold atoms also diags
dirSet = (dirSet << angle | dirSet >> 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
}
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
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] ||
} 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
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
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;
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);
}
}
}
+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
{
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;
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) {
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]) ) {
}
/* 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);
((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
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,
}
/* 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);
((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) {
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);
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;
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);
/* 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:
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:
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);
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);
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)
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)
/* 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;
/* 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;
break;
case SHOGI BlackQueen:
- if(gameInfo.variant == VariantChu) goto doQueen;
+ if(vari == VariantChu) goto doQueen;
for(i = 1;; i++) {
rt = rf - i;
ft = ff;
/* 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;
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);
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;
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]))
// 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,
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,
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:
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;
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)
{
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 ||
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
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;
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)) ?
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) {
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')
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;
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) ||
/* 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"
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)) {
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!
*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;