X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=b914fe40c69d0376c834685e219d3015804cf488;hb=f2344ce98b5950c7a047c3ee29959ad9f26ae8ae;hp=da18261941b547d0e0f8557dc9ed30307faddfdb;hpb=5a0607f180d86de11f7aa5764d02453687266d87;p=xboard.git diff --git a/backend.c b/backend.c index da18261..b914fe4 100644 --- a/backend.c +++ b/backend.c @@ -233,6 +233,8 @@ void InitDrawingSizes(int x, int y); void NextMatchGame P((void)); int NextTourneyGame P((int nr, int *swap)); int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync)); +FILE *WriteTourneyFile P((char *results)); +void DisplayTwoMachinesTitle P(()); #ifdef WIN32 extern void ConsoleCreate(); @@ -394,6 +396,7 @@ PosFlags(index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -417,7 +420,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ*10]; -ChessProgramState first, second; +ChessProgramState first, second, pairing; /* premove variables */ int premoveToX = 0; @@ -492,7 +495,7 @@ int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) int loadFlag = 0; -int shuffleOpenings; +Boolean shuffleOpenings; int mute; // mute all sounds // [HGM] vari: next 12 to save and restore variations @@ -597,6 +600,13 @@ ChessSquare JanusArray[2][BOARD_FILES] = { BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook } }; +ChessSquare GrandArray[2][BOARD_FILES] = { + { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, + WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare }, + { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing, + BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare } +}; + #ifdef GOTHIC ChessSquare GothicArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, @@ -860,6 +870,7 @@ ReplaceEngine(ChessProgramState *cps, int n) appData.noChessProgram = FALSE; appData.clockMode = TRUE; InitEngine(cps, n); + UpdateLogos(TRUE); if(n) return; // only startup first engine immediately; second can wait savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-( LoadEngine(); @@ -875,7 +886,7 @@ static char resetOptions[] = void Load(ChessProgramState *cps, int i) { - char *p, *q, buf[MSG_SIZ], command[MSG_SIZ]; + char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ]; if(engineLine[0]) { // an engine was selected from the combo box snprintf(buf, MSG_SIZ, "-fcp %s", engineLine); SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second* @@ -895,6 +906,7 @@ Load(ChessProgramState *cps, int i) appData.directory[i] = strdup(engineName); p[-1] = SLASH; } else appData.directory[i] = "."; + if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces if(params[0]) { snprintf(command, MSG_SIZ, "%s %s", p, params); p = command; @@ -907,15 +919,18 @@ Load(ChessProgramState *cps, int i) if(useNick) ASSIGN(appData.pgnName[i], nickName); if(addToList) { int len; + char quote; q = firstChessProgramNames; if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR; - snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], + quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n", + quote, p, quote, appData.directory[i], useNick ? " -fn \"" : "", useNick ? nickName : "", useNick ? "\"" : "", v1 ? " -firstProtocolVersion 1" : "", hasBook ? "" : " -fNoOwnBookUCI", - isUCI ? " -fUCI" : "", + isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "", storeVariant ? " -variant " : "", storeVariant ? VariantName(gameInfo.variant) : ""); firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); @@ -1018,6 +1033,13 @@ InitBackEnd1() InitEngine(&second, 1); CommonEngineInit(); + pairing.which = "pairing"; // pairing engine + pairing.pr = NoProc; + pairing.isr = NULL; + pairing.program = appData.pairingEngine; + pairing.host = "localhost"; + pairing.dir = "."; + if (appData.icsActive) { appData.clockMode = TRUE; /* changes dynamically in ICS mode */ } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode @@ -1098,13 +1120,14 @@ InitBackEnd1() case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ case VariantShatranj: /* should work except for all win conditions */ - case VariantMakruk: /* should work except for daw countdown */ + case VariantMakruk: /* should work except for draw countdown */ case VariantBerolina: /* might work if TestLegality is off */ case VariantCapaRandom: /* should work */ case VariantJanus: /* should work */ case VariantSuper: /* experimental */ case VariantGreat: /* experimental, requires legality testing to be off */ case VariantSChess: /* S-Chess, should work */ + case VariantGrand: /* should work */ case VariantSpartan: /* should work */ break; } @@ -1368,7 +1391,7 @@ ReserveGame(int gameNr, char resChar) safeStrCpy(q, p, strlen(p) + 2); if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame); - if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done + if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char q[nextGame] = '*'; } @@ -1380,7 +1403,7 @@ ReserveGame(int gameNr, char resChar) fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing DisplayMessage(buf, ""); free(p); appData.results = q; - if(nextGame <= appData.matchGames && resChar != ' ' && + if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch && (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) { UnloadEngine(&first); // next game belongs to other pairing; UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones. @@ -1393,8 +1416,7 @@ MatchEvent(int mode) int dummy; if(matchMode) { // already in match mode: switch it off abortMatch = TRUE; - appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game. - ModeHighlight(); // kludgey way to remove checkmark... + if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game. return; } // if(gameMode != BeginningOfGame) { @@ -1402,14 +1424,29 @@ MatchEvent(int mode) // return; // } abortMatch = FALSE; - appData.matchGames = appData.defaultMatchGames; + if(mode == 2) appData.matchGames = appData.defaultMatchGames; /* Set up machine vs. machine match */ nextGame = 0; - NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it + NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it if(appData.tourneyFile[0]) { ReserveGame(-1, 0); if(nextGame > appData.matchGames) { char buf[MSG_SIZ]; + if(strchr(appData.results, '*') == NULL) { + FILE *f; + appData.tourneyCycles++; + if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles + fclose(f); + NextTourneyGame(-1, &dummy); + ReserveGame(-1, 0); + if(nextGame <= appData.matchGames) { + DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec")); + matchMode = mode; + ScheduleDelayedEvent(NextMatchGame, 10000); + return; + } + } + } snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile); DisplayError(buf, 0); appData.tourneyFile[0] = 0; @@ -1423,7 +1460,7 @@ MatchEvent(int mode) } matchMode = mode; matchGame = roundNr = 1; - first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches + first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches NextMatchGame(); } @@ -1524,6 +1561,8 @@ InitBackEnd3 P((void)) if(f = fopen(appData.tourneyFile, "r")) { ParseArgsFromFile(f); // make sure tourney parmeters re known fclose(f); + appData.clockMode = TRUE; + SetGNUMode(); } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file } MatchEvent(TRUE); @@ -4611,8 +4650,8 @@ ParseBoard12(string) safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0])); strcat(moveList[moveNum - 1], "\n"); - if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper - && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board + if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat + && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board for(k=0; k 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); + SendToProgram(buf, cps); } else SendToProgram(moveList[moveNum], cps); /* End of additions by Tord */ @@ -5246,6 +5290,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } Boolean pushed = FALSE; +char *lastParseAttempt; void ParsePV(char *pv, Boolean storeComments, Boolean atEnd) @@ -5263,6 +5308,7 @@ ParsePV(char *pv, Boolean storeComments, Boolean atEnd) do { while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace 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(appData.debugMode){ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv); @@ -5304,6 +5350,7 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f fromY, fromX, toY, toX, promoChar, parseList[endPV - 1]); } while(valid); + if(atEnd == 2) return; // used hidden, for PV conversion currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1; if(currentMove == forwardMostMove) ClearPremoveHighlights(); else SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE, @@ -5351,6 +5398,25 @@ LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end) return TRUE; } +char * +PvToSAN(char *pv) +{ + static char buf[10*MSG_SIZ]; + int i, k=0, savedEnd=endPV; + *buf = NULLCHAR; + if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); + ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it + for(i = forwardMostMove; i= BOARD_RGHT || overrule) continue; - initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[gameInfo.variant == VariantGrand][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) { @@ -5837,7 +5911,13 @@ InitPosition(redraw) } } } - initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth]; + if(gameInfo.variant == VariantGrand) { + if(j==BOARD_LEFT || j>=BOARD_RGHT-1) { + initialPosition[0][j] = WhiteRook; + initialPosition[BOARD_HEIGHT-1][j] = BlackRook; + } + } + initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth]; } if( (gameInfo.variant == VariantShogi) && !overrule ) { @@ -6016,7 +6096,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) if(gameInfo.variant == VariantShogi) { promotionZoneSize = BOARD_HEIGHT/3; highestPromotingPiece = (int)WhiteFerz; - } else if(gameInfo.variant == VariantMakruk) { + } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) { promotionZoneSize = 3; } @@ -6462,8 +6542,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) { char *bookHit = 0; - if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { - // [HGM] superchess: suppress promotions to non-available piece + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) { + // [HGM] superchess: suppress promotions to non-available piece (but P always allowed) int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); if(WhiteOnMove(currentMove)) { if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0; @@ -6666,7 +6746,7 @@ MarkTargetSquares(int clear) for(x=0; x1) capt++; if(capt) @@ -6745,13 +6825,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y); if(gameInfo.holdingsWidth && (WhiteOnMove(currentMove) - ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0 - : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) { + ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0 + : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) { // click in right holdings, for determining promotion piece ChessSquare p = boards[currentMove][y][x]; if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p); - if(p != EmptySquare) { - FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p))); + if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral + if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer + FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p))); fromX = fromY = -1; return; } @@ -6960,7 +7041,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { SetHighlights(fromX, fromY, toX, toY); - if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { // [HGM] super: promotion to captured piece selected from holdings ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX]; promotionChoice = TRUE; @@ -7030,16 +7111,16 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) if (action != Press) return -2; // return code to be ignored switch (gameMode) { case IcsExamining: - if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; + if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; case EditPosition: - if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; + 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 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep 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; case IcsObserving: if(!appData.icsEngineAnalyze) return -1; case IcsPlayingWhite: @@ -7107,6 +7188,15 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp SetProgramStats( &stats ); } +void +ClearEngineOutputPane(int which) +{ + static FrontEndProgramStats dummyStats; + dummyStats.which = which; + dummyStats.pv = "#"; + SetProgramStats( &dummyStats ); +} + #define MAXPLAYERS 500 char * @@ -7116,6 +7206,8 @@ TourneyStandings(int display) int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS]; char result, *p, *names[MAXPLAYERS]; + if(appData.tourneyType < 0 && !strchr(appData.results, '*')) + return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO names[0] = p = strdup(appData.participants); while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants @@ -7614,6 +7706,8 @@ void DeferredBookMove(void) HandleMachineMove(savedMessage, savedState); } +static int savedWhitePlayer, savedBlackPlayer, pairingReceived; + void HandleMachineMove(message, cps) char *message; @@ -7624,10 +7718,21 @@ HandleMachineMove(message, cps) int fromX, fromY, toX, toY; ChessMove moveType; char promoChar; - char *p; + char *p, *pv=buf1; int machineWhite; char *bookHit; + if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) { + // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands). + if(savedWhitePlayer == 0 || savedBlackPlayer == 0) { + DisplayError(_("Invalid pairing from pairing engine"), 0); + return; + } + pairingReceived = 1; + NextMatchGame(); + return; // Skim the pairing messages here. + } + cps->userError = 0; FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit @@ -7984,6 +8089,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (!strncmp(message, "telluser ", 9)) { if(message[9] == '\\' && message[10] == '\\') EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box + PlayTellSound(); DisplayNote(message + 9); return; } @@ -7991,6 +8097,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. cps->userError = 1; if(message[14] == '\\' && message[15] == '\\') EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box + PlayTellSound(); DisplayError(message + 14, 0); return; } @@ -8468,6 +8575,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. curscore = -curscore; } + if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1); tempStats.depth = plylev; tempStats.nodes = nodes; @@ -8489,15 +8597,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } /* Buffer overflow protection */ - if (buf1[0] != NULLCHAR) { - if (strlen(buf1) >= sizeof(tempStats.movelist) + if (pv[0] != NULLCHAR) { + if (strlen(pv) >= sizeof(tempStats.movelist) && appData.debugMode) { fprintf(debugFP, "PV is too long; using the first %u bytes.\n", (unsigned) sizeof(tempStats.movelist) - 1); } - safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) ); + safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) ); } else { sprintf(tempStats.movelist, " no PV\n"); } @@ -8534,14 +8642,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if( buf1[0] != NULLCHAR ) { unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1; - if( strlen(buf1) > max_len ) { + if( strlen(pv) > max_len ) { if( appData.debugMode) { fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n"); } - buf1[max_len+1] = '\0'; + pv[max_len+1] = '\0'; } - strcat( thinkOutput, buf1 ); + strcat( thinkOutput, pv); } if (currentMove == forwardMostMove || gameMode == AnalyzeMode @@ -8872,7 +8980,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) Board board; { ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0; - int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1; + int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 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 */ @@ -8970,18 +9078,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][BOARD_LEFT] = EmptySquare; } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) - && toY >= BOARD_HEIGHT-promoRank + && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); - if (board[toY][toX] == EmptySquare) { - board[toY][toX] = WhiteQueen; - } if(gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; - } else if ((fromY == BOARD_HEIGHT-4) + } else if ((fromY >= BOARD_HEIGHT>>1) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9034,18 +9139,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][2] = BlackRook; } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) - && toY < promoRank + && toY < promoRank && promoChar ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); - if (board[toY][toX] == EmptySquare) { - board[toY][toX] = BlackQueen; - } if(gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; - } else if ((fromY == 3) + } else if ((fromY < BOARD_HEIGHT>>1) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9106,7 +9208,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) if (captured != EmptySquare && gameInfo.holdingsSize > 0 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) { /* [HGM] holdings: Add to holdings, if holdings exist */ - if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured; } @@ -9157,8 +9259,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } 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) { + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) + && promoChar != NULLCHAR && gameInfo.holdingsSize) { // [HGM] superchess: take promotion piece out of holdings int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); if((int)piece < (int)BlackPawn) { // determine stm from piece color @@ -9386,6 +9488,8 @@ InitChessProgram(cps, setup) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; if( gameInfo.variant == VariantSChess ) overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7; + if( gameInfo.variant == VariantGrand ) + overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7; if(overruled) { snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, @@ -9447,6 +9551,7 @@ InitChessProgram(cps, setup) SendToProgram(buf, cps); } cps->initDone = TRUE; + ClearEngineOutputPane(cps == &second); } @@ -9477,9 +9582,14 @@ StartChessProgram(cps) if (err != 0) { snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program); - DisplayFatalError(buf, err, 1); - cps->pr = NoProc; - cps->isr = NULL; + DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it + if(cps != &first) return; + appData.noChessProgram = TRUE; + ThawUI(); + SetNCPMode(); +// DisplayFatalError(buf, err, 1); +// cps->pr = NoProc; +// cps->isr = NULL; return; } @@ -9513,39 +9623,95 @@ TwoMachinesEventIfReady P((void)) TwoMachinesEvent(); } +char * +MakeName(char *template) +{ + time_t clock; + struct tm *tm; + static char buf[MSG_SIZ]; + char *p = buf; + int i; + + clock = time((time_t *)NULL); + tm = localtime(&clock); + + while(*p++ = *template++) if(p[-1] == '%') { + switch(*template++) { + case 0: *p = 0; return buf; + case 'Y': i = tm->tm_year+1900; break; + case 'y': i = tm->tm_year-100; break; + case 'M': i = tm->tm_mon+1; break; + case 'd': i = tm->tm_mday; break; + case 'h': i = tm->tm_hour; break; + case 'm': i = tm->tm_min; break; + case 's': i = tm->tm_sec; break; + default: i = 0; + } + snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p); + } + return buf; +} + +int +CountPlayers(char *p) +{ + int n = 0; + while(p = strchr(p, '\n')) p++, n++; // count participants + return n; +} + +FILE * +WriteTourneyFile(char *results) +{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing + FILE *f = fopen(appData.tourneyFile, "w"); + if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else { + // create a file with tournament description + fprintf(f, "-participants {%s}\n", appData.participants); + fprintf(f, "-tourneyType %d\n", appData.tourneyType); + fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles); + fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames); + fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false"); + fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false"); + fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile); + fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile); + fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex); + fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); + fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); + fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); + if(searchTime > 0) + fprintf(f, "-searchTime \"%s\"\n", appData.searchTime); + else { + fprintf(f, "-mps %d\n", appData.movesPerSession); + fprintf(f, "-tc %s\n", appData.timeControl); + fprintf(f, "-inc %.2f\n", appData.timeIncrement); + } + fprintf(f, "-results \"%s\"\n", results); + } + return f; +} + int CreateTourney(char *name) { FILE *f; - if(name[0] == NULLCHAR) return 0; - f = fopen(appData.tourneyFile, "r"); + if(name[0] == NULLCHAR) { + if(appData.participants[0]) + DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0); + return 0; + } + f = fopen(name, "r"); if(f) { // file exists + ASSIGN(appData.tourneyFile, name); ParseArgsFromFile(f); // parse it } else { - f = fopen(appData.tourneyFile, "w"); - if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else { - // create a file with tournament description - fprintf(f, "-participants {%s}\n", appData.participants); - fprintf(f, "-tourneyType %d\n", appData.tourneyType); - fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles); - fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames); - fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false"); - fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false"); - fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile); - fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile); - fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex); - fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); - fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); - fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); - if(searchTime > 0) - fprintf(f, "-searchTime \"%s\"\n", appData.searchTime); - else { - fprintf(f, "-mps %d\n", appData.movesPerSession); - fprintf(f, "-tc %s\n", appData.timeControl); - fprintf(f, "-inc %.2f\n", appData.timeIncrement); - } - fprintf(f, "-results \"\"\n"); + if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants + if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) { + DisplayError(_("Not enough participants"), 0); + return 0; } + ASSIGN(appData.tourneyFile, name); + if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1 + if((f = WriteTourneyFile("")) == NULL) return 0; } fclose(f); appData.noChessProgram = FALSE; @@ -9600,6 +9766,7 @@ void SwapEngines(int n) SWAP(timeOdds, h) SWAP(logo, p) SWAP(pgnName, p) + SWAP(pvSAN, h) } void @@ -9621,7 +9788,7 @@ SetPlayer(int player) int Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval) { // determine players from game number - int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound; + int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1; if(appData.tourneyType == 0) { roundsPerCycle = (nPlayers - 1) | 1; @@ -9649,9 +9816,9 @@ Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInter *whitePlayer = curRound; *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd } else { - *whitePlayer = curRound - pairingsPerRound + curPairing; + *whitePlayer = curRound - (nPlayers-1)/2 + curPairing; if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1); - *blackPlayer = curRound + pairingsPerRound - curPairing; + *blackPlayer = curRound + (nPlayers-1)/2 - curPairing; if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1); } } else if(appData.tourneyType > 0) { @@ -9668,7 +9835,7 @@ int NextTourneyGame(int nr, int *swapColors) { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game char *p, *q; - int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0; + int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers; FILE *tf; if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game tf = fopen(appData.tourneyFile, "r"); @@ -9676,9 +9843,9 @@ NextTourneyGame(int nr, int *swapColors) ParseArgsFromFile(tf); fclose(tf); InitTimeControls(); // TC might be altered from tourney file - p = appData.participants; - while(p = strchr(p, '\n')) p++, nPlayers++; // count participants - *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval); + nPlayers = CountPlayers(appData.participants); // count participants + if(appData.tourneyType < 0) syncInterval = nPlayers/2; else + *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval); if(syncInterval) { p = q = appData.results; @@ -9692,6 +9859,29 @@ NextTourneyGame(int nr, int *swapColors) waitingForGame = FALSE; } + if(appData.tourneyType < 0) { + if(nr>=0 && !pairingReceived) { + char buf[1<<16]; + if(pairing.pr == NoProc) { + if(!appData.pairingEngine[0]) { + DisplayFatalError(_("No pairing engine specified"), 0, 1); + return 0; + } + StartChessProgram(&pairing); // starts the pairing engine + } + snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results); + SendToProgram(buf, &pairing); + snprintf(buf, 1<<16, "pairing %d\n", nr+1); + SendToProgram(buf, &pairing); + return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again... + } + pairingReceived = 0; // ... so we continue here + *swapColors = 0; + appData.matchGames = appData.tourneyCycles * syncInterval - 1; + whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1; + matchGame = 1; roundNr = nr / syncInterval + 1; + } + if(first.pr != NoProc) return 1; // engines already loaded // redefine engines, engine dir, etc. @@ -9703,6 +9893,7 @@ NextTourneyGame(int nr, int *swapColors) InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. InitEngine(&second, 1); CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes + UpdateLogos(FALSE); // leave display to ModeHiglight() return 1; } @@ -9852,7 +10043,7 @@ GameEnds(result, resultDetails, whosays) // now verify win claims, but not in drop games, as we don't understand those yet if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper - || gameInfo.variant == VariantGreat) && + || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && (result == WhiteWins && claimer == 'w' || result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win if (appData.debugMode) { @@ -9878,7 +10069,8 @@ GameEnds(result, resultDetails, whosays) /* (Claiming a loss is accepted no questions asked!) */ } /* [HGM] bare: don't allow bare King to win */ - if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) + if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper + || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course... && result != GameIsDrawn) @@ -10097,9 +10289,10 @@ GameEnds(result, resultDetails, whosays) } if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game - if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result + if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result ReserveGame(nextGame, resChar); // sets nextGame if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done + else ranking = strdup("busy"); //suppress popup when aborted but not finished } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame if (nextGame <= appData.matchGames && !abortMatch) { @@ -10115,6 +10308,7 @@ GameEnds(result, resultDetails, whosays) first.tidy, second.tidy, first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); + if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title popupRequested++; // [HGM] crash: postpone to after resetting endingGame if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match first.twoMachinesColor = "black\n"; @@ -10134,11 +10328,13 @@ GameEnds(result, resultDetails, whosays) if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion. if(matchMode == TRUE) { // match through command line: exit with or without popup if(ranking) { + ToNrEvent(forwardMostMove); if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0); else ExitEvent(0); } else DisplayFatalError(buf, 0, 0); } else { // match through menu; just stop, with or without popup matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; + ModeHighlight(); if(ranking){ if(strcmp(ranking, "busy")) DisplayNote(ranking); } else DisplayNote(buf); @@ -12267,6 +12463,9 @@ ExitEvent(status) RemoveInputSource(second.isr); } + if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing); + if (pairing.isr != NULL) RemoveInputSource(pairing.isr); + ShutDownFrontEnd(); exit(status); } @@ -12578,6 +12777,12 @@ DisplayTwoMachinesTitle() { char buf[MSG_SIZ]; if (appData.matchGames > 0) { + if(appData.tourneyFile[0]) { + snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)", + gameInfo.white, gameInfo.black, + nextGame+1, appData.matchGames+1, + appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); + } else if (first.twoMachinesColor[0] == 'w') { snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)", gameInfo.white, gameInfo.black, @@ -12708,7 +12913,7 @@ TwoMachinesEvent P((void)) gameMode = TwoMachinesPlay; pausing = FALSE; - ModeHighlight(); + ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); firstMove = TRUE; @@ -14328,6 +14533,7 @@ ReceiveFromProgram(isr, closure, message, count, error) if (count <= 0) { if (count == 0) { RemoveInputSource(cps->isr); + if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"), _(cps->which), cps->program); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ @@ -14879,14 +15085,14 @@ AskQuestionEvent(title, question, replyPrefix, which) void TypeInEvent(char firstChar) { - if ((gameMode == BeginningOfGame && !appData.icsActive) || - gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || - gameMode == AnalyzeMode || gameMode == EditGame || - gameMode == EditPosition || gameMode == IcsExamining || - gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || - isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes - ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile || - gameMode == IcsObserving || gameMode == TwoMachinesPlay ) || + if ((gameMode == BeginningOfGame && !appData.icsActive) || + gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || + gameMode == AnalyzeMode || gameMode == EditGame || + gameMode == EditPosition || gameMode == IcsExamining || + gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || + isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes + ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile || + gameMode == IcsObserving || gameMode == TwoMachinesPlay ) || gameMode == Training) PopUpMoveDialog(firstChar); } @@ -14896,34 +15102,34 @@ TypeInDoneEvent(char *move) Board board; int n, fromX, fromY, toX, toY; char promoChar; - ChessMove moveType; - - // [HGM] FENedit - if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { - EditPositionPasteFEN(move); - return; - } - // [HGM] movenum: allow move number to be typed in any mode - if(sscanf(move, "%d", &n) == 1 && n != 0 ) { - ToNrEvent(2*n-1); - return; - } - - if (gameMode != EditGame && currentMove != forwardMostMove && - gameMode != Training) { - DisplayMoveError(_("Displayed move is not current")); - } else { - int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, - &moveType, &fromX, &fromY, &toX, &toY, &promoChar); - if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized - if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, - &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { - UserMoveEvent(fromX, fromY, toX, toY, promoChar); - } else { - DisplayMoveError(_("Could not parse move")); - } - } -} + ChessMove moveType; + + // [HGM] FENedit + if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { + EditPositionPasteFEN(move); + return; + } + // [HGM] movenum: allow move number to be typed in any mode + if(sscanf(move, "%d", &n) == 1 && n != 0 ) { + ToNrEvent(2*n-1); + return; + } + + if (gameMode != EditGame && currentMove != forwardMostMove && + gameMode != Training) { + DisplayMoveError(_("Displayed move is not current")); + } else { + int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + &moveType, &fromX, &fromY, &toX, &toY, &promoChar); + if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized + if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { + UserMoveEvent(fromX, fromY, toX, toY, promoChar); + } else { + DisplayMoveError(_("Could not parse move")); + } + } +} void DisplayMove(moveNumber)