X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=moves.c;h=a6b066b5fbdbe4bdeb488e74ff53c8a02e68a6a0;hb=v4.9.x;hp=787ab57ab04f55a074909ac8f4aec91bebd3ec19;hpb=01c9a60a2a37ba6441eeacca3a81cbb3ae3d484d;p=xboard.git diff --git a/moves.c b/moves.c index 787ab57..3a07137 100644 --- a/moves.c +++ b/moves.c @@ -5,7 +5,8 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free + * Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -71,7 +72,6 @@ int BlackPiece P((ChessSquare)); int SameColor P((ChessSquare, ChessSquare)); int PosFlags(int index); -extern signed char initialRights[BOARD_FILES]; /* [HGM] all rights enabled, set in InitPosition */ int quickFlag; char *pieceDesc[EmptySquare]; char *defaultDesc[EmptySquare] = { @@ -114,19 +114,34 @@ SameColor (ChessSquare piece1, ChessSquare piece2) #define SameColor(piece1, piece2) (piece1 < EmptySquare && piece2 < EmptySquare && (piece1 < BlackPawn) == (piece2 < BlackPawn) || piece1 == DarkSquare || piece2 == DarkSquare) #endif -char pieceToChar[] = { +unsigned char pieceToChar[EmptySquare+1] = { 'P', 'N', 'B', 'R', 'Q', 'F', 'E', 'A', 'C', 'W', 'M', 'O', 'H', 'I', 'J', 'G', 'D', 'V', 'L', 'S', 'U', 'K', 'p', 'n', 'b', 'r', 'q', 'f', 'e', 'a', 'c', 'w', 'm', 'o', 'h', 'i', 'j', 'g', 'd', 'v', 'l', 's', 'u', 'k', 'x' }; -char pieceNickName[EmptySquare]; +unsigned char pieceNickName[EmptySquare]; +int promoPartner[EmptySquare]; +unsigned char autoProm[EmptySquare]; char PieceToChar (ChessSquare p) { - if((int)p < 0 || (int)p >= (int)EmptySquare) return('x'); /* [HGM] for safety */ - return pieceToChar[(int) p]; + int c; + if((int)p < 0 || (int)p >= (int)EmptySquare) return('?'); /* [HGM] for safety */ + c = pieceToChar[(int) p]; + if(c & 128) c = c & 63 | 64; + return c; +} + +char +PieceSuffix (ChessSquare p) +{ + int c; + if((int)p < 0 || (int)p >= (int)EmptySquare) return 0; /* [HGM] for safety */ + c = pieceToChar[(int) p]; + if(c < 128) return 0; + return SUFFIXES[c - 128 >> 6]; } int @@ -156,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 @@ -191,32 +206,67 @@ CollectPieceDescriptors () // dump all engine defined pieces, and pieces with non-standard names, // but suppress black pieces that are the same as their white counterpart ChessSquare p; - static char buf[MSG_SIZ]; - char *m, c, d, *pieceName = defaultName; - int len; + static char buf[MSG_SIZ], s[2]; + char *m, *pieceName = defaultName; + int len, c, d; *buf = NULLCHAR; 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= BlackPawn && pieceToChar[BLACK_TO_WHITE p] == toupper(c) - && (c != '+' || pieceToChar[DEMOTED BLACK_TO_WHITE p] == d)) { // black member of normal pair + m = pieceDesc[p]; d = (c == '+' ? pieceToChar[DEMOTED(p)] : c); + if(p >= BlackPawn && pieceToChar[BLACK_TO_WHITE p] == (c & ~32) + && (c != '+' || pieceToChar[DEMOTED(BLACK_TO_WHITE p)] == d)) {// black member of normal pair char *wm = pieceDesc[BLACK_TO_WHITE p]; if(!m && !wm || m && wm && !strcmp(wm, m)) continue; // moves as a white piece } else // white or unpaired black - if((p < BlackPawn || CharToPiece(toupper(d)) != EmptySquare) && // white or lone black + if((p < BlackPawn || CharToPiece(d & ~32) != EmptySquare) && // white or lone black !pieceDesc[p] /*&& pieceName[p] == c*/) continue; // orthodox piece known by its usual name // TODO: listing pieces because of unusual name can only be done if we have accurate Betza of all defaults if(!m) m = defaultDesc[p]; + if(!m) continue; len = strlen(buf); - snprintf(buf+len, MSG_SIZ-len, "%s%s%c:%s", len ? ";" : "", c == '+' ? "+" : "", d, m); + *s = (d > 128 ? SUFFIXES[d-128>>6] : 0); d = 64 + (d & 63); + snprintf(buf+len, MSG_SIZ-len, "%s%s%c%s:%s", len ? ";" : "", c == '+' ? "+" : "", d, s, m); } return buf; } +int +LoadPieceDesc (char *s) +{ + ChessSquare piece; + static char suf[] = SUFFIXES; + char *r, *p, *q = s; + int ok = TRUE, promoted, c; + while(q && *s) { + p = s; + q = strchr(s, ';'); + if(q) *q = 0, s = q+1; + if(*p == '+') promoted = 1, p++; else promoted = 0; + c = *p++; + if(!c) { ok = FALSE; continue; } // bad syntax + if(*p && (r = strchr(suf, *p))) c += 64*(r - suf + 1), p++; + if(*p++ != ':') { ok = FALSE; continue; } // bad syntax + if(!strcmp(p, "(null)")) continue; // handle bug in writing of XBoard 4.8.0 + piece = CharToPiece(c); + if(piece >= EmptySquare) { ok = FALSE; continue; } // non-existent piece + if(promoted) { + piece = promoPartner[piece]; + if(pieceToChar[piece] != '+') { ok = FALSE; continue; } // promoted form does not exist + } + ASSIGN(pieceDesc[piece], p); + if(piece < BlackPawn && (pieceToChar[WHITE_TO_BLACK piece] == pieceToChar[piece] + 32 || promoted)) { + ASSIGN(pieceDesc[WHITE_TO_BLACK piece], p); + } + pieceDefs = TRUE; + if(q) *q = ';'; + } + return ok; +} + // [HGM] gen: configurable move generation from Betza notation sent by engine. // Some notes about two-leg moves: GenPseudoLegal() works in two modes, depending on whether a 'kill- // square has been set: without one is generates all moves, and a global int legNr flags in bits 0 and 1 @@ -235,9 +285,14 @@ char symmetry[] = "FBNW.FFW.NKN.NW.QR....W..N"; char xStep[] = "2110.130.102.10.00....0..2"; char yStep[] = "2132.133.313.20.11....1..3"; char dirType[] = "01000104000200000260050000"; +char upgrade[] = "AFCD.BGH.JQL.NO.KW....R..Z"; +char rotate[] = "DRCA.WHG.JKL.NO.QB....F..Z"; + // alphabet "a b c d e f g h i j k l m n o p q r s t u v w x y z " int dirs1[] = { 0,0x3C,0,0,0,0xC3,0,0, 0,0,0,0xF0,0,0,0,0,0,0x0F,0 ,0,0,0 ,0,0,0,0 }; int dirs2[] = { 0,0x18,0,0,0,0x81,0,0xFF,0,0,0,0x60,0,0,0,0,0,0x06,0x66,0,0,0x99,0,0,0,0 }; +int dirs3[] = { 0,0x38,0,0,0,0x83,0,0xFF,0,0,0,0xE0,0,0,0,0,0,0x0E,0xEE,0,0,0xBB,0,0,0,0 }; +int dirs4[] = { 0,0x10,0,0,0,0x01,0,0xFF,0,0,0,0x40,0,0,0,0,0,0x04,0x44,0,0,0x11,0,0,0,0 }; int rot[][4] = { // rotation matrices for each direction { 1, 0, 0, 1 }, @@ -256,24 +311,34 @@ OK (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOID (*(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, char *desc, MoveCallback cb, VOIDSTAR cl) +MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle, int range, char *desc, MoveCallback cb, VOIDSTAR cl) { - char *p = desc; - int mine, his, dir, bit, occup, i; + char buf[80], *p = desc, *atom = NULL; + int mine, his, dir, bit, occup, i, ep, promoRank = -1; + 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(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, retry=0, initial=0, jump=1, skip = 0; - char *cont = NULL; - if(*p == 'i') initial = 1, desc = ++p; + 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 + dx = xStep[*p-'A'] - '0';// step vector of atom + dy = yStep[*p-'A'] - '0'; dirSet = 0; // build direction set based on atom symmetry switch(symmetry[*p-'A']) { case 'B': expo = 0; // bishop, slide - case 'F': // diagonal atom (degenerate 4-fold) - if(tx < 0) { // for continuation legs relative directions are orthogonal! - while(islower(*desc) && (i = dirType[*desc-'a']) != '0') { + case 'F': all = 0xAA; // diagonal atom (degenerate 4-fold) + if(tx >= 0) goto king; // continuation legs specified in K/Q system! + while(islower(*desc) && (i = dirType[*desc-'a']) != '0') { int b = dirs1[*desc-'a']; // use wide version if( islower(desc[1]) && ((i | dirType[desc[1]-'a']) & 3) == 3) { // combinable (perpendicular dim) @@ -281,17 +346,21 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle desc += 2; } else desc++; dirSet |= b; - } - dirSet &= 0x55; if(!dirSet) dirSet = 0x55; - break; } + dirSet &= 0xAA; if(!dirSet) dirSet = 0xAA; + break; case 'R': expo = 0; // rook, slide - case 'W': // orthogonal atom (non-deg 4-fold) + case 'W': all = 0x55; // orthogonal atom (non-deg 4-fold) + if(tx >= 0) goto king; // continuation legs specified in K/Q system! while(islower(*desc) && (dirType[*desc-'a'] & ~4) != '0') dirSet |= dirs2[*desc++-'a']; dirSet &= 0x55; if(!dirSet) dirSet = 0x55; dirSet = (dirSet << angle | dirSet >> 8-angle) & 255; // re-orient direction system break; - case 'N': // oblique atom (degenerate 8-fold) + case 'N': all = 0xFF; // oblique atom (degenerate 8-fold) + if(tx >= 0) goto king; // continuation legs specified in K/Q system! + if(*desc == 'h') { // chiral direction sets 'hr' and 'hl' + dirSet = (desc[1] == 'r' ? 0x55 : 0xAA); desc += 2; + } else while(islower(*desc) && (i = dirType[*desc-'a']) != '0') { int b = dirs2[*desc-'a']; // when alone, use narrow version if(desc[1] == 'h') b = dirs1[*desc-'a'], desc += 2; // dirs1 is wide version @@ -305,47 +374,85 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle if(!dirSet) dirSet = 0xFF; break; case 'Q': expo = 0; // queen, slide - case 'K': // non-deg (pseudo) 8-fold - dirSet=0x55; // start with orthogonal moves - retry = 1; // and schedule the diagonal moves for later + case 'K': all = 0xFF; // non-deg (pseudo) 8-fold + king: + while(islower(*desc) && (i = dirType[*desc-'a']) != '0') { + int b = dirs4[*desc-'a']; // when alone, use narrow version + if(desc[1] == *desc) desc++; // doubling forces alone + else if(islower(desc[1]) && i < '4' + && ((i | dirType[desc[1]-'a']) & 3) == 3) { // combinable (perpendicular dim or same) + b = dirs3[*desc-'a'] & dirs3[desc[1]-'a']; // intersect wide & perp wide + desc += 2; + } else desc++; + dirSet |= b; + } + 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 + else dx = dy, dirSet = ds2; // if no orthogonal directions, do diagonal immediately break; // should not have direction indicators default: return; // syntax error: invalid atom } if(mine == 2 && tx < 0) dirSet = dirSet >> 4 | dirSet << 4 & 255; // invert black moves mode = 0; // build mode mask - if(*desc == 'm') mode |= 4, desc++; - if(*desc == 'c') mode |= his, desc++; - if(*desc == 'd') mode |= mine, desc++; - if(*desc == 'e') mode |= 8, desc++; - if(*desc == 'p') mode |= 32, desc++; - if(*desc == 'g') mode |= 64, desc++; - if(*desc == 'o') mode |= 128, desc++; - if(*desc == 'n') jump = 0, desc++; - while(*desc == 'j') jump++, desc++; - if(*desc == 'a') cont = ++desc; - if(!cont) { - if(!(mode & 15)) mode = his + 4; // no mode spec, use default = mc - } else { - if(mode & 32) mode ^= 256 + 32; // in non-final legs 'p' means 'pass through' - if(!(mode & 0x10F)) mode = his + 0x104; // and default = mcp - } - dx = xStep[*p-'A'] - '0'; // step vector of atom - dy = yStep[*p-'A'] - '0'; + 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(*desc == 'o') mode |= 128, desc++; // wrap around cylinder board + if(*desc == 'y') mode |= 512, desc++; // toggle range on empty square + if(*desc == 'n') jump = 0, desc++; // non-jumping + while(*desc == 'j') jump++, desc++; // must jump (on B,R,Q: skip first square) + if(*desc == 'a') cont = ++desc; // move again after doing what preceded it if(isdigit(*++p)) expo = atoi(p++); // read exponent if(expo > 9) p++; // allow double-digit desc = p; // this is start of next move - if(initial && (board[r][f] != initialPosition[r][f] || + 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] || r == 0 && board[TOUCHED_W] & 1< 1 && dx == 0 && dy == 0) { // castling indicated by O + number - mode |= 16; dy = 1; + r == BOARD_HEIGHT-1 && board[TOUCHED_B] & 1< 0 && dx == 0 && dy == 0) { // castling indicated by O + number + mode |= 1024; dy = 1; + } + if(expo < 0) expo = 1; // use 1 for default + if(!cont) { + if(!(mode & 15)) mode |= his + 4; // no mode spec, use default = mc + } 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(mode & 512) mode ^= 0x304; // and 'y' is m-like 'g' + *atom = upgrade[*atom-'A']; // replace atom, BRQ <-> FWK + atom[1] = atom[2] = '\0'; // make sure any old range is stripped off + if(expo == 1) atom[1] = '0'; // turn other leapers into riders + } + if(!(mode & 0x30F)) mode |= 4; // and default of this leg = m } if(dy == 1) skip = jump - 1, jump = 1; // on W & F atoms 'j' = skip first square do { for(dir=0, bit=1; dir<8; dir++, bit += bit) { // loop over directions - int i = expo, j = skip, hop = mode, vx, vy; + int i = expo, j = skip, hop = mode, vx, vy, loop = 0; if(!(bit & dirSet)) continue; // does not move in this direction - if(dy != 1) j = 0; // + if(dy != 1 || mode & 1024) j = 0; // vx = dx*rot[dir][0] + dy*rot[dir][1]; // rotate step vector vy = dx*rot[dir][2] + dy*rot[dir][3]; if(tx < 0) x = f, y = r; // start square @@ -353,52 +460,92 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle do { // traverse ray x += vx; y += vy; // step to next square if(y < 0 || y >= BOARD_HEIGHT) break; // vertically off-board: always done - if(x < BOARD_LEFT) { if(mode & 128) x += BOARD_RGHT - BOARD_LEFT; else break; } - if(x >= BOARD_RGHT) { if(mode & 128) x -= BOARD_RGHT - BOARD_LEFT; else break; } - if(j) { j--; continue; } // skip irrespective of occupation + 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 > 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(x == f && y == r) occup = 4; else // start square counts as empty + if(x == f && y == r && !loop) occup = 4; else // start square counts as empty (if not around cylinder!) if(board[y][x] < BlackPawn) occup = 0x101; else if(board[y][x] < EmptySquare) occup = 0x102; else occup = 4; + 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 - if(occup & mode & 0x104) // no side effects, merge legs to one move - MovesFromString(board, flags, f, r, x, y, dir, cont, cb, cl); - if(occup & mode & 3 && (killX < 0 || killX == x && killY == y)) { // destructive first leg + char origAtom = *atom; + 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(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(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; - MovesFromString(board, flags, f, r, x, y, dir, cont, &OK, &cnt); // count possible continuations - if(cnt) { // and if there are - if(killX < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); // then generate their first leg + legNr <<= 1; + MovesFromString(board, flags, f, r, x, y, dir, rg, cont, &OK, &cnt); // count possible continuations + legNr >>= 1; + if(cnt) { // and if there are + if(legNr & 1 ? killX < 0 : kill2X < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); // then generate their first leg legNr <<= 1; - MovesFromString(board, flags, f, r, x, y, dir, cont, cb, cl); + MovesFromString(board, flags, f, r, x, y, dir, rg, cont, cb, cl); legNr >>= 1; } } + *atom = origAtom; // undo any interconversion } if(occup != 4) break; // occupied squares always terminate the leg continue; } if(hop & 32+64) { if(occup != 4) { if(hop & 64 && i != 1) i = 2; hop &= 31; } continue; } // hopper - if(mode & 8 && y == board[EP_RANK] && occup == 4 && board[EP_FILE] == x) { // to e.p. square + ep = board[EP_RANK]; + if(mode & 8 && occup == 4 && board[EP_FILE] == x && (y == (ep & 127) || y - vy == ep - 128)) { // to e.p. square (or 2nd e.p. square) cb(board, flags, mine == 1 ? WhiteCapturesEnPassant : BlackCapturesEnPassant, r, f, y, x, cl); } - if(mode & 16) { // castling + if(mode & 1024) { // castling i = 2; // kludge to elongate move indefinitely if(occup == 4) continue; // skip empty squares - if(x == BOARD_LEFT && board[y][x] == initialPosition[y][x]) // reached initial corner piece + if((x == BOARD_LEFT + skip || x > BOARD_LEFT + skip && vx < 0 && board[y][x-1-skip] == DarkSquare) + && board[y][x] == initialPosition[y][x]) { // reached initial corner piece + if(pc != WhiteKing && pc != BlackKing || expo == 1) { // non-royal castling (to be entered as two-leg move via 'Rook') + if(killX < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); if(killX < f) + legNr <<= 1, cb(board, flags, NormalMove, r, f, y, f - expo, cl), legNr >>= 1; + } else cb(board, flags, mine == 1 ? WhiteQueenSideCastle : BlackQueenSideCastle, r, f, y, f - expo, cl); - if(x == BOARD_RGHT-1 && board[y][x] == initialPosition[y][x]) + } + if((x == BOARD_RGHT-1-skip || x < BOARD_RGHT-1-skip && vx > 0 && board[y][x+1+skip] == DarkSquare) + && board[y][x] == initialPosition[y][x]) { + if(pc != WhiteKing && pc != BlackKing || expo == 1) { + if(killX < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); if(killX > f) + legNr <<= 1, cb(board, flags, NormalMove, r, f, y, f + expo, cl), legNr >>= 1; + } else cb(board, flags, mine == 1 ? WhiteKingSideCastle : BlackKingSideCastle, r, f, y, f + expo, cl); + } break; } - if(occup & mode) cb(board, flags, NormalMove, r, f, y, x, cl); // allowed, generate + if(mode & 16 && (board[y][x] == WhiteKing || board[y][x] == BlackKing)) break; // tame piece, cannot capture royal + 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); } - dx = dy = 1; dirSet = 0x99; // prepare for diagonal moves of K,Q - } while(retry--); // and start doing them + dx = dy; dirSet = ds2; // prepare for diagonal moves of K,Q + } while(retry-- && ds2); // and start doing them if(tx >= 0) break; // don't do other atoms in continuation legs } } // next atom @@ -501,12 +648,12 @@ Bishop (Board board, int flags, int rf, int ff, MoveCallback callback, VOIDSTAR void Sting (Board board, int flags, int rf, int ff, int dy, int dx, MoveCallback callback, VOIDSTAR closure) -{ // Lion-like move of Horned Falcon and Souring Eagle +{ // Lion-like move of Horned Falcon and Soaring Eagle int ft = ff + dx, rt = rf + dy; if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) return; legNr += 2; if (!SameColor(board[rf][ff], board[rt][ft])) - callback(board, flags, board[rt][ft] != EmptySquare ? FirstLeg : NormalMove, rf, ff, rt, ft, closure); + callback(board, flags, killX < 0 && board[rt][ft] != EmptySquare ? FirstLeg : NormalMove, rf, ff, rt, ft, closure); legNr -= 2; ft += dx; rt += dy; if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) return; @@ -611,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 @@ -625,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; @@ -636,13 +822,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, if ((flags & F_WHITE_ON_MOVE) != (board[rf][ff] < BlackPawn)) continue; // [HGM] speed: wrong color m = 0; piece = board[rf][ff]; if(PieceToChar(piece) == '~') - piece = (ChessSquare) ( DEMOTED piece ); + piece = (ChessSquare) ( DEMOTED(piece) ); if(filter != EmptySquare && piece != filter) continue; if(pieceDefs && pieceDesc[piece]) { // [HGM] gen: use engine-defined moves - MovesFromString(board, flags, ff, rf, -1, -1, 0, pieceDesc[piece], callback, 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) { @@ -652,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]) ) { @@ -661,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); @@ -687,13 +882,14 @@ 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_TO] & 0x40 ? ff + s : board[LAST_TO] & 255); if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && - (epfile == ff + s || epfile == EP_UNKNOWN) && rf < BOARD_HEIGHT-3 && - board[rf][ff + s] == BlackPawn && + (board[EP_FILE] == ff + s || epfile == EP_UNKNOWN) && rf < BOARD_HEIGHT-3 && + (board[rf][victimFile] == BlackPawn || board[rf][victimFile] == BlackLance) && board[rf+1][ff + s] == EmptySquare) { callback(board, flags, WhiteCapturesEnPassant, rf, ff, rf+1, ff + s, closure); @@ -703,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, @@ -711,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); @@ -737,13 +941,14 @@ 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_TO] & 0x40 ? ff + s : board[LAST_TO] & 255); if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && - (epfile == ff + s || epfile == EP_UNKNOWN) && rf > 2 && - board[rf][ff + s] == WhitePawn && + (board[EP_FILE] == ff + s || epfile == EP_UNKNOWN) && rf > 2 && + (board[rf][victimFile] == WhitePawn || board[rf][victimFile] == WhiteLance) && board[rf-1][ff + s] == EmptySquare) { callback(board, flags, BlackCapturesEnPassant, rf, ff, rf-1, ff + s, closure); @@ -762,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); @@ -791,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; @@ -798,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); @@ -812,15 +1022,18 @@ 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 (PROMOTED WhitePawn): - if(gameInfo.variant == VariantShogi) goto WhiteGold; - case SHOGI (PROMOTED BlackPawn): - if(gameInfo.variant == VariantShogi) goto BlackGold; + case SHOGI (PROMO WhitePawn): + if(vari == VariantShogi) goto WhiteGold; + case SHOGI (PROMO BlackPawn): + if(vari == VariantShogi) goto BlackGold; + case SHOGI WhiteAxe: + case SHOGI BlackAxe: SlideVertical(board, flags, rf, ff, callback, closure); break; - case SHOGI (PROMOTED WhiteKnight): - if(gameInfo.variant == VariantShogi) goto WhiteGold; + case SHOGI (PROMO WhiteKnight): + if(vari == VariantShogi) goto WhiteGold; + case SHOGI WhiteClaw: case SHOGI BlackDrunk: case SHOGI BlackAlfil: Ferz(board, flags, rf, ff, callback, closure); @@ -828,8 +1041,9 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, StepBackward(board, flags, rf, ff, callback, closure); break; - case SHOGI (PROMOTED BlackKnight): - if(gameInfo.variant == VariantShogi) goto BlackGold; + case SHOGI (PROMO BlackKnight): + if(vari == VariantShogi) goto BlackGold; + case SHOGI BlackClaw: case SHOGI WhiteDrunk: case SHOGI WhiteAlfil: Ferz(board, flags, rf, ff, callback, closure); @@ -838,15 +1052,15 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, break; - case SHOGI WhiteStag: - case SHOGI BlackStag: - if(gameInfo.variant == VariantShogi) goto BlackGold; + case SHOGI WhiteGnu: + case SHOGI BlackGnu: + 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); break; - case SHOGI (PROMOTED WhiteQueen): + case SHOGI (PROMO WhiteQueen): case SHOGI WhiteTokin: case SHOGI WhiteWazir: WhiteGold: @@ -854,7 +1068,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, Wazir(board, flags, rf, ff, callback, closure); break; - case SHOGI (PROMOTED BlackQueen): + case SHOGI (PROMO BlackQueen): case SHOGI BlackTokin: case SHOGI BlackWazir: BlackGold: @@ -864,6 +1078,22 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, case WhiteWazir: case BlackWazir: + 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); break; @@ -886,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) @@ -907,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) @@ -918,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; @@ -955,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; @@ -968,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; @@ -983,19 +1214,16 @@ 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) 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; ft = ff + s * (1 - d); if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue; - if (board[rf+rt>>1][ff+ft>>1] == EmptySquare && gameInfo.variant != VariantSpartan) continue; + if (board[rf+rt>>1][ff+ft>>1] == EmptySquare) continue; if (SameColor(board[rf][ff], board[rt][ft])) continue; callback(board, flags, NormalMove, rf, ff, rt, ft, closure); } - if(gameInfo.variant == VariantSpartan) // in Spartan Chess restrict range to modern Dababba - Wazir(board, flags, rf, ff, callback, closure); - else Rook(board, flags, rf, ff, callback, closure); break; @@ -1014,19 +1242,33 @@ 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); break; + case WhiteTower: + case BlackTower: + for (d = 0; d <= 1; d++) // Dababba moves + for (s = -2; s <= 2; s += 4) { + rt = rf + s * d; + ft = ff + s * (1 - d); + if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue; + if (SameColor(board[rf][ff], board[rt][ft])) continue; + callback(board, flags, NormalMove, rf, ff, rt, ft, closure); + } + Wazir(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; @@ -1048,19 +1290,26 @@ 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(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])) + callback(board, flags, NormalMove, rf, ff, rt, ft, closure); + } else /* [HGM] support Shatranj pieces */ Ferz(board, flags, rf, ff, callback, closure); break; @@ -1105,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, @@ -1121,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, @@ -1146,14 +1395,14 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue; if (!(ff == ft && rf == rt) && SameColor(board[rf][ff], board[rt][ft])) continue; i = (killX >= 0 && (rt-killY)*(rt-killY) + (killX-ft)*(killX-ft) < 3); legNr += 2*i; - callback(board, flags, (rt-rf)*(rt-rf) + (ff-ft)*(ff-ft) < 3 && board[rt][ft] != EmptySquare ? FirstLeg : NormalMove, + callback(board, flags, (rt-rf)*(rt-rf) + (ff-ft)*(ff-ft) < 3 && board[rt][ft] != EmptySquare && !i ? FirstLeg : NormalMove, rf, ff, rt, ft, closure); legNr -= 2*i; } break; - case SHOGI WhiteFalcon: - case SHOGI BlackFalcon: + case SHOGI WhiteDagger: + case SHOGI BlackDagger: case SHOGI WhitePDagger: case SHOGI BlackPDagger: SlideSideways(board, flags, rf, ff, callback, closure); @@ -1165,28 +1414,30 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, StepVertical(board, flags, rf, ff, callback, closure); break; - case SHOGI (PROMOTED WhiteFerz): - if(gameInfo.variant == VariantShogi) goto WhiteGold; - case SHOGI (PROMOTED BlackFerz): - if(gameInfo.variant == VariantShogi) goto BlackGold; + case SHOGI (PROMO WhiteFerz): + if(vari == VariantShogi) goto WhiteGold; + case SHOGI (PROMO BlackFerz): + if(vari == VariantShogi) goto BlackGold; + case SHOGI WhiteSword: + case SHOGI BlackSword: case SHOGI WhitePSword: case SHOGI BlackPSword: SlideVertical(board, flags, rf, ff, callback, closure); StepSideways(board, flags, rf, ff, callback, closure); break; - case SHOGI WhiteUnicorn: - case SHOGI BlackUnicorn: + case SHOGI WhiteCat: + case SHOGI BlackCat: Ferz(board, flags, rf, ff, callback, closure); StepVertical(board, flags, rf, ff, callback, closure); break; - case SHOGI WhiteMan: + case SHOGI WhiteCopper: StepDiagForward(board, flags, rf, ff, callback, closure); StepVertical(board, flags, rf, ff, callback, closure); break; - case SHOGI BlackMan: + case SHOGI BlackCopper: StepDiagBackward(board, flags, rf, ff, callback, closure); StepVertical(board, flags, rf, ff, callback, closure); break; @@ -1203,7 +1454,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, SlideVertical(board, flags, rf, ff, callback, closure); break; - case SHOGI WhiteHorned: + case SHOGI WhiteUnicorn: Sting(board, flags, rf, ff, 1, 0, callback, closure); callback(board, flags, NormalMove, rf, ff, rf, ff, closure); if(killX >= 0) break; @@ -1212,7 +1463,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, SlideBackward(board, flags, rf, ff, callback, closure); break; - case SHOGI BlackHorned: + case SHOGI BlackUnicorn: Sting(board, flags, rf, ff, -1, 0, callback, closure); callback(board, flags, NormalMove, rf, ff, rf, ff, closure); if(killX >= 0) break; @@ -1221,7 +1472,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, SlideForward(board, flags, rf, ff, callback, closure); break; - case SHOGI WhiteEagle: + case SHOGI WhiteFalcon: Sting(board, flags, rf, ff, 1, 1, callback, closure); Sting(board, flags, rf, ff, 1, -1, callback, closure); callback(board, flags, NormalMove, rf, ff, rf, ff, closure); @@ -1230,7 +1481,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure, SlideDiagBackward(board, flags, rf, ff, callback, closure); break; - case SHOGI BlackEagle: + case SHOGI BlackFalcon: Sting(board, flags, rf, ff, -1, 1, callback, closure); Sting(board, flags, rf, ff, -1, -1, callback, closure); callback(board, flags, NormalMove, rf, ff, rf, ff, closure); @@ -1290,7 +1541,7 @@ GenLegalCallback (Board board, int flags, ChessMove kind, int rf, int ff, int rt if(rFilter >= 0 && rFilter != rt || fFilter >= 0 && fFilter != ft) return; // [HGM] speed: ignore moves with wrong to-square - if (board[EP_STATUS] == EP_IRON_LION && (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion)) return; //[HGM] lion + if ((int)board[EP_STATUS] == EP_IRON_LION && (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion)) return; //[HGM] lion if (!(flags & F_IGNORE_CHECK) ) { int check, promo = (gameInfo.variant == VariantSpartan && kind == BlackPromotion); @@ -1546,11 +1797,11 @@ CheckTestCallback (Board board, int flags, ChessMove kind, int rf, int ff, int r register CheckTestClosure *cl = (CheckTestClosure *) closure; if (rt == cl->rking && ft == cl->fking) { - if(xqCheckers[EP_STATUS] >= 2 && xqCheckers[rf][ff]) return; // checker is piece with suspended checking power + if((int)xqCheckers[EP_STATUS] >= 2 && xqCheckers[rf][ff]) return; // checker is piece with suspended checking power cl->check++; xqCheckers[rf][ff] = xqCheckers[EP_STATUS] & 1; // remember who is checking (if status == 1) } - if( board[EP_STATUS] == EP_ROYAL_LION && (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion) + if( (int)board[EP_STATUS] == EP_ROYAL_LION && (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion) && (gameInfo.variant != VariantLion || board[rf][ff] != WhiteKing && board[rf][ff] != BlackKing) ) cl->check++; // [HGM] lion: forbidden counterstrike against Lion equated to putting yourself in check } @@ -1568,15 +1819,17 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant { CheckTestClosure cl; ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing; - ChessSquare captured = EmptySquare, ep=0, trampled=0; + ChessSquare captured = EmptySquare, ep=0, trampled=0, trampled2 = 0; + 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; - if(gameInfo.variant == VariantChu) { // strictly speaking this is not needed, as Chu officially has no check + if(gameInfo.variant == VariantChu || gameInfo.variant == VariantShogi) { // strictly speaking this is not needed, as Chu officially has no check int r, f, k = king, royals=0, prince = flags & F_WHITE_ON_MOVE ? WhiteMonarch : BlackMonarch; + if(gameInfo.variant == VariantShogi) prince -= 11; // White/BlackFalcon for(r=0; r 1) return FALSE; // no check if we have two royals (ignores double captureby Lion!) @@ -1585,13 +1838,18 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant } } + if(PieceToChar(king) == '.') return 0; // never in check if the royal piece does not participate + if (rt >= 0) { if (enPassant) { captured = board[rf][ft]; board[rf][ft] = EmptySquare; } else { captured = board[rt][ft]; - if(killX >= 0) { trampled = board[killY][killX]; board[killY][killX] = EmptySquare; } + if(killX >= 0) { + trampled = board[killY][killX]; board[killY][killX] = EmptySquare; killX = -1; saveKill += (kill2X << 16) + (1 << 30); + if(kill2X >= 0) { trampled2 = board[kill2Y][kill2X]; board[kill2Y][kill2X] = EmptySquare; kill2X = -1; } + } } if(rf == DROP_RANK) board[rt][ft] = ff; else { // [HGM] drop board[rt][ft] = board[rf][ff]; @@ -1599,10 +1857,11 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant } ep = board[EP_STATUS]; if( captured == WhiteLion || captured == BlackLion ) { // [HGM] lion: Chu Lion-capture rules - ChessSquare victim = killX < 0 ? EmptySquare : trampled; + ChessSquare victim = saveKill < 0 ? EmptySquare : trampled; if( (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion) && // capturer is Lion (ff - ft > 1 || ft - ff > 1 || rf - rt > 1 || rt - rf > 1) && // captures from a distance - (victim == EmptySquare || victim == WhitePawn || victim == BlackPawn) ) // no or worthless 'bridge' + (victim == EmptySquare || victim == WhitePawn || victim == BlackPawn // no or worthless 'bridge' + || victim == WhiteCobra || victim == BlackCobra) ) // (Pawn or Go Between) board[EP_STATUS] = EP_ROYAL_LION; // on distant Lion x Lion victim must not be pseudo-legally protected } } @@ -1610,10 +1869,10 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant /* For compatibility with ICS wild 9, we scan the board in the order a1, a2, a3, ... b1, b2, ..., h8 to find the first king, and we test only whether that one is in check. */ + cl.check = 0; for (cl.fking = BOARD_LEFT+0; cl.fking < BOARD_RGHT; cl.fking++) for (cl.rking = 0; cl.rking < BOARD_HEIGHT; cl.rking++) { if (board[cl.rking][cl.fking] == king) { - cl.check = 0; if(gameInfo.variant == VariantXiangqi) { /* [HGM] In Xiangqi opposing Kings means check as well */ int i, dir; @@ -1639,13 +1898,39 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant board[rf][ft] = captured; board[rt][ft] = EmptySquare; } else { - if(killX >= 0) board[killY][killX] = trampled; + if(saveKill >= 0) { + if(saveKill & 1<<30) board[kill2Y][kill2X = saveKill >> 16 & 0xFFF] = trampled2; + board[killY][killX = saveKill & 0xFFF] = trampled; + } board[rt][ft] = captured; } board[EP_STATUS] = ep; } - return cl.fking < BOARD_RGHT ? cl.check : 1000; // [HGM] atomic: return 1000 if we have no king + 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 @@ -1661,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 || @@ -1715,7 +2001,7 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC if(quickFlag) flags = flags & ~1 | quickFlag & 1; // [HGM] speed: in quick mode quickFlag specifies side-to-move. if(rf == DROP_RANK) return LegalDrop(board, flags, ff, rt, ft); piece = filterPiece = board[rf][ff]; - if(PieceToChar(piece) == '~') filterPiece = DEMOTED piece; + if(PieceToChar(piece) == '~') filterPiece = DEMOTED(piece); /* [HGM] Cobra and Falcon are wildcard pieces; consider all their moves legal */ /* (perhaps we should disallow moves that obviously leave us in check?) */ @@ -1736,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 @@ -1750,18 +2054,23 @@ 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; if(promoChar != '+') return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove; - if(PieceToChar(CHUPROMOTED board[rf][ff]) != '+') return ImpossibleMove; + if(PieceToChar(CHUPROMOTED(board[rf][ff])) != '+') { + if(PieceToChar(CHUPROMOTED (board[rf][ff] < BlackPawn ? WhitePawn : BlackPawn)) != '.') + return ImpossibleMove; + } return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion; } else if(gameInfo.variant == VariantShogi) { /* [HGM] Shogi promotions. '=' means defer */ if(rf != DROP_RANK && cl.kind == NormalMove) { ChessSquare piece = board[rf][ff]; + int zone = BOARD_HEIGHT/3 + (BOARD_HEIGHT == 8); if(promoChar == PieceToChar(BlackQueen)) promoChar = NULLCHAR; /* [HGM] Kludge */ if(promoChar == 'd' && (piece == WhiteRook || piece == BlackRook) || @@ -1773,7 +2082,7 @@ if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promo return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove; else if(flags & F_WHITE_ON_MOVE) { if( (int) piece < (int) WhiteWazir && - (rf >= BOARD_HEIGHT - BOARD_HEIGHT/3 || rt >= BOARD_HEIGHT - BOARD_HEIGHT/3) ) { + (rf >= BOARD_HEIGHT - zone || rt >= BOARD_HEIGHT - zone) ) { if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 || piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */ cl.kind = promoChar == '=' ? IllegalMove : WhitePromotion; @@ -1781,7 +2090,7 @@ if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promo cl.kind = promoChar == '+' ? WhitePromotion : WhiteNonPromotion; } else cl.kind = promoChar == '+' ? IllegalMove : NormalMove; } else { - if( (int) piece < (int) BlackWazir && (rf < BOARD_HEIGHT/3 || rt < BOARD_HEIGHT/3) ) { + if( (int) piece < (int) BlackWazir && (rf < zone || rt < zone) ) { if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 || piece == BlackKnight && rt < 2 ) /* promotion obligatory */ cl.kind = promoChar == '=' ? IllegalMove : BlackPromotion; @@ -1798,7 +2107,7 @@ if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promo // should test if in zone, really if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight) && HasLion(board, flags)) return IllegalMove; - if(PieceToChar(PROMOTED piece) == '+') return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion; + if(PieceToChar(PROMOTED(piece)) == '+') return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion; } else if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi if (cl.kind == WhitePromotion || cl.kind == BlackPromotion) { @@ -1893,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)) ? @@ -1911,22 +2221,35 @@ DisambiguateCallback (Board board, int flags, ChessMove kind, int rf, int ff, in { register DisambiguateClosure *cl = (DisambiguateClosure *) closure; int wildCard = FALSE; ChessSquare piece = board[rf][ff]; + extern int kifu; // in parser.c // [HGM] wild: for wild-card pieces rt and rf are dummies if(piece == WhiteFalcon || piece == BlackFalcon || piece == WhiteCobra || piece == BlackCobra) - wildCard = TRUE; + wildCard = !pieceDefs; // no wildcards when engine defined pieces if ((cl->pieceIn == EmptySquare || cl->pieceIn == board[rf][ff] || PieceToChar(board[rf][ff]) == '~' - && cl->pieceIn == (ChessSquare)(DEMOTED board[rf][ff]) + && cl->pieceIn == (ChessSquare)(DEMOTED(board[rf][ff])) ) && (cl->rfIn == -1 || cl->rfIn == rf) && (cl->ffIn == -1 || cl->ffIn == ff) && (cl->rtIn == -1 || cl->rtIn == rt || wildCard) && (cl->ftIn == -1 || cl->ftIn == ft || wildCard)) { - if(cl->count && rf == cl->rf && ff == cl->ff) return; // duplicate move + if(cl->count && rf == cl->rf && ff == cl->ff && rt == cl->rt && ft == cl->ft) return; // duplicate move + + if(cl->count == 1 && kifu & 0x7E && cl->rfIn == -1 && cl->ffIn == -1) { // traditional Shogi disambiguation required + int this = 1, other = 1; + if(kifu & 2) this &= (flags & 1 ? rt > rf : rt < rf), other &= (flags & 1 ? cl->rt > cl->rf : cl->rt < cl->rf); + if(kifu & 4) this &= (flags & 1 ? rt < rf : rt > rf), other &= (flags & 1 ? cl->rt < cl->rf : cl->rt > cl->rf); + if(kifu & 8) this &= (rf == rt), other &= (cl->rt == cl->rf); + if(kifu & 0x10) this &= (flags & 1 ? ft <= ff : ft >= ff), other &= (flags & 1 ? cl->ft <= cl->ff : cl->ft >= cl->ff); + if(kifu & 0x20) this &= (flags & 1 ? ft >= ff : ft <= ff), other &= (flags & 1 ? cl->ft >= cl->ff : cl->ft <= cl->ff); + if(kifu & 0x40) this &= (ft == ff), other &= (cl->ft == cl->ff); // should never be used + if(!other) cl->count--; // the old move did not satisfy the requested relative position, erase it + if(!this) return; // the current move does not satisfy the requested relative position, ignore it + } cl->count++; if(cl->count == 1 || board[rt][ft] != EmptySquare) { @@ -1969,7 +2292,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn); if (closure->count == 0) { /* No, it's not even that */ - if(!appData.testLegality && closure->pieceIn != EmptySquare) { + if(!appData.testLegality && !pieceDefs && closure->pieceIn != EmptySquare) { int f, r; // if there is only a single piece of the requested type on the board, use that closure->rt = closure->rtIn, closure->ft = closure->ftIn; for(r=0; rcount > 1) { // [HGM] gen: move is ambiguous under engine-defined rules + } 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) { @@ -2018,6 +2353,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) /* [HGM] Shogi promotions. On input, '=' means defer, '+' promote. Afterwards, c is set to '+' for promotions, NULL other */ if(closure->rfIn != DROP_RANK && closure->kind == NormalMove) { ChessSquare piece = closure->piece; + int zone = BOARD_HEIGHT/3 + (BOARD_HEIGHT == 8); if (c == 'd' && (piece == WhiteRook || piece == BlackRook) || c == 'h' && (piece == WhiteBishop || piece == BlackBishop) || c == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) ) @@ -2025,7 +2361,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) if(c != NULLCHAR && c != '+' && c != '=') closure->kind = IllegalMove; // otherwise specifying a piece is illegal else if(flags & F_WHITE_ON_MOVE) { if( (int) piece < (int) WhiteWazir && - (closure->rf >= BOARD_HEIGHT-(BOARD_HEIGHT/3) || closure->rt >= BOARD_HEIGHT-(BOARD_HEIGHT/3)) ) { + (closure->rf >= BOARD_HEIGHT-zone || closure->rt >= BOARD_HEIGHT-zone) ) { if( (piece == WhitePawn || piece == WhiteQueen) && closure->rt > BOARD_HEIGHT-2 || piece == WhiteKnight && closure->rt > BOARD_HEIGHT-3) /* promotion mandatory */ closure->kind = c == '=' ? IllegalMove : WhitePromotion; @@ -2033,7 +2369,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) closure->kind = c == '+' ? WhitePromotion : WhiteNonPromotion; } else closure->kind = c == '+' ? IllegalMove : NormalMove; } else { - if( (int) piece < (int) BlackWazir && (closure->rf < BOARD_HEIGHT/3 || closure->rt < BOARD_HEIGHT/3) ) { + if( (int) piece < (int) BlackWazir && (closure->rf < zone || closure->rt < zone) ) { if( (piece == BlackPawn || piece == BlackQueen) && closure->rt < 1 || piece == BlackKnight && closure->rt < 2 ) /* promotion obligatory */ closure->kind = c == '=' ? IllegalMove : BlackPromotion; @@ -2048,8 +2384,10 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) if (closure->kind == WhitePromotion || closure->kind == BlackPromotion) { if(c == NULLCHAR) { // missing promoChar on mandatory promotion; use default for variant if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) + gameInfo.variant == VariantMakruk) c = PieceToChar(BlackFerz); + else if(gameInfo.variant == VariantASEAN) + c = PieceToChar(BlackRook); else if(gameInfo.variant == VariantGreat) c = PieceToChar(BlackMan); else if(gameInfo.variant == VariantGrand) @@ -2060,7 +2398,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure) else if(c == 'l' && gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove; } else if (c == '+') { // '+' outside shogi, check if pieceToCharTable enabled it ChessSquare p = closure->piece; - if(p > WhiteMan && p < BlackPawn || p > BlackMan || PieceToChar(PROMOTED p) != '+') + if(p > WhiteMan && p < BlackPawn || p > BlackMan || PieceToChar(PROMOTED(p)) != '+') closure->kind = ImpossibleMove; // used on non-promotable piece else if(gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove; } else if (c != NULLCHAR) closure->kind = IllegalMove; @@ -2105,7 +2443,7 @@ CoordsToAlgebraicCallback (Board board, int flags, ChessMove kind, int rf, int f if ((rt == cl->rt && ft == cl->ft || rt == rf && ft == ff) && // [HGM] null move matches any toSquare (board[rf][ff] == cl->piece || PieceToChar(board[rf][ff]) == '~' && - (ChessSquare) (DEMOTED board[rf][ff]) == cl->piece) + (ChessSquare) (DEMOTED(board[rf][ff])) == cl->piece) ) { if (rf == cl->rf) { if (ff == cl->ff) { @@ -2133,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') @@ -2149,7 +2490,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p if (promoChar == 'x') promoChar = NULLCHAR; piece = board[rf][ff]; - if(PieceToChar(piece)=='~') piece = (ChessSquare)(DEMOTED piece); + if(PieceToChar(piece)=='~') piece = (ChessSquare)(DEMOTED(piece)); switch (piece) { case WhitePawn: @@ -2172,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; @@ -2196,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) || @@ -2210,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" @@ -2241,13 +2586,13 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p cl.kind = IllegalMove; cl.rank = cl.file = cl.either = 0; c = PieceToChar(piece) ; - GenLegal(board, flags, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece)); // [HGM] speed + GenLegal(board, flags, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED(piece))); // [HGM] speed if (cl.kind == IllegalMove && !(flags&F_IGNORE_CHECK)) { /* Generate pretty moves for moving into check, but still return IllegalMove. */ - GenLegal(board, flags|F_IGNORE_CHECK, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece)); + GenLegal(board, flags|F_IGNORE_CHECK, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED(piece))); if (cl.kind == IllegalMove) break; cl.kind = IllegalMove; } @@ -2257,12 +2602,16 @@ 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) (DEMOTED piece - 11*(gameInfo.variant == VariantChu)); + 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)) { *outp++ = ff + AAA; @@ -2280,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! @@ -2291,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; @@ -2316,7 +2673,10 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p int r, f; for(r=0; r