X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=c531656bed48f42a754c3587762858d3b45cdc9f;hb=refs%2Fheads%2Fv4.9.x;hp=0e0a5b891ce958e3c3c0128dbf91a1819be6a46c;hpb=4176af9f411840bc9806909dcd04f359a72d8629;p=xboard.git diff --git a/backend.c b/backend.c index 0e0a5b8..36bdf2d 100644 --- a/backend.c +++ b/backend.c @@ -298,7 +298,7 @@ int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */ static int initPing = -1; int border; /* [HGM] width of board rim, needed to size seek graph */ char bestMove[MSG_SIZ], avoidMove[MSG_SIZ]; -int solvingTime, totalTime; +int solvingTime, totalTime, jawsClock; /* States for ics_getting_history */ #define H_FALSE 0 @@ -420,8 +420,11 @@ PosFlags (int index) case VariantChu: case VariantChuChess: case VariantLion: + case VariantJanggi: flags |= F_NULL_MOVE; break; + case VariantDuck: + flags |= F_IGNORE_CHECK; default: break; } @@ -442,9 +445,9 @@ char *currentDebugFile; // [HGM] debug split: to remember name */ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; -char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; +char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ], hintSrc; char thinkOutput1[MSG_SIZ*10]; -char promoRestrict[MSG_SIZ]; +char promoRestrict[MSG_SIZ], defaultChoice[MSG_SIZ]; ChessProgramState first, second, pairing; @@ -789,7 +792,7 @@ UnloadEngine (ChessProgramState *cps) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", cps); - DestroyChildProcess(cps->pr, 4 + cps->useSigterm); + DestroyChildProcess(cps->pr, 4*!cps->isUCI + cps->useSigterm); } cps->pr = NoProc; if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which); @@ -952,6 +955,7 @@ SaveEngineList () void AddToEngineList (int i) { + if(addToList) { int len; char quote, buf[MSG_SIZ]; char *q = firstChessProgramNames, *p = newEngineCommand; @@ -975,6 +979,7 @@ AddToEngineList (int i) SaveEngineList(); FloatToFront(&appData.recentEngineList, buf); ASSIGN(currentEngine[i], buf); + } } void @@ -1269,6 +1274,8 @@ InitBackEnd1 () case VariantSpartan: /* should work */ case VariantLion: /* should work */ case VariantChuChess: /* should work */ + case VariantJanggi: + case VariantDuck: break; } } @@ -1507,7 +1514,7 @@ LoadGameOrPosition (int gameNr) appData.loadPositionFile)) { DisplayFatalError(_("Bad position file"), 0, 1); return 0; - } + } else startedFromSetupPosition = TRUE; } return 1; } @@ -1520,7 +1527,10 @@ ReserveGame (int gameNr, char resChar) if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match safeStrCpy(buf, lastMsg, MSG_SIZ); DisplayMessage(_("Pick new game"), ""); - flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it + if(flock(fileno(tf), LOCK_EX)) { // lock the tourney file while we are messing with it + abortMatch = TRUE; appData.matchMode = FALSE; DisplayError("Access to tourney file denied", 0); + fclose(tf); return; + } ParseArgsFromFile(tf); p = q = appData.results; if(appData.debugMode) { @@ -1882,7 +1892,7 @@ establish () appData.remoteUser, appData.telnetProgram, appData.icsHost, appData.icsPort); } - return StartChildProcess(buf, "", &icsPR); + return StartChildProcess(buf, "", &icsPR, 0); } } else if (appData.useTelnet) { @@ -2842,6 +2852,8 @@ SeekGraphClick (ClickType click, int x, int y, int moving) return TRUE; } +int remoteEchoOption = FALSE; /* telnet ECHO option */ + void read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error) { @@ -2975,7 +2987,6 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int telnet server that will try to keep WILL ECHO on permanently. */ if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) { - static int remoteEchoOption = FALSE; /* telnet ECHO option */ unsigned char option; oldi = i; switch ((unsigned char) buf[++i]) { @@ -5143,7 +5154,7 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) char buf[MSG_SIZ]; if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') { - if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) { + if(PosFlags(0) & F_NULL_MOVE) { sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : ""); SendToProgram(buf, cps); return; @@ -5193,6 +5204,12 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) char *m = moveList[moveNum]; static char c[2]; *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar + if(gameInfo.variant == VariantDuck) + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves + m[2], m[3] - '0', + m[2], m[3] - '0', + m[5], m[6] - '0', c); + else if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves m[2], m[3] - '0', @@ -5400,10 +5417,15 @@ void CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9]) { if (rf == DROP_RANK) { - if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass - sprintf(move, "%c@%c%c\n", - ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt); + if(ff == EmptySquare) sprintf(move, "@@@@\n"); else { // [HGM] pass + char p = PieceToChar((ChessSquare) ff); + if(p == '+' && pieceNickName[ff] >= 'A') p = pieceNickName[ff]; // prefer nick over +X + sprintf(move, "%c@%c%c\n", + ToUpper(p), AAA + ft, ONE + rt); + } } else { + char c = autoProm[boards[forwardMostMove][rf][ff]]; + if(c && (c == '-' || boards[forwardMostMove][rt][ft] != EmptySquare)) promoChar = '+'; if (promoChar == 'x' || promoChar == NULLCHAR) { sprintf(move, "%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt); @@ -5670,7 +5692,7 @@ Boolean pushed = FALSE; char *lastParseAttempt; void -ParsePV (char *pv, Boolean storeComments, Boolean atEnd) +ParsePV (char *pv, Boolean storeComments, Boolean atEnd, int engine) { // Parse a string of PV moves, and append to current game, behind forwardMostMove int fromX, fromY, toX, toY; char promoChar; ChessMove moveType; @@ -5688,10 +5710,15 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses lastParseAttempt = pv; valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); - if(!valid && nr == 0 && - ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ - nr++; moveType = Comment; // First move has been played; kludge to make sure we continue - // Hande case where played move is different from leading PV move + if(!valid && nr == 0) { + if(engine == hintSrc && *lastHint && appData.ponderNextMove && + ParseOneMove(lastHint, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ + // first PV move is invalid, but the engine provided hint and is probably pondering on it + valid++; goto append_move; // use this hint as first move + } + if(ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ + nr++; moveType = Comment; // PV was for previous position; kludge to make sure we continue + // Handle case where played move is different from leading PV move CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]); @@ -5704,7 +5731,8 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) parseList[endPV-1][0] = NULLCHAR; safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move } - } + } + } pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move; if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE); @@ -5712,6 +5740,7 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) valid++; // allow comments in PV continue; } + append_move: nr++; if(endPV+1 > framePtr) break; // no space, truncate if(!valid) break; @@ -5790,20 +5819,20 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) Collapse(origIndex - lineStart); return FALSE; } - ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); + ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode, pane+1); *start = startPV; *end = index-1; extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5); return TRUE; } char * -PvToSAN (char *pv) +PvToSAN (char *pv, int engine) { static char buf[10*MSG_SIZ]; int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove; *buf = NULLCHAR; if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk - ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it + ParsePV(pv, FALSE, 2, engine+1); // this appends PV to game, suppressing any display of it for(i = forwardMostMove; i= BOARD_RGHT || overrule) continue; - initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[fw][j] = pieces[0][j-gameInfo.holdingsWidth]; initialPosition[pawnRow][j] = WhitePawn; initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn; - if(gameInfo.variant == VariantXiangqi) { + if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) { if(j&1) { initialPosition[pawnRow][j] = initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare; @@ -6439,7 +6473,7 @@ InitPosition (int redraw) initialPosition[BOARD_HEIGHT-1][j] = BlackRook; } } - initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth]; + initialPosition[BOARD_HEIGHT-1-fw][j] = pieces[1][j-gameInfo.holdingsWidth]; } if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing; if( (gameInfo.variant == VariantShogi) && !overrule ) { @@ -6731,12 +6765,14 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions + gameInfo.variant == VariantJanggi || !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move return FALSE; if(legal[toY][toX] == 4) return FALSE; piece = boards[currentMove][fromY][fromX]; + if(PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = WhiteKing; // always obey PTC table if(gameInfo.variant == VariantChu) { promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3; if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it @@ -6744,10 +6780,10 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i } else if(gameInfo.variant == VariantShogi) { promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8); highestPromotingPiece = (int)WhiteAlfil; - if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece; + if(PieceToChar(piece) != '+' && PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece; } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) { promotionZoneSize = 3; - } + } else if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) promotionZoneSize = 2; // Treat Lance as Pawn when it is not representing Amazon or Lance if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) { @@ -6759,12 +6795,13 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i if((int)piece >= BlackPawn) { if(toY >= promotionZoneSize && fromY >= promotionZoneSize) return FALSE; - if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE; + if(fromY < promotionZoneSize && (gameInfo.variant == VariantChuChess || gameInfo.variant == VariantFairy)) return FALSE; highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece; } else { if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize && fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE; - if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess) + if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && + (gameInfo.variant == VariantChuChess || gameInfo.variant == VariantFairy)) return FALSE; } @@ -6888,6 +6925,8 @@ DrawPosition (int repaint, Board board) } else DrawPositionX(repaint, board); } +static Boolean prelude; + int OKToStartUserMove (int x, int y) { @@ -6939,11 +6978,11 @@ OKToStartUserMove (int x, int y) if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode case EditGame: case AnalyzeMode: - if (!white_piece && WhiteOnMove(currentMove)) { + if (!white_piece && WhiteOnMove(currentMove) && prelude != 2) { DisplayMoveError(_("It is White's turn")); return FALSE; } - if (white_piece && !WhiteOnMove(currentMove)) { + if (white_piece && (!WhiteOnMove(currentMove) || prelude == 2)) { DisplayMoveError(_("It is Black's turn")); return FALSE; } @@ -7053,7 +7092,7 @@ int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; -int doubleClick; +int doubleClick, doDuck = -1, duckX, duckY; Boolean addToBookFlag; static Board rightsBoard, nullBoard; @@ -7061,7 +7100,7 @@ void UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) { ChessMove moveType; - ChessSquare pup; + ChessSquare pup = boards[currentMove][fromY][fromX]; int ff=fromX, rf=fromY, ft=toX, rt=toY; /* Check if the user is playing in turn. This is complicated because we @@ -7109,14 +7148,14 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) case AnalyzeMode: case Training: if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move - if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && - (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { + if ((int) pup >= (int) BlackPawn && + (int) pup < (int) EmptySquare) { /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); return; } - } else { + } else if(pup < (int) BlackPawn) { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); @@ -7180,7 +7219,7 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) DrawPosition(FALSE, boards[currentMove]); return; } else if (toX >= 0 && toY >= 0) { - if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) { + if(toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) { ChessSquare p = boards[0][rf][ff]; if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else @@ -7202,6 +7241,8 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--; if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; } + } else if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize && gatingPiece == EmptySquare && (fromY == 0 || fromY == BOARD_HEIGHT-1)) { + boards[0][fromY][fromX] = DarkSquare; // empty on gating rank -> dark } else boards[0][fromY][fromX] = gatingPiece; ClearHighlights(); @@ -7225,6 +7266,7 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; fromY = DROP_RANK; + if(autoProm[fromX] && shiftKey) fromX = CHUPROMOTED(fromX); } /* [HGM] always test for legality, to get promotion info */ @@ -7263,6 +7305,21 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) return; } + if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize && !promoChar) { + int piece, rank, file; + switch(moveType) { // detect castlings + case WhiteKingSideCastle: file = BOARD_RGHT-1; rank = 0; break; + case WhiteQueenSideCastle: file = BOARD_LEFT; rank = 0; break; + case BlackKingSideCastle: file = BOARD_RGHT-1; rank = BOARD_HEIGHT-1; break; + case BlackQueenSideCastle: file = BOARD_LEFT; rank = BOARD_HEIGHT-1; break; + default: file = -1; + } + if(file >= 0 && (piece = boards[currentMove][rank][file]) != DarkSquare) { // castling that should have gated at Rook + promoChar = ToLower(PieceToChar(piece)); + } + } + + if(moveType == Swap && killX < 0) killX = fromX, killY = fromY, moveType = NormalMove; FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } @@ -7336,6 +7393,17 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom ClearMap(); + if(gameInfo.variant == VariantDuck) { + if(doDuck < 0) { // Duck move not yet indicated + Board testBoard; + CopyBoard(testBoard, boards[forwardMostMove]); // do the move without Duck + ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard); + DrawPosition(TRUE, testBoard); + duckX = fromX; duckY = fromY; doDuck = promoChar; // remember promotion + return 1; + } + } + /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); @@ -7396,8 +7464,10 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); first.maybeThinking = TRUE; } else if(fromY == DROP_RANK && fromX == EmptySquare) { + if(PosFlags(0) & F_NULL_MOVE) SendMoveToProgram(currentMove, &first); else { if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard SendBoard(&first, currentMove+1); + } if(second.analyzing) { if(!second.useSetboard) SendToProgram("undo\n", &second); SendBoard(&second, currentMove+1); @@ -7428,6 +7498,13 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom GameEnds(WhiteWins, "White mates", GE_PLAYER); } break; + case MT_STEALMATE: + if (WhiteOnMove(currentMove)) { + GameEnds(WhiteWins, "Stalemated", GE_PLAYER); + } else { + GameEnds(BlackWins, "Stalemated", GE_PLAYER); + } + break; case MT_STALEMATE: GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER); break; @@ -7502,11 +7579,12 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO typedef char Markers[BOARD_RANKS][BOARD_FILES]; Markers *m = (Markers *) closure; if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : - kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4)) + kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4)) { (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare || kind == WhiteCapturesEnPassant - || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3; - else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3; + || kind == BlackCapturesEnPassant), legal[rt][ft] = 3; + if(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)) (*m)[rt][ft] = 5; + } else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3; } static int hoverSavedValid; @@ -7555,7 +7633,9 @@ CanPromote (ChessSquare piece, int y) int zone = (gameInfo.variant == VariantChuChess ? 3 : 1); if(gameMode == EditPosition) return FALSE; // no promotions when editing position // some variants have fixed promotion piece, no promotion at all, or another selection mechanism + if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) zone = 2; if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi || + gameInfo.variant == VariantJanggi || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE; @@ -7608,6 +7688,7 @@ void ReportClick(char *action, int x, int y) Boolean right; // instructs front-end to use button-1 events as if they were button 3 Boolean deferChoice; int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode +ChessSquare selectedType = EmptySquare; void LeftClick (ClickType clickType, int xPix, int yPix) @@ -7631,6 +7712,31 @@ LeftClick (ClickType clickType, int xPix, int yPix) x = BOARD_WIDTH - 1 - x; } + saveAnimate = appData.animate; + if(clickType == Press && doDuck >= 0) { // extra click for Duck placement + if(boards[currentMove][y][x] != EmptySquare && (x != duckX || y != duckY)) return; // ignore clicks on occupied square + killX = x; killY = y; + appData.animate = FALSE; + UserMoveEvent(duckX, duckY, toX, toY, doDuck); // promoChoice remembered in doDuck + fromX = fromY = killX = killY = -1; + appData.animate = saveAnimate; + doDuck = -1; + return; + } + + if(gameMode == EditPosition && fromX < 0 && selectedType != EmptySquare && + (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY)) { // placement click + if(clickType == Press) { + ChessSquare newType = boards[currentMove][y][x]; + if(newType != EmptySquare) { + do { if(++newType == EmptySquare) newType = WhitePawn; } while(PieceToChar(newType) == '.'); + } else newType = selectedType; + EditPositionMenuEvent(newType, x, y); + createX = x; createY = y; // remember where we last dropped + } + return; + } + // map clicks in offsetted holdings back to true coords (or switch the offset) if(x == BOARD_RGHT+1) { if(handOffsets & 1) { @@ -7729,7 +7835,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves doubleClick = TRUE; gatingPiece = boards[currentMove][y][x]; } - fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR; + fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { @@ -7739,10 +7845,18 @@ LeftClick (ClickType clickType, int xPix, int yPix) ReportClick("lift", x, y); MarkTargetSquares(0); if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX]; + if(gameInfo.variant == VariantSChess && !gameInfo.holdingsWidth) { // auto-gating + int white = (boards[currentMove][fromY][fromX] < BlackPawn); + if(fromY == (white ? 1 : BOARD_HEIGHT - 2) // piece on shifted back-rank + && boards[currentMove][VIRGIN][fromX] & (white ? VIRGIN_W : VIRGIN_B)) { // and is virgin + int p = boards[currentMove][fromY - 2*white + 1][fromX]; + if(p != DarkSquare) gatingPiece = p; + } + } DragPieceBegin(xPix, yPix, FALSE); dragging = 1; if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; - selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0; + selectFlag = 0; lastX = xPix; lastY = yPix; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); Sweep(0); // Pawn that is going to promote: preview promotion piece DisplayMessage("", _("Pull pawn backwards to under-promote")); } @@ -7781,7 +7895,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc)))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); - killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR; + killX = killY = kill2X = kill2Y = -1; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) { second = FALSE; // first double-click rather than scond click doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves @@ -7796,7 +7910,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } if (OKToStartUserMove(x, y)) { if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating - (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && + (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && gameInfo.holdingsWidth && y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) gatingPiece = boards[currentMove][fromY][fromX]; else gatingPiece = doubleClick ? fromP : EmptySquare; @@ -7807,7 +7921,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) DragPieceBegin(xPix, yPix, FALSE); if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { promoSweep = defaultPromoChoice; - selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0; + selectFlag = 0; lastX = xPix; lastY = yPix; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); Sweep(0); // Pawn that is going to promote: preview promotion piece } } @@ -7875,7 +7989,6 @@ LeftClick (ClickType clickType, int xPix, int yPix) piece = boards[currentMove][fromY][fromX]; - saveAnimate = appData.animate; if (clickType == Press) { if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece; if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { @@ -7914,7 +8027,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) MarkTargetSquares(1); } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square - *promoRestrict = 0; appData.flashCount = saveFlash; + safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); appData.flashCount = saveFlash; if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } else { @@ -8040,6 +8153,13 @@ LeftClick (ClickType clickType, int xPix, int yPix) } } +void +Deselect () +{ + fromX = -1; + ClearHighlights(); +} + int RightClick (ClickType action, int x, int y, int *fromX, int *fromY) { // front-end-free part taken out of PieceMenuPopup @@ -8084,8 +8204,34 @@ RightClick (ClickType action, int x, int y, int *fromX, int *fromY) case EditPosition: if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; if (xSqr < 0 || ySqr < 0) return -1; - if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu +// if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr; + if(appData.pieceMenu) { // select click + ChessSquare newType = boards[currentMove][ySqr][xSqr]; + boards[currentMove][ySqr][xSqr] = EmptySquare; + if(newType == EmptySquare && selectedType == EmptySquare) { // click on empty summons context menu if not deselect + DisplayNote( _("To edit the position you can:\n" + "* Move pieces around with left mouse button\n" + "* Copy pieces by moving with Ctrl key pressed\n" + " OR by starting the move with a double-click\n" + "* Click a K, R or P a second time to toggle its rights\n" + "* 'Lift' a piece with right-click for multi-dropping\n" + "* Drop a piece of the lifted type by left-click on empty\n" + "* Right-click on empty to finish dropping\n" + "* Adjust the type of a dropped piece by clicking it again\n" + "* Click the active clock (repeatedly) to clear the board\n" + "* Click the inactive clock to change the side to move")); + return -2; + } + selectedType = newType; Deselect(); + if(selectedType == EmptySquare) DisplayMessage("", ""); else { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "left-click places %s %c", newType < BlackPawn ? _("white") : _("black"), ToUpper(PieceToChar(newType))); + DisplayMessage("", buf); + } + DrawPosition(FALSE, boards[currentMove]); + return -2; + } if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) { ChessSquare p = boards[currentMove][ySqr][xSqr]; do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.'); @@ -8384,7 +8530,7 @@ Adjudicate (ChessProgramState *cps) Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor); /* Some material-based adjudications that have to be made before stalemate test */ - if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) { + if((gameInfo.variant == VariantAtomic || gameInfo.variant == VariantDuck) && nr[WhiteKing] + nr[BlackKing] < 2) { // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated if(canAdjudicate && appData.checkMates) { @@ -8447,7 +8593,7 @@ Adjudicate (ChessProgramState *cps) reason = "Xboard adjudication: Stalemate"; if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw - if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: + if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantDuck) // [HGM] losers: boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win else if(gameInfo.variant == VariantSuicide) // in suicide it depends boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE : @@ -8843,7 +8989,8 @@ DeferredBookMove (void) static int savedWhitePlayer, savedBlackPlayer, pairingReceived; static ChessProgramState *stalledEngine; -static char stashedInputMove[MSG_SIZ], abortEngineThink; +static char stashedInputMove[MSG_SIZ], abortEngineThink, startPieceToChar[MSG_SIZ]; +static char preludeText[MSG_SIZ], diceRoll[MSG_SIZ]; void HandleMachineMove (char *message, ChessProgramState *cps) @@ -8851,7 +8998,6 @@ HandleMachineMove (char *message, ChessProgramState *cps) static char firstLeg[20], legs; char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; char realname[MSG_SIZ]; - int fromX, fromY, toX, toY; ChessMove moveType; char promoChar, roar; char *p, *pv=buf1; @@ -8910,6 +9056,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) { + int fromX, fromY, toX, toY; if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume. if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which); safeStrCpy(stashedInputMove, message, MSG_SIZ); @@ -9000,7 +9147,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h // [HGM] lion: (some very limited) support for Alien protocol killX = killY = kill2X = kill2Y = -1; - if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move + if(machineMove[strlen(machineMove)-1] == ',') { // move ends in comma: non-final leg of composite move if(legs++) return; // middle leg contains only redundant info, ignore (but count it) safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives return; @@ -9011,9 +9158,15 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs! safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20); } - if(firstLeg[0]) { // there was a previous leg; + if(firstLeg[0]) { // there was a previous leg + char buf[20], *p = machineMove+1, *q = buf+1, f; + if(gameInfo.variant == VariantDuck) { // Duck Chess: 1st leg is FIDE move, 2nd is Duck + int l = strlen(firstLeg), promo = machineMove[4]; + sscanf(machineMove, "%c%d%c%d", &f, &killY, &f, &killY); killX = f - AAA; killY -= ONE - '0'; + safeStrCpy(machineMove, firstLeg, 20); + snprintf(machineMove + l - 1, 20 - l, ";%c%d%c", killX + AAA, killY + ONE - '0', promo); + } else { // only support case where same piece makes two step - char buf[20], *p = machineMove+1, *q = buf+1, f; safeStrCpy(buf, machineMove, 20); while(isdigit(*q)) q++; // find start of to-square safeStrCpy(machineMove, firstLeg, 20); @@ -9022,7 +9175,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling) safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global - firstLeg[0] = NULLCHAR; legs = 0; + } + firstLeg[0] = NULLCHAR; legs = 0; } if (!ParseOneMove(machineMove, forwardMostMove, &moveType, @@ -9049,13 +9203,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h ChessMove moveType; moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), fromY, fromX, toY, toX, promoChar); + if(gameInfo.variant == VariantDuck && + (killX < BOARD_LEFT || killX >= BOARD_RGHT || killY < 0 || killY >= BOARD_HEIGHT || + boards[forwardMostMove][killY][killX] != EmptySquare && (killX != fromX || killY != fromY))) moveType = IllegalMove; if(moveType == IllegalMove) { snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, buf1, GE_XBOARD); return; - } else if(!appData.fischerCastling) + } else if(!appData.fischerCastling && toX != BOARD_WIDTH>>1) /* [HGM] Kludge to handle engines that send FRC-style castling when they shouldn't (like TSCP-Gothic) */ switch(moveType) { @@ -9073,7 +9230,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } } hintRequested = FALSE; - lastHint[0] = NULLCHAR; + lastHint[0] = NULLCHAR; hintSrc = 0; bookRequested = FALSE; /* Program may be pondering now */ cps->maybeThinking = TRUE; @@ -9251,35 +9408,45 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } if (!strncmp(message, "setup ", 6) && - (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || + (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || prelude || NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize)) ) { // [HGM] allow first engine to define opening position - int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ]; + int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ], *p = varName; + Board tmp; if(appData.icsActive || forwardMostMove != 0 || cps != &first) return; *buf = NULLCHAR; if(sscanf(message, "setup (%s", buf) == 1) { - s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES); - ASSIGN(appData.pieceToCharTable, buf); + char *ptc = strlen(buf) < 3 ? "PNBRQKpnbrqk" : buf; + s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, ptc, SUFFIXES); + ASSIGN(appData.pieceToCharTable, ptc); + if(gameInfo.variant == VariantUnknown) safeStrCpy(startPieceToChar, ptc, MSG_SIZ); } + CopyBoard(tmp, boards[0]); dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName); if(dummy >= 3) { while(message[s] && message[s++] != ' '); - if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand || - dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant + if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand || dummy == 4) { + // engine wants to change board format or variant // if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings - if(hand > h) handSize = hand; else handSize = h; - appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand; - if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant + if(hand > h) handSize = hand; else handSize = h; + appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand; + if(dummy == 4) { + p += prelude = (*p == '!'); // strip leading '!', and enable acceptance of further setups + gameInfo.variant = StringToVariant(p); // parent variant + } InitPosition(1); // calls InitDrawingSizes to let new parameters take effect if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition - startedFromSetupPosition = FALSE; +// startedFromSetupPosition = FALSE; } } - if(startedFromSetupPosition) return; + fromX = fromY = -1; ParseFEN(boards[0], &dummy, message+s, FALSE); - DrawPosition(TRUE, boards[0]); + if(dummy) prelude *= 2; CopyBoard(initialPosition, boards[0]); - startedFromSetupPosition = TRUE; + MarkTargetSquares(1); ClearHighlights(); + if(startedFromSetupPosition) CopyBoard(boards[0], tmp); + DrawPosition(TRUE, boards[0]); + if(!prelude) startedFromSetupPosition = TRUE; return; } if(sscanf(message, "piece %s %s", buf2, buf1) == 2) { @@ -9303,7 +9470,13 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } return; } - if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) { + if(sscanf(message, "choice %s", promoRestrict) == 1) { + if(gameMode == BeginningOfGame && cps == &first && + (!appData.testLegality || *engineVariant != NULLCHAR || gameInfo.variant == VariantFairy)) { + safeStrCpy(defaultChoice, promoRestrict, MSG_SIZ); // redefine promotion choice for entire game + return; + } + if(appData.testLegality) return; if(deferChoice) { LeftClick(Press, 0, 0); // finish the click that was interrupted } else if(promoSweep != EmptySquare) { @@ -9312,6 +9485,31 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } return; } + if(!strncmp(message, "dice ", 5)) { // [HGM] dice: provide dice rolls + if(gameMode != TwoMachinesPlay || (cps->twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) { + static char previousRoll[MSG_SIZ]; + char buf[MSG_SIZ], *p = message + 5; + int n, k, l; + sprintf(buf, "pips"); + while(sscanf(p, "%d", &n) == 1) { + k = (random() + 256*random() & 0xFFFF)*n >> 16; + l = strlen(buf); + snprintf(buf + l, MSG_SIZ - 1 - l, " %d/%d", k+1, n); + while(*++p > ' ') {} + } + l = strlen(diceRoll); + if(l == 0) safeStrCpy(previousRoll, diceRoll+1, MSG_SIZ); // remember series from previous turn + snprintf(diceRoll + l, MSG_SIZ - l, "%s", buf + 4); // accumulate rolls for display + strcat(buf, "\n"); SendToProgram(buf, cps); + if(gameMode != TwoMachinesPlay) { + int human = (gameMode == MachinePlaysWhite) != WhiteOnMove(forwardMostMove); + if(human) snprintf(buf, MSG_SIZ, "Your dice roll:%s (mine %s)", diceRoll, previousRoll); + else snprintf(buf, MSG_SIZ, "My dice roll:%s", diceRoll); + DisplayTitle(buf); + } else if(cps->other->dice) SendToProgram(buf, cps->other); + } + return; + } /* [HGM] Allow engine to set up a position. Don't ask me why one would * want this, I was asked to put it in, and obliged. */ @@ -9369,7 +9567,12 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11); SendToICS(buf1); } - } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment + } else { + if(forwardMostMove == 0 && !strncmp(message+11, "prelude ", 8)) { // allow engine to add Prelude tag + strncpy(preludeText, message+19, MSG_SIZ); + } else + if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment + } return; } if (!strncmp(message, "tellall ", 8)) { @@ -9617,6 +9820,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h * Look for hint output */ if (sscanf(message, "Hint: %s", buf1) == 1) { + int fromX, fromY, toX, toY; if (cps == &first && hintRequested) { hintRequested = FALSE; if (ParseOneMove(buf1, forwardMostMove, &moveType, @@ -9634,7 +9838,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h DisplayError(buf2, 0); } } else { - safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0])); + safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0])); hintSrc = (cps != &first) + 1; } return; } @@ -9879,7 +10083,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h curscore = -curscore; } - if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1); + if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1, cps == &second); if(*bestMove) { // rememer time best EPD move was first found int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2; @@ -10120,6 +10324,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h curscore = -curscore; } + if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1, cps == &second); + cpstats.depth = plylev; cpstats.nodes = nodes; cpstats.time = time; @@ -10128,7 +10334,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h cpstats.movelist[0] = '\0'; if (buf1[0] != NULLCHAR) { - safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) ); + safeStrCpy( cpstats.movelist, pv, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) ); } cpstats.ok_to_send = 0; @@ -10338,16 +10544,18 @@ ParseGameHistory (char *game) void ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) { - ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0; + ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; + int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0, isEP = 0; int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1; /* [HGM] compute & store e.p. status and castling rights for new position */ /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */ if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A; + if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) promoRank = 2; oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8; board[EP_STATUS] = EP_NONE; - board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040; + board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = toX + 256*toY; if (fromY == DROP_RANK) { /* must be first */ @@ -10360,27 +10568,41 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) // ChessSquare victim; int i; + piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion and igui */ + board[fromY][fromX] = EmptySquare; + if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something // victim = board[killY][killX], + if(gameInfo.variant == VariantDuck) { // killXY used to indicate Duck move + int r, f; + for(r=0; r= 0 && kill2Y >= 0) killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare; + if(killed == EmptySquare) // [HGM] unload: kill-square also used for push destination + board[killY][killX] = board[toY][toX], board[EP_STATUS] = EP_NONE; + } } if( board[toY][toX] != EmptySquare ) { board[EP_STATUS] = EP_CAPTURE; if( (fromX != toX || fromY != toY) && // not igui! - (captured == WhiteLion && board[fromY][fromX] != BlackLion || - captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules + (captured == WhiteLion && piece != BlackLion || + captured == BlackLion && piece != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed } } - pawn = board[fromY][fromX]; + pawn = piece; if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture - if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) { + board[EP_RANK] = epRank; board[EP_FILE] = epFile; // Ughh! cleared it too soon :-( + isEP = EnPassantTest(board, PosFlags(currentMove), fromY, fromX, toY, toX, promoChar); + if(isEP != 1) board[EP_RANK] = board[EP_FILE] = 100;// assumes only e.p. capturing pieces generate rights! + if(isEP == 2) { captured = board[lastRank][lastFile]; // remove victim board[lastRank][lastFile] = EmptySquare; pawn = EmptySquare; // kludge to suppress old e.p. code @@ -10395,29 +10617,27 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) if( pawn == WhitePawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers board[EP_STATUS] = EP_PAWN_MOVE; - if( toY-fromY>=2) { - board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2); + if(!isEP && toY-fromY>=2) { + board[EP_FILE] = (2*fromX + toX + 1)/3; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2); if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn && gameInfo.variant != VariantBerolina || toX < fromX) board[EP_STATUS] = toX | berolina; if(toX fromX) board[EP_STATUS] = toX; - board[LAST_TO] = toX + 256*toY; } } else if( pawn == BlackPawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers board[EP_STATUS] = EP_PAWN_MOVE; - if( toY-fromY<= -2) { - board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2); + if(!isEP && toY-fromY<= -2) { + board[EP_FILE] = (2*fromX + toX + 1)/3; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2); if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && gameInfo.variant != VariantBerolina || toX < fromX) board[EP_STATUS] = toX | berolina; if(toX fromX) board[EP_STATUS] = toX; - board[LAST_TO] = toX + 256*toY; } } @@ -10433,22 +10653,25 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } if(gameInfo.variant == VariantSChess) { // update virginity - if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving - if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B; - if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture - if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B; + int offs = !gameInfo.holdingsSize; + if(fromY == offs) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving + if(fromY == BOARD_HEIGHT-1-offs) board[VIRGIN][fromX] &= ~VIRGIN_B; + if(toY == offs) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture + if(toY == BOARD_HEIGHT-1-offs) board[VIRGIN][toX] &= ~VIRGIN_B; } if (fromX == toX && fromY == toY && killX < 0) return; - piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */ king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */ if(gameInfo.variant == VariantKnightmate) king += (int) WhiteUnicorn - (int) WhiteKing; + if(autoProm[piece]) { + board[toY][toX] = (autoProm[piece] != '!' || board[toY][toX] != EmptySquare ? CHUPROMOTED(piece) : piece); + } else if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own - board[toY][toX] = piece; board[fromY][fromX] = EmptySquare; + board[toY][toX] = piece; board[toY][toX + (killX < fromX ? 1 : -1)] = killed; board[EP_STATUS] = EP_NONE; // capture was fake! } else @@ -10459,20 +10682,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } else /* Code added by Tord: */ /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */ - if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook || - board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) { + if (piece == WhiteKing && board[toY][toX] == WhiteRook || + piece == WhiteRook && board[toY][toX] == WhiteKing) { board[EP_STATUS] = EP_NONE; // capture was fake! - board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if((toX > fromX) != (piece == WhiteRook)) { board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook; } else { board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook; } - } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook || - board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) { + } else if (piece == BlackKing && board[toY][toX] == BlackRook || + piece == BlackRook && board[toY][toX] == BlackKing) { board[EP_STATUS] = EP_NONE; - board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if((toX > fromX) != (piece == BlackRook)) { board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook; @@ -10482,52 +10703,47 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) /* End of code added by Tord */ } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) { - board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling - board[toY][toX] = piece; - } else if (board[fromY][fromX] == king + board[toY][toX] = piece; // never castle if King has virgin moves defined on it other than castling + } else if (piece == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX > fromX+1) { for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++) ; // castle with nearest piece board[fromY][toX-1] = board[fromY][rookX]; board[fromY][rookX] = EmptySquare; - board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - } else if (board[fromY][fromX] == king + } else if (piece == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--) ; // castle with nearest piece board[fromY][toX+1] = board[fromY][rookX]; board[fromY][rookX] = EmptySquare; - board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) + } else if ((piece == WhitePawn && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi || + piece == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX])); - board[fromY][fromX] = EmptySquare; } else if ((fromY >= BOARD_HEIGHT>>1) && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4) && (toX != fromX) && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantJanggi && gameInfo.variant != VariantBerolina && (pawn == WhitePawn) && (board[toY][toX] == EmptySquare)) { if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present - board[fromY][fromX] = EmptySquare; board[toY][toX] = piece; captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare; } else if ((fromY == BOARD_HEIGHT-4) && (toX == fromX) && gameInfo.variant == VariantBerolina - && (board[fromY][fromX] == WhitePawn) + && (piece == WhitePawn) && (board[toY][toX] == EmptySquare)) { - board[fromY][fromX] = EmptySquare; board[toY][toX] = WhitePawn; if(oldEP & EP_BEROLIN_A) { captured = board[fromY][fromX-1]; @@ -10535,64 +10751,58 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) }else{ captured = board[fromY][fromX+1]; board[fromY][fromX+1] = EmptySquare; } - } else if (board[fromY][fromX] == king + } else if (piece == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX > fromX+1) { for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++) ; board[fromY][toX-1] = board[fromY][rookX]; board[fromY][rookX] = EmptySquare; - board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - } else if (board[fromY][fromX] == king + } else if (piece == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--) ; board[fromY][toX+1] = board[fromY][rookX]; board[fromY][rookX] = EmptySquare; - board[fromY][fromX] = EmptySquare; board[toY][toX] = king; } else if (fromY == 7 && fromX == 3 - && board[fromY][fromX] == BlackKing + && piece == BlackKing && toY == 7 && toX == 5) { - board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackKing; board[fromY][7] = EmptySquare; board[toY][4] = BlackRook; } else if (fromY == 7 && fromX == 3 - && board[fromY][fromX] == BlackKing + && piece == BlackKing && toY == 7 && toX == 1) { - board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackKing; board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; - } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) + } else if ((piece == BlackPawn && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi || + piece == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY < promoRank && promoChar ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX])); - board[fromY][fromX] = EmptySquare; } else if ((fromY < BOARD_HEIGHT>>1) && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4) && (toX != fromX) && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantJanggi && gameInfo.variant != VariantBerolina && (pawn == BlackPawn) && (board[toY][toX] == EmptySquare)) { if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY; - board[fromY][fromX] = EmptySquare; board[toY][toX] = piece; captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare; } else if ((fromY == 3) && (toX == fromX) && gameInfo.variant == VariantBerolina - && (board[fromY][fromX] == BlackPawn) + && (piece == BlackPawn) && (board[toY][toX] == EmptySquare)) { - board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackPawn; if(oldEP & EP_BEROLIN_A) { captured = board[fromY][fromX-1]; @@ -10601,8 +10811,6 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[fromY][fromX+1] = EmptySquare; } } else { - ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to) - board[fromY][fromX] = EmptySquare; board[toY][toX] = piece; } } @@ -10619,6 +10827,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) /* and erasing image if necessary */ p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar)); if(p < (int) BlackPawn) { /* white drop */ + if(PieceToChar(p) == '+') p = CHUDEMOTED(p); // promoted drop p -= (int)WhitePawn; p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) p = 0; @@ -10628,6 +10837,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[p][BOARD_WIDTH-2] = 0; } else { /* black drop */ p -= (int)BlackPawn; + if(PieceToChar(p) == '+') p = CHUDEMOTED(p); p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) p = 0; if(--board[handSize-1-p][1] <= 0) @@ -10682,8 +10892,21 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } - if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) { - board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating + if(gameInfo.variant == VariantSChess) { + int file = fromX, white = (piece < BlackPawn); + int rank = fromY-2*white+1; + if(promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) { + if(!gameInfo.holdingsSize && rank >= 0 && rank < BOARD_HEIGHT) { + if(board[rank][file] == DarkSquare) file = (toX > fromX ? BOARD_RGHT -1 : BOARD_LEFT); // must be castling + board[rank][file] = DarkSquare; + } + board[fromY][file] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating + } + if(!gameInfo.holdingsSize) { + rank = toY+2*white-1; + if(captured != EmptySquare && (rank == 0 || rank == BOARD_HEIGHT-1)) + board[rank][toX] = DarkSquare; // gateable piece disappears with its gator + } } else if(promoChar == '+') { /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */ @@ -10719,17 +10942,25 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) ChessSquare p = boards[forwardMostMove][toY][toX]; // forwardMostMove++; // [HGM] bare: moved downstream + diceRoll[0] = NULLCHAR; // [HGM] dice: consumed by this move + if(gameInfo.variant != VariantDuck) { if(kill2X >= 0) x = kill2X, y = kill2Y; else if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one + } (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), fromY, fromX, y, x, (killX < 0)*promoChar, s); if(kill2X >= 0 && kill2Y >= 0) sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture - if(killX >= 0 && killY >= 0) + if(killX >= 0 && killY >= 0) { + if(gameInfo.variant == VariantDuck) { + if(promoChar) sprintf(s + strlen(s), "=%c,%c%d", ToUpper(promoChar), killX + AAA, killY + ONE - '0'); + else sprintf(s + strlen(s), ",%c%d", killX + AAA, killY + ONE - '0'); + } else sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar); + } if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ int timeLeft; static int lastLoadFlag=0; int king, piece; @@ -10901,7 +11132,7 @@ NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdi int width = 8, height = 8, holdings = 0; // most common sizes if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix // correct the deviations default for each variant - if( v == VariantXiangqi ) width = 9, height = 10; + if( v == VariantXiangqi || v == VariantJanggi ) width = 9, height = 10; if( v == VariantShogi ) width = 9, height = 9, holdings = 7; if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5; if( v == VariantCapablanca || v == VariantCapaRandom || @@ -11103,13 +11334,15 @@ void StartChessProgram (ChessProgramState *cps) { char buf[MSG_SIZ]; - int err; + int err, priority = appData.niceEngines; if (appData.noChessProgram) return; cps->initDone = FALSE; + if(cps->isUCI && strstr(appData.adapterCommand, "%niceEngines")) priority = 0; // no nicing on nice adapters! + if (strcmp(cps->host, "localhost") == 0) { - err = StartChildProcess(cps->program, cps->dir, &cps->pr); + err = StartChildProcess(cps->program, cps->dir, &cps->pr, priority); } else if (*appData.remoteShell == NULLCHAR) { err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr); } else { @@ -11120,7 +11353,7 @@ StartChessProgram (ChessProgramState *cps) snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell, cps->host, appData.remoteUser, cps->program); } - err = StartChildProcess(buf, "", &cps->pr); + err = StartChildProcess(buf, "", &cps->pr, 0); } if (err != 0) { @@ -11423,7 +11656,7 @@ SaveEngineSettings (int n) s = malloc(len); snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q); FREE(firstChessProgramNames); firstChessProgramNames = s; // new list - if(*engineListFile) SaveEngineList(); + SaveEngineList(); } // following implemented as macro to avoid type limitations @@ -11672,8 +11905,10 @@ NextMatchGame () appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening Reset(FALSE, first.pr != NoProc); + if(startPieceToChar[0]) SetCharTableEsc(pieceToChar, startPieceToChar, SUFFIXES); res = LoadGameOrPosition(matchGame); // setup game appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too! + if(startPieceToChar[0]) SetCharTableEsc(pieceToChar, startPieceToChar, SUFFIXES); if(!res) return; // abort when bad game/pos file if(appData.epd) {// in EPD mode we make sure first engine is to move firstWhite = !(forwardMostMove & 1); @@ -12014,7 +12249,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DestroyChildProcess(first.pr, 4 + first.useSigterm); + DestroyChildProcess(first.pr, 4*!first.isUCI + first.useSigterm); first.reload = TRUE; } first.pr = NoProc; @@ -12039,7 +12274,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DestroyChildProcess(second.pr, 4 + second.useSigterm); + DestroyChildProcess(second.pr, 4*!second.isUCI + second.useSigterm); second.reload = TRUE; } second.pr = NoProc; @@ -12222,6 +12457,7 @@ Reset (int redraw, int init) pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves deadRanks = 0; // assume entire board is used handSize = 0; + prelude = FALSE; preludeText[0] = NULLCHAR; for(i=0; i 0 || startedFromSetupPosition) { char *fen = PositionToFEN(backwardMostMove, NULL, 1); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); + if(*preludeText) fprintf(f, "[Prelude \"%s\"]\n", preludeText); fprintf(f, "\n{--------------\n"); PrintPosition(f, backwardMostMove); fprintf(f, "--------------}\n"); @@ -14694,12 +14948,12 @@ ExitEvent (int status) DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ ); + DestroyChildProcess(first.pr, 4*!first.isUCI + first.useSigterm /* [AS] first.useSigterm */ ); } if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ ); + DestroyChildProcess(second.pr, 4*!second.isUCI + second.useSigterm /* [AS] second.useSigterm */ ); } if (first.isr != NULL) { RemoveInputSource(first.isr); @@ -14757,6 +15011,7 @@ PauseEvent () else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second); StartClocks(); } else { + if(jawsClock && gameMode == EditGame) StartClocks(); DisplayBothClocks(); } if (gameMode == PlayFromGameFile) { @@ -14824,6 +15079,12 @@ PauseEvent () pausing = TRUE; ModeHighlight(); break; + case EditGame: + if(!jawsClock) return; + StopClocks(); + pausing = TRUE; + ModeHighlight(); + break; } } } @@ -15007,12 +15268,12 @@ MachineWhiteEvent () if (gameMode == EditPosition) EditPositionDone(TRUE); - +/* if (!WhiteOnMove(currentMove)) { DisplayError(_("It is not White's turn"), 0); return; } - +*/ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) ExitAnalyzeMode(); @@ -15028,6 +15289,14 @@ MachineWhiteEvent () gameMode = MachinePlaysWhite; pausing = FALSE; ModeHighlight(); + + if(!WhiteOnMove(currentMove)) { + first.bookSuspend = TRUE; + StartClocks(); + firstMove = FALSE; + return; + } + SetGameInfo(); snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); @@ -15090,12 +15359,12 @@ MachineBlackEvent () if (gameMode == EditPosition) EditPositionDone(TRUE); - +/* if (WhiteOnMove(currentMove)) { DisplayError(_("It is not Black's turn"), 0); return; } - +*/ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) ExitAnalyzeMode(); @@ -15107,6 +15376,14 @@ MachineBlackEvent () gameMode = MachinePlaysBlack; pausing = FALSE; ModeHighlight(); + + if(WhiteOnMove(currentMove)) { + first.bookSuspend = TRUE; + StartClocks(); + firstMove = FALSE; + return; + } + SetGameInfo(); snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); @@ -15127,6 +15404,7 @@ MachineBlackEvent () SetMachineThinkingEnables(); first.maybeThinking = TRUE; StartClocks(); + firstMove = FALSE; if (appData.autoFlipView && flipView) { flipView = !flipView; @@ -15525,6 +15803,17 @@ EditGameEvent () } void +DisplayClockMessage (char *msg) +{ + if(!msg) SetClockMessage(0, NULL), SetClockMessage(1, NULL); else + if(blackPlaysFirst) SetClockMessage(1, msg), SetClockMessage(0, _("Set White_to Move")); + else SetClockMessage(0, msg), SetClockMessage(1, _("Set Black_to Move")); + DisplayBothClocks(); +} + +int clearCycle; + +void EditPositionEvent () { int i; @@ -15550,7 +15839,9 @@ EditPositionEvent () currentMove = forwardMostMove = backwardMostMove = 0; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); DisplayMove(-1); - if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), ""); + DisplayMessage(_("Keep Ctrl pressed to duplicate pieces"), ""); + DisplayClockMessage(_("Clear_Board")); + clearCycle = 0; } void @@ -15574,12 +15865,13 @@ EditPositionDone (Boolean fakeRights) { int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; + selectedType = EmptySquare; startedFromSetupPosition = TRUE; InitChessProgram(&first, FALSE); if(fakeRights) { // [HGM] suppress this if we just pasted a FEN. int r, f; boards[0][EP_STATUS] = EP_NONE; - for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights; + for(f=0; f<=6; f++) boards[0][CASTLING][f] = NoRights; for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p. if(rightsBoard[r][f]) { ChessSquare p = boards[0][r][f]; @@ -15612,12 +15904,16 @@ EditPositionDone (Boolean fakeRights) } DisplayTitle(""); DisplayMessage("", ""); + DisplayClockMessage(NULL); timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; gameMode = EditGame; ModeHighlight(); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); ClearHighlights(); /* [AS] */ + if(!appData.icsActive && !appData.noChessProgram) { + if(forwardMostMove) MachineWhiteEvent(); else MachineBlackEvent(); + } } /* Pause for `ms' milliseconds */ @@ -15691,7 +15987,6 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) char buf[MSG_SIZ]; ChessSquare piece = boards[0][y][x]; static Board erasedBoard, currentBoard, menuBoard, nullBoard; - static int lastVariant; int baseRank = BOARD_HEIGHT-1, hasRights = 0; if (gameMode != EditPosition && gameMode != IcsExamining) return; @@ -15709,7 +16004,6 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) SendToICS(ics_prefix); SendToICS("clearboard\n"); } else { - int nonEmpty = 0; for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare; if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */ for (y = 0; y < BOARD_HEIGHT; y++) { @@ -15719,15 +16013,13 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) AAA + x, ONE + y); SendToICS(buf); } - } else if(boards[0][y][x] != DarkSquare) { - if(boards[0][y][x] != p) nonEmpty++; - boards[0][y][x] = p; } } } CopyBoard(rightsBoard, nullBoard); if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards int r, i; + CopyBoard(menuBoard, initialPosition); for(r = 0; r < BOARD_HEIGHT; r++) { for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates ChessSquare p = menuBoard[r][x]; @@ -15735,19 +16027,36 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) } } menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted - DisplayMessage("Clicking clock again restores position", ""); - if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]); - if(!nonEmpty) { // asked to clear an empty board - CopyBoard(boards[0], menuBoard); - } else - if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board - CopyBoard(boards[0], initialPosition); - } else - if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard) - && !CompareBoards(nullBoard, erasedBoard)) { + switch(clearCycle++) { + case 0: + DisplayClockMessage(_("Clear_More")); + CopyBoard(erasedBoard, boards[0]); + CopyBoard(boards[0], menuBoard); break; + case 1: + DisplayClockMessage(_("Restore_Position")); + for(r = 0; r < BOARD_HEIGHT; r++) { + ChessSquare king = WhiteKing; + for(x = 0; x < BOARD_WIDTH; x++) { // erase except Kings and center + ChessSquare p = (x == BOARD_LEFT-1 || x == BOARD_RGHT ? 0 : EmptySquare); + if((x < BOARD_WIDTH/2 - 2 || x >= BOARD_WIDTH/2 + 2 || + r < BOARD_HEIGHT/2 - 2 || r >= BOARD_HEIGHT/2 + 2) && + boards[0][r][x] != king && boards[0][r][x] != king + BlackPawn + && boards[0][r][x] != DarkSquare && boards[0][r][x] != p) boards[0][r][x] = EmptySquare; + } + } + break; + case 2: + if(!CompareBoards(erasedBoard, initialPosition)) { // initial position if notyet there + DisplayClockMessage(_("Resume_Edit")); + CopyBoard(boards[0], initialPosition); + break; + } + clearCycle++; + case 3: + DisplayClockMessage(_("Clear_Board")); CopyBoard(boards[0], erasedBoard); - } else - CopyBoard(erasedBoard, currentBoard); + } + clearCycle &= 3; // wrap for(i=0; iexcludeMoves, cps)) continue; if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; + if (BoolFeature(&p, "dice", &cps->dice, cps)) continue; // [HGM] dice if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); @@ -18173,6 +18486,7 @@ SwitchClocks (int newMoveNr) break; case EditGame: + if(jawsClock) break; case PlayFromGameFile: case IcsExamining: return; @@ -18409,7 +18723,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) int i, j, fromX, fromY, toX, toY; int whiteToPlay, haveRights = nrCastlingRights; char buf[MSG_SIZ]; - char *p, *q; + char *p, *q, c; int emptycount; ChessSquare piece; @@ -18431,12 +18745,13 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; } emptycount = 0; } - if(PieceToChar(piece) == '+') { + c = PieceToChar(piece); + if(c == '+') { /* [HGM] write promoted pieces as '+' (Shogi) */ - *p++ = '+'; - piece = (ChessSquare)(CHUDEMOTED(piece)); + if(autoProm[piece] && pieceNickName[piece] >= 'A') c = pieceNickName[piece]; else + *p++ = '+', piece = (ChessSquare)(CHUDEMOTED(piece)), c = PieceToChar(piece); } - *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece)); + *p++ = (piece == DarkSquare ? '*' : c); if(*p = PieceSuffix(piece)) p++; if(p[-1] == '~') { /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ @@ -18568,6 +18883,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) } if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantJanggi && gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) { /* En passant target square */ @@ -18670,6 +18986,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) #endif } else if (*p == '*') { board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++; + } else if(*p == '0') { + p++; board[i][(j++) + gameInfo.holdingsWidth] = EmptySquare; } else if (isdigit(*p)) { emptycount = *p++ - '0'; while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */ @@ -18820,7 +19138,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) /* set defaults in case FEN is incomplete */ board[EP_STATUS] = EP_UNKNOWN; board[TOUCHED_W] = board[TOUCHED_B] = 0; - for(i=0; i