X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=804da7d19d14d219248754064e29c88a7f69f253;hb=d9f4b584106f4d7477158476ff792f6fa0b2630a;hp=b914fe40c69d0376c834685e219d3015804cf488;hpb=f2344ce98b5950c7a047c3ee29959ad9f26ae8ae;p=xboard.git diff --git a/backend.c b/backend.c index b914fe4..804da7d 100644 --- a/backend.c +++ b/backend.c @@ -63,6 +63,7 @@ int flock(int f, int code); #else +#include #define DoSleep( n ) if( (n) >= 0) sleep(n) #define SLASH '/' @@ -4860,6 +4861,11 @@ SendMoveToProgram(moveNum, cps) { char buf[MSG_SIZ]; + if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') { + // null move in variant where engine does not understand it (for analysis purposes) + SendBoard(cps, moveNum + 1); // send position after move in stead. + return; + } if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -4899,8 +4905,13 @@ SendMoveToProgram(moveNum, cps) else SendToProgram(moveList[moveNum], cps); } else if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed - snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0', - moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); + if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move + if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else + snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0], + moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); + } else + snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0', + moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); SendToProgram(buf, cps); } else SendToProgram(moveList[moveNum], cps); @@ -5071,6 +5082,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) char move[7]; { 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); } else { @@ -6076,7 +6088,7 @@ DefaultPromoChoice(int white) static int autoQueen; // [HGM] oneclick int -HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) +HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect) { /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */ /* [HGM] add Shogi promotions */ @@ -6158,9 +6170,9 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) } // give caller the default choice even if we will not make it *promoChoice = ToLower(PieceToChar(defaultPromoChoice)); - if(gameInfo.variant == VariantShogi) *promoChoice = '+'; - if(appData.sweepSelect && gameInfo.variant != VariantGreat - && gameInfo.variant != VariantShogi + if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+'); + if( sweepSelect && gameInfo.variant != VariantGreat + && gameInfo.variant != VariantGrand && gameInfo.variant != VariantSuper) return FALSE; if(autoQueen) return FALSE; // predetermined @@ -6219,7 +6231,6 @@ OKToStartUserMove(x, y) (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -6247,6 +6258,8 @@ OKToStartUserMove(x, y) } break; + case PlayFromGameFile: + if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode case EditGame: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); @@ -6290,6 +6303,7 @@ OKToStartUserMove(x, y) } if (currentMove != forwardMostMove && gameMode != AnalyzeMode && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode + && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line && gameMode != AnalyzeFile && gameMode != Training) { DisplayMoveError(_("Displayed position is not current")); return FALSE; @@ -6300,7 +6314,7 @@ OKToStartUserMove(x, y) Boolean OnlyMove(int *x, int *y, Boolean captures) { DisambiguateClosure cl; - if (appData.zippyPlay) return FALSE; + if (appData.zippyPlay || !appData.testLegality) return FALSE; switch(gameMode) { case MachinePlaysBlack: case IcsPlayingWhite: @@ -6381,7 +6395,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -6407,6 +6420,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } break; + case PlayFromGameFile: + if(!shiftKey ||!appData.variations) return; // [HGM] only variations case EditGame: case IcsExamining: case BeginningOfGame: @@ -6522,6 +6537,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* [HGM] always test for legality, to get promotion info */ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar); + + if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove; + /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { @@ -6598,7 +6616,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ - if ((gameMode == AnalyzeMode || gameMode == EditGame) + if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey) && currentMove < forwardMostMove) { if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game else forwardMostMove = currentMove; @@ -6663,6 +6681,9 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) // [HGM] book: if program might be playing, let it use book bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); first.maybeThinking = TRUE; + } else if(fromY == DROP_RANK && fromX == EmptySquare) { + if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard + SendBoard(&first, currentMove+1); } else SendMoveToProgram(forwardMostMove-1, &first); if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; @@ -6740,7 +6761,7 @@ void MarkTargetSquares(int clear) { int x, y; - if(!appData.markers || !appData.highlightDragging || + if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi || !appData.testLegality || gameMode == EditPosition) return; if(clear) { for(x=0; x= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click return; } + if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { + ChessSquare piece = boards[currentMove][fromY][fromX]; + DragPieceBegin(xPix, yPix, TRUE); dragging = 1; + promoSweep = defaultPromoChoice; + if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); + DrawPosition(FALSE, boards[currentMove]); + return; + } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); @@ -7039,7 +7072,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece)); - if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { + if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { // [HGM] super: promotion to captured piece selected from holdings @@ -7120,7 +7153,7 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) toX = xSqr; toY = ySqr; lastX = x, lastY = y; if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY; NextPiece(0); - return -2; + return 2; // grab case IcsObserving: if(!appData.icsEngineAnalyze) return -1; case IcsPlayingWhite: @@ -7341,6 +7374,28 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol } int +CompareWithRights(Board b1, Board b2) +{ + int rights = 0; + if(!CompareBoards(b1, b2)) return FALSE; + if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE; + /* compare castling rights */ + if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) ) + rights++; /* King lost rights, while rook still had them */ + if( b1[CASTLING][2] != NoRights ) { /* king has rights */ + if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] ) + rights++; /* but at least one rook lost them */ + } + if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) ) + rights++; + if( b1[CASTLING][5] != NoRights ) { + if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] ) + rights++; + } + return rights == 0; +} + +int Adjudicate(ChessProgramState *cps) { // [HGM] some adjudications useful with buggy engines // [HGM] adjudicate: made into separate routine, which now can be called after every move @@ -8989,15 +9044,19 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) oldEP = (signed char)board[EP_STATUS]; board[EP_STATUS] = EP_NONE; - if( board[toY][toX] != EmptySquare ) - board[EP_STATUS] = EP_CAPTURE; - if (fromY == DROP_RANK) { /* must be first */ + if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change. + board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible + return; + } piece = board[toY][toX] = (ChessSquare) fromX; } else { int i; + if( board[toY][toX] != EmptySquare ) + board[EP_STATUS] = EP_CAPTURE; + if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants @@ -9679,7 +9738,7 @@ WriteTourneyFile(char *results) fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); if(searchTime > 0) - fprintf(f, "-searchTime \"%s\"\n", appData.searchTime); + fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60); else { fprintf(f, "-mps %d\n", appData.movesPerSession); fprintf(f, "-tc %s\n", appData.timeControl); @@ -10428,6 +10487,7 @@ Reset(redraw, init) redraw, init, gameMode); } CleanupTail(); // [HGM] vari: delete any stored variations + CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -10535,8 +10595,8 @@ AutoPlayOneMove() if (currentMove >= forwardMostMove) { if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); } - gameMode = EditGame; - ModeHighlight(); +// gameMode = EndOfGame; +// ModeHighlight(); /* [AS] Clear current move marker at the end of a game */ /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */ @@ -10983,7 +11043,133 @@ ReloadGame(offset) } } +int keys[EmptySquare+1]; + +int +PositionMatches(Board b1, Board b2) +{ + int r, f, sum=0; + switch(appData.searchMode) { + case 1: return CompareWithRights(b1, b2); + case 2: + for(r=0; r>8 ^ rand()<<6 ^rand()<<20; + initDone = TRUE; + } + dummyInfo.variant = VariantNormal; + FREE(dummyInfo.fen); dummyInfo.fen = NULL; + dummyInfo.whiteRating = 0; + dummyInfo.blackRating = 0; + FREE(dummyInfo.date); dummyInfo.date = NULL; + fseek(f, lg->offset, 0); + yynewfile(f); + CopyBoard(boards[scratch], initialPosition); // default start position + while(1) { + yyboardindex = scratch + (plyNr&1); + quickFlag = 1; + next = Myylex(); + quickFlag = 0; + switch(next) { + case PGNTag: + if(plyNr) return -1; // after we have seen moves, any tags will be start of next game + ParsePGNTag(yy_text, &dummyInfo); + if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL; + default: + continue; + + case XBoardGame: + case GNUChessGame: + if(plyNr) return -1; // after we have seen moves, this is for new game + continue; + + case AmbiguousMove: // we cannot reconstruct the game beyond these two + case ImpossibleMove: + case WhiteWins: // game ends here with these four + case BlackWins: + case GameIsDrawn: + case GameUnfinished: + return -1; + + case IllegalMove: + if(appData.testLegality) return -1; + case WhiteCapturesEnPassant: + case BlackCapturesEnPassant: + case WhitePromotion: + case BlackPromotion: + case WhiteNonPromotion: + case BlackNonPromotion: + case NormalMove: + case WhiteKingSideCastle: + case WhiteQueenSideCastle: + case BlackKingSideCastle: + case BlackQueenSideCastle: + case WhiteKingSideCastleWild: + case WhiteQueenSideCastleWild: + case BlackKingSideCastleWild: + case BlackQueenSideCastleWild: + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; + promoChar = currentMoveString[4]; + break; + case WhiteDrop: + case BlackDrop: + fromX = next == WhiteDrop ? + (int) CharToPiece(ToUpper(currentMoveString[0])) : + (int) CharToPiece(ToLower(currentMoveString[0])); + fromY = DROP_RANK; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; + break; + } + // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move + if(plyNr == 0) { // but first figure out variant and initial position + if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant + if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1; + if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1; + if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1; + if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++; + if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr; + } + CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]); + plyNr++; + ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]); + if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr; + } +} /* Load the nth game from open file f */ int @@ -10998,7 +11184,7 @@ LoadGame(f, gameNumber, title, useList) int gn = gameNumber; ListGame *lg = NULL; int numPGNTags = 0; - int err; + int err, pos = -1; GameMode oldGameMode; VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ @@ -11024,6 +11210,7 @@ LoadGame(f, gameNumber, title, useList) if (lg) { fseek(f, lg->offset, 0); GameListHighlight(gameNumber); + pos = lg->position; gn = 1; } else { @@ -11423,10 +11610,11 @@ LoadGame(f, gameNumber, title, useList) AnalyzeFileEvent(); } + if (!matchMode && pos >= 0) { + ToNrEvent(pos); // [HGM] no autoplay if selected on position + } else if (matchMode || appData.timeDelay == 0) { ToEndEvent(); - gameMode = EditGame; - ModeHighlight(); } else if (appData.timeDelay > 0) { AutoPlayGameLoop(); } @@ -13542,7 +13730,9 @@ ClockClick(int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetBlackToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack) { @@ -13552,7 +13742,9 @@ ClockClick(int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetWhiteToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingBlack || gameMode == MachinePlaysWhite) { @@ -13829,6 +14021,16 @@ BackwardInner(target) if (gameMode == EditGame || gameMode==AnalyzeMode || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { while (currentMove > target) { + if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') { + // null move cannot be undone. Reload program with move history before it. + int i; + for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move + if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; + } + SendBoard(&first, i); + for(currentMove=i; currentMove= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { - if(text == NULL) text = ""; - score = pvInfoList[moveNumber].score; - snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., - depth, (pvInfoList[moveNumber].time+50)/100, text); - text = buf; - } if (text != NULL && (appData.autoDisplayComment || commentUp)) CommentPopUp(title, text); } @@ -15817,7 +16009,7 @@ PositionToFEN(move, overrideCastling) { int i, j, fromX, fromY, toX, toY; int whiteToPlay; - char buf[128]; + char buf[MSG_SIZ]; char *p, *q; int emptycount; ChessSquare piece; @@ -15828,6 +16020,7 @@ PositionToFEN(move, overrideCastling) /* Piece placement data */ for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); } emptycount = 0; for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { if (boards[move][i][j] == EmptySquare) { @@ -16352,6 +16545,7 @@ int wrap(char *dest, char *src, int count, int width, int *lp) } // [HGM] vari: routines for shelving variations +Boolean modeRestore = FALSE; void PushInner(int firstMove, int lastMove) @@ -16395,6 +16589,7 @@ PushTail(int firstMove, int lastMove) PushInner(firstMove, lastMove); if(storedGames == 1) GreyRevert(FALSE); + if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE; } void @@ -16403,8 +16598,8 @@ PopInner(Boolean annotate) int i, j, nrMoves; char buf[8000], moveBuf[20]; - storedGames--; - ToNrEvent(savedFirst[storedGames]); // sets currentMove + ToNrEvent(savedFirst[storedGames-1]); // sets currentMove + storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns nrMoves = savedLast[storedGames] - currentMove; if(annotate) { int cnt = 10; @@ -16452,8 +16647,10 @@ PopTail(Boolean annotate) CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open PopInner(annotate); + if(currentMove < forwardMostMove) ForwardEvent(); else + HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); - if(storedGames == 0) GreyRevert(TRUE); + if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; } return TRUE; } @@ -16480,7 +16677,7 @@ LoadVariation(int index, char *text) char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR; int level = 0, move; - if(gameMode != EditGame && gameMode != AnalyzeMode) return; + if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return; // first find outermost bracketing variation while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting () if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}