X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=0026207b752bd3b74c727b860c88da1656de28d7;hb=4a538e46ba98f9cca1d9c156347e5bfc3952b160;hp=6bd4418d5daed9d96f238e5d1d1e7361916b6c02;hpb=4d6bd700a90b76e2b34fbfba71e6f0fb61c556ae;p=xboard.git diff --git a/backend.c b/backend.c index 6bd4418..0026207 100644 --- a/backend.c +++ b/backend.c @@ -311,23 +311,19 @@ char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ char* safeStrCpy( char *dst, const char *src, size_t count ) -{ - /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html - * - * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]); - */ - +{ // [HGM] made safe + int i; assert( dst != NULL ); assert( src != NULL ); assert( count > 0 ); - strncpy( dst, src, count ); - if( dst[ count-1 ] != '\0' ) + for(i=0; i= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1) @@ -4377,7 +4373,7 @@ ParseBoard12(string) strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); /* currentMoveString is set as a side-effect of ParseOneMove */ - if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '+'; + if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^'; safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0])); strcat(moveList[moveNum - 1], "\n"); @@ -4884,9 +4880,6 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) int *fromX, *fromY, *toX, *toY; char *promoChar; { - if (appData.debugMode) { - fprintf(debugFP, "move to parse: %s\n", move); - } *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr); switch (*moveType) { @@ -5598,7 +5591,8 @@ SendBoard(cps, moveNum) /* Kludge to set black to move, avoiding the troublesome and now * deprecated "black" command. */ - if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps); + if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move... + SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps); SendToProgram("edit\n", cps); SendToProgram("#\n", cps); @@ -5721,6 +5715,11 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *promoChoice = PieceToChar(BlackFerz); // no choice return FALSE; } + // no sense asking what we must promote to if it is going to explode... + if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) { + *promoChoice = PieceToChar(BlackQueen); // Queen as good as any + return FALSE; + } if(autoQueen) { // predetermined if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers) *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want @@ -5733,7 +5732,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) gameMode == IcsPlayingBlack && WhiteOnMove(currentMove); if(appData.testLegality && !premove) { moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - fromY, fromX, toY, toX, NULLCHAR); + fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR); if(moveType != WhitePromotion && moveType != BlackPromotion) return FALSE; } @@ -5875,6 +5874,8 @@ OnlyMove(int *x, int *y, Boolean captures) { case IcsPlayingBlack: if(WhiteOnMove(currentMove)) return FALSE; break; + case EditGame: + break; default: return FALSE; } @@ -6161,7 +6162,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) the previous line in Analysis Mode */ if ((gameMode == AnalyzeMode || gameMode == EditGame) && currentMove < forwardMostMove) { - PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game + if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game + else forwardMostMove = currentMove; } /* If we need the chess program but it's dead, restart it */ @@ -6311,6 +6313,20 @@ MarkTargetSquares(int clear) DrawPosition(TRUE, NULL); } +int +Explode(Board board, int fromX, int fromY, int toX, int toY) +{ + if(gameInfo.variant == VariantAtomic && + (board[toY][toX] != EmptySquare || // capture? + toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ? + board[fromY][fromX] == BlackPawn ) + )) { + AnimateAtomicCapture(board, fromX, fromY, toX, toY); + return TRUE; + } + return FALSE; +} + void LeftClick(ClickType clickType, int xPix, int yPix) { int x, y; @@ -6423,8 +6439,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix) MarkTargetSquares(0); DragPieceBegin(xPix, yPix); } - return; } + if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on + second = FALSE; } // ignore clicks on holdings if(x < BOARD_LEFT || x >= BOARD_RGHT) return; @@ -6502,7 +6519,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } // off-board moves should not be highlighted - if(x < 0 || x < 0) ClearHighlights(); + if(x < 0 || y < 0) ClearHighlights(); if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { SetHighlights(fromX, fromY, toX, toY); @@ -6521,9 +6538,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } PromotionPopUp(); } else { + int oldMove = currentMove; UserMoveEvent(fromX, fromY, toX, toY, promoChoice); if (!appData.highlightLastMove || gotPremove) ClearHighlights(); if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY); + if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed + Explode(boards[currentMove-1], fromX, fromY, toX, toY)) + DrawPosition(TRUE, boards[currentMove]); fromX = fromY = -1; } appData.animate = saveAnimate; @@ -7433,6 +7454,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands! } + if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position + int dummy, s=6; char buf[MSG_SIZ]; + if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return; + if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); + ParseFEN(boards[0], &dummy, message+s); + DrawPosition(TRUE, boards[0]); + startedFromSetupPosition = TRUE; + 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. */ @@ -7459,13 +7489,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. * Look for communication commands */ if (!strncmp(message, "telluser ", 9)) { - EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box + if(message[9] == '\\' && message[10] == '\\') + EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box DisplayNote(message + 9); return; } if (!strncmp(message, "tellusererror ", 14)) { cps->userError = 1; - EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box + if(message[14] == '\\' && message[15] == '\\') + EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box DisplayError(message + 14, 0); return; } @@ -8189,7 +8221,7 @@ ParseGameHistory(game) yynewstr(game); for (;;) { yyboardindex = boardIndex; - moveType = (ChessMove) yylex(); + moveType = (ChessMove) Myylex(); switch (moveType) { case IllegalMove: /* maybe suicide chess, etc. */ if (appData.debugMode) { @@ -8355,10 +8387,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) if( board[toY][toX] != EmptySquare ) board[EP_STATUS] = EP_CAPTURE; - /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk) - && promoChar != 0) promoChar = PieceToChar(WhiteFerz); - if (fromY == DROP_RANK) { /* must be first */ piece = board[toY][toX] = (ChessSquare) fromX; @@ -8621,8 +8649,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) if(promoChar == '+') { /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */ board[toY][toX] = (ChessSquare) (PROMOTED piece); - } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar - board[toY][toX] = CharToPiece(promoChar); + } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar + board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); } if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR && gameInfo.holdingsSize) { @@ -9642,7 +9670,7 @@ LoadGameOneMove(readAhead) } else { if (gameFileFP == NULL) return FALSE; - moveType = (ChessMove) yylex(); + moveType = (ChessMove) Myylex(); } done = FALSE; @@ -10137,7 +10165,7 @@ LoadGame(f, gameNumber, title, useList) cm = lastLoadGameStart = EndOfFile; while (gn > 0) { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); switch (cm) { case EndOfFile: if (cmailMsgLoaded) { @@ -10190,7 +10218,7 @@ LoadGame(f, gameNumber, title, useList) if (gn > 0) { do { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } while (cm == PGNTag || cm == Comment); } break; @@ -10229,7 +10257,7 @@ LoadGame(f, gameNumber, title, useList) /* Skip any header junk before position diagram and/or move 1 */ for (;;) { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); if (cm == EndOfFile || cm == GNUChessGame || cm == XBoardGame) { @@ -10302,7 +10330,7 @@ LoadGame(f, gameNumber, title, useList) } yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); /* Handle comments interspersed among the tags */ while (cm == Comment) { @@ -10312,7 +10340,7 @@ LoadGame(f, gameNumber, title, useList) p = yy_text; AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } } @@ -10389,7 +10417,7 @@ LoadGame(f, gameNumber, title, useList) } } yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } if (first.pr == NoProc) { @@ -10415,7 +10443,7 @@ LoadGame(f, gameNumber, title, useList) p = yy_text; AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) || @@ -11824,6 +11852,37 @@ DisplayTwoMachinesTitle() } void +SettingsMenuIfReady() +{ + if (second.lastPing != second.lastPong) { + DisplayMessage("", _("Waiting for second chess program")); + ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000 + return; + } + ThawUI(); + DisplayMessage("", ""); + SettingsPopUp(&second); +} + +int +WaitForSecond(DelayedEventCallback retry) +{ + if (second.pr == NULL) { + StartChessProgram(&second); + if (second.protocolVersion == 1) { + retry(); + } else { + /* kludge: allow timeout for initial "feature" command */ + FreezeUI(); + DisplayMessage("", _("Starting second chess program")); + ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); + } + return 1; + } + return 0; +} + +void TwoMachinesEvent P((void)) { int i; @@ -11865,21 +11924,14 @@ TwoMachinesEvent P((void)) TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... ResurrectChessProgram(); /* in case first program isn't running */ - if (second.pr == NULL) { - StartChessProgram(&second); - if (second.protocolVersion == 1) { - TwoMachinesEventIfReady(); - } else { - /* kludge: allow timeout for initial "feature" command */ - FreezeUI(); - DisplayMessage("", _("Starting second chess program")); - ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT); - } - return; - } + if(WaitForSecond(TwoMachinesEventIfReady)) return; DisplayMessage("", ""); InitChessProgram(&second, FALSE); SendToProgram("force\n", &second); + if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position + ScheduleDelayedEvent(TwoMachinesEvent, 10); + return; + } if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); if (appData.debugMode) { @@ -13305,7 +13357,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); } else { commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4... if(addBraces) - safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0])); + safeStrCpy(commentList[index], "{\n", 3); else commentList[index][0] = NULLCHAR; strcat(commentList[index], text); strcat(commentList[index], "\n"); @@ -13509,6 +13561,8 @@ ReceiveFromProgram(isr, closure, message, count, error) sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 && sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && + sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 && + sscanf(message, "hint: %c", &c)!=1 && sscanf(message, "pong %c", &c)!=1 && start != '#') { quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### "; print = (appData.engineComments >= 2); @@ -13574,10 +13628,10 @@ SendTimeControl(cps, mps, tc, inc, sd, st) /* Note old gnuchess bug -- minutes:seconds used to not work. Fixed in later versions, but still avoid :seconds when seconds is 0. */ - snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000); + snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.); } else { snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000, - seconds, inc/1000); + seconds, inc/1000.); } } SendToProgram(buf, cps); @@ -13784,6 +13838,20 @@ ParseOption(Option *opt, ChessProgramState *cps) if(p && (p == cps->optionSettings || p[-1] == ',')) { snprintf(buf, MSG_SIZ, "option %s", p); if(p = strstr(buf, ",")) *p = 0; + if(q = strchr(buf, '=')) switch(opt->type) { + case ComboBox: + for(n=0; nmax; n++) + if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n; + break; + case TextBox: + safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name)); + break; + case Spin: + case CheckBox: + opt->value = atoi(q+1); + default: + break; + } strcat(buf, "\n"); SendToProgram(buf, cps); } @@ -13797,6 +13865,7 @@ FeatureDone(cps, val) { DelayedEventCallback cb = GetDelayedEvent(); if ((cb == InitBackEnd3 && cps == &first) || + (cb == SettingsMenuIfReady && cps == &second) || (cb == TwoMachinesEventIfReady && cps == &second)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000);