profile
[xboard.git] / backend.c
index 9b81943..286ec32 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -465,6 +465,7 @@ int have_sent_ICS_logon = 0;
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
+Boolean adjustedClock;
 long timeControl_2; /* [AS] Allow separate time controls */
 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
@@ -618,10 +619,10 @@ ChessSquare GothicArray[2][BOARD_FILES] = {
 
 #ifdef FALCON
 ChessSquare FalconArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
-        WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
-        BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
+        WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
+        BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
 };
 #else // !FALCON
 #define FalconArray CapablancaArray
@@ -879,6 +880,7 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 
 static char resetOptions[] = 
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+       "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
        "-firstOptions \"\" -firstNPS -1 -fn \"\"";
 
 void
@@ -1665,6 +1667,8 @@ InitBackEnd3 P((void))
 void
 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
 {
+    DisplayBook(current+1);
+
     MoveHistorySet( movelist, first, last, current, pvInfoList );
 
     EvalGraphSet( first, last, current, pvInfoList );
@@ -1864,7 +1868,7 @@ SendToICS(s)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
@@ -1883,7 +1887,7 @@ SendToICSDelayed(s,msdelay)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     if (appData.debugMode) {
@@ -3896,7 +3900,7 @@ read_from_ics(isr, closure, data, count, error)
 #if ZIPPY
                if (appData.zippyPlay && first.initDone) {
                    ZippyGameEnd(endtype, why);
-                   if (first.pr == NULL) {
+                   if (first.pr == NoProc) {
                      /* Start the next process early so that we'll
                         be ready for the next challenge */
                      StartChessProgram(&first);
@@ -5422,7 +5426,7 @@ char *
 PvToSAN(char *pv)
 {
        static char buf[10*MSG_SIZ];
-       int i, k=0, savedEnd=endPV;
+       int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
        *buf = NULLCHAR;
        if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
        ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
@@ -5432,7 +5436,7 @@ PvToSAN(char *pv)
            k += strlen(buf+k);
        }
        snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
-       if(forwardMostMove < savedEnd) PopInner(0);
+       if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
        endPV = savedEnd;
        return buf;
 }
@@ -9435,6 +9439,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+    adjustedClock = FALSE;
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -9488,7 +9493,6 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
-    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9770,6 +9774,7 @@ WriteTourneyFile(char *results, FILE *f)
        fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
        fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
        fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
        if(searchTime > 0)
                fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
        else {
@@ -9932,6 +9937,7 @@ SetPlayer(int player)
     if(mnemonic[i]) {
        snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
        ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
+       appData.firstHasOwnBookUCI = !appData.defNoBook;
        ParseArgsFromString(buf);
     }
     free(engineName);
@@ -10034,7 +10040,7 @@ NextTourneyGame(int nr, int *swapColors)
        matchGame = 1; roundNr = nr / syncInterval + 1;
     }
 
-    if(first.pr != NoProc) return 1; // engines already loaded
+    if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
 
     // redefine engines, engine dir, etc.
     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
@@ -10642,7 +10648,7 @@ Reset(redraw, init)
     timeRemaining[0][0] = whiteTimeRemaining;
     timeRemaining[1][0] = blackTimeRemaining;
 
-    if (first.pr == NULL) {
+    if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
     if (init) {
@@ -11167,6 +11173,130 @@ PositionMatches(Board b1, Board b2)
     return TRUE;
 }
 
+#define Q_PROMO  4
+#define Q_EP     3
+#define Q_WCASTL 2
+#define Q_BCASTL 1
+
+int pieceList[256], quickBoard[256];
+ChessSquare pieceType[256] = { EmptySquare };
+Board soughtBoard, reverseBoard;
+int counts[EmptySquare], soughtCounts[EmptySquare], reverseCounts[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
+
+typedef struct {
+    unsigned char piece, to;
+} Move;
+
+Move moveDatabase[4000000];
+int movePtr = 0;
+
+void MakePieceList(Board board, int *counts)
+{
+    int r, f, n=Q_PROMO;
+    for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+       int sq = f + (r<<4);
+        if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
+           quickBoard[sq] = ++n;
+           pieceList[n] = sq;
+           pieceType[n] = board[r][f];
+           counts[board[r][f]]++;
+           if(board[r][f] == WhiteKing) pieceList[1] = sq; else
+           if(board[r][f] == BlackKing) pieceList[2] = sq; // remember where Kings start, for castling
+       }
+    }
+}
+
+void PackMove(int fromX, int fromY, int toX, int toY, char promoChar)
+{
+    int sq = fromX + (fromY<<4);
+    int piece = quickBoard[sq];
+    quickBoard[sq] = 0;
+    moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
+    if(promoChar) {
+       
+    }
+    moveDatabase[movePtr].piece = piece;
+    quickBoard[sq] = piece;
+    movePtr++;
+}
+
+int PackGame(Board board)
+{
+    moveDatabase[movePtr++].piece = 0; // terminate previous game
+    MakePieceList(board, counts);
+    return movePtr;
+}
+
+int QuickCompare(Board board, int *counts, int *maxCounts)
+{   // compare according to search mode
+    int r, f;
+    switch(appData.searchMode)
+    {
+      case 1: // exact position match
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       }
+       return TRUE;
+      case 2: // can have extra material on empty squares
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] == EmptySquare) continue;
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       }
+       return TRUE;
+      case 3: // material with exact Pawn structure
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       } // fall through to material comparison
+      case 4: // exact material
+       for(r=0; r<EmptySquare; r++) if(counts[r] != soughtCounts[r]) return FALSE;
+       return TRUE;
+      case 5: // material range with given imbalance
+       for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
+      case 6: // material range
+       for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
+       return TRUE;
+    }
+}
+
+int QuickScan(Board board, Move *move)
+{   // reconstruct game,and compare all positions in it
+    MakePieceList(board, counts);
+    do {
+       int piece = move->piece;
+       int to = move->to, from = pieceList[piece];
+       if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
+         if(!piece) return FALSE;
+         if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
+           piece = (++move)->piece;
+           from = pieceList[piece];
+           counts[pieceType[piece]]--;
+           pieceType[piece] = (ChessSquare) move->to;
+           counts[move->to]++;
+         } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
+           counts[pieceType[quickBoard[to]]]--;
+           quickBoard[to] = 0;
+           move++;
+           continue;
+         } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
+           from = pieceList[piece]; // first two elements of pieceList contain initial King positions
+           piece = quickBoard[from]; // so this must be King
+           quickBoard[from] = 0;
+           quickBoard[to] = piece;
+           pieceList[piece] = to;
+           continue;
+         }
+       }
+       if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
+       quickBoard[from] = 0;
+       quickBoard[to] = piece;
+       pieceList[piece] = to;
+       if(QuickCompare(soughtBoard, soughtCounts, maxSought)) return TRUE;
+       move++;
+    } while(1);
+}
+
 GameInfo dummyInfo;
 
 int GameContainsPosition(FILE *f, ListGame *lg)
@@ -11176,44 +11306,30 @@ int GameContainsPosition(FILE *f, ListGame *lg)
     char promoChar;
     static int initDone=FALSE;
 
+    // weed out games based on numerical tag comparison
+    if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
+    if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
+    if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
+    if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
     if(!initDone) {
        for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
        initDone = TRUE;
     }
-    dummyInfo.variant = VariantNormal;
-    FREE(dummyInfo.fen); dummyInfo.fen = NULL;
-    dummyInfo.whiteRating = 0;
-    dummyInfo.blackRating = 0;
-    FREE(dummyInfo.date); dummyInfo.date = NULL;
+    if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
+    else CopyBoard(boards[scratch], initialPosition); // default start position
+    if(lg->moves && !QuickScan( boards[scratch], &moveDatabase[lg->moves] )) return -1; // quick scan rules out it is there
+    if(btm) plyNr++;
+    if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
     fseek(f, lg->offset, 0);
     yynewfile(f);
-    CopyBoard(boards[scratch], initialPosition); // default start position
     while(1) {
-       yyboardindex = scratch + (plyNr&1);
-      quickFlag = 1;
+       yyboardindex = scratch;
+       quickFlag = plyNr+1;
        next = Myylex();
-      quickFlag = 0;
+       quickFlag = 0;
        switch(next) {
            case PGNTag:
                if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
-#if 0
-               ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
-               if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
-#else
-               // do it ourselves avoiding malloc
-               { char *p = yy_text+1, *q;
-                 while(!isdigit(*p) && !isalpha(*p)) p++;
-                 q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
-                 *p = NULLCHAR;
-                 if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
-                 if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
-                 if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
-                 if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
-                 if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
-                 if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
-                 if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
-               }
-#endif
            default:
                continue;
 
@@ -11268,18 +11384,9 @@ int GameContainsPosition(FILE *f, ListGame *lg)
                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;
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
+       if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
     }
 }
 
@@ -11804,7 +11911,7 @@ LoadPosition(f, positionNumber, title)
     lastLoadPositionFP = f;
     lastLoadPositionNumber = positionNumber;
     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
-    if (first.pr == NoProc) {
+    if (first.pr == NoProc && !appData.noChessProgram) {
       StartChessProgram(&first);
       InitChessProgram(&first, FALSE);
     }
@@ -11876,7 +11983,6 @@ LoadPosition(f, positionNumber, title)
     }
     startedFromSetupPosition = TRUE;
 
-    SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
        currentMove = forwardMostMove = backwardMostMove = 1;
@@ -11889,7 +11995,10 @@ LoadPosition(f, positionNumber, title)
        DisplayMessage("", _("White to play"));
     }
     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
-    SendBoard(&first, forwardMostMove);
+    if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
+       SendToProgram("force\n", &first);
+       SendBoard(&first, forwardMostMove);
+    }
     if (appData.debugMode) {
 int i, j;
   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
@@ -13124,7 +13233,7 @@ int
 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
 {
     char buf[MSG_SIZ];
-    if (cps->pr == NULL) {
+    if (cps->pr == NoProc) {
        StartChessProgram(cps);
        if (cps->protocolVersion == 1) {
          retry();
@@ -13409,9 +13518,11 @@ EditGameEvent()
            SendToProgram("undo\n", &first);
            i--;
        }
+       if(!adjustedClock) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
        DisplayBothClocks();
+       }
        if (whiteFlag || blackFlag) {
            whiteFlag = blackFlag = 0;
        }
@@ -14054,7 +14165,6 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
-    DisplayBook(currentMove);
 }
 
 
@@ -14167,7 +14277,6 @@ BackwardInner(target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
-    DisplayBook(currentMove);
 }
 
 void
@@ -14805,7 +14914,7 @@ SendToProgram(message, cps)
     int count, outCount, error;
     char buf[MSG_SIZ];
 
-    if (cps->pr == NULL) return;
+    if (cps->pr == NoProc) return;
     Attention(cps);
 
     if (appData.debugMode) {
@@ -15735,9 +15844,11 @@ NextTickLength(timeRemaining)
 void
 AdjustClock(Boolean which, int dir)
 {
+    if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
     if(which) blackTimeRemaining += 60000*dir;
     else      whiteTimeRemaining += 60000*dir;
     DisplayBothClocks();
+    adjustedClock = TRUE;
 }
 
 /* Stop clocks and reset to a fresh time control */
@@ -15761,6 +15872,7 @@ ResetClocks()
     }
     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
     DisplayBothClocks();
+    adjustedClock = FALSE;
 }
 
 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */