X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=f6e75c49f0e69aa380491ecdccc2ddb9739b3116;hb=2b59fb6b9575ad038bae89a48063084c8b3243a9;hp=b48ea5f9a9a678fbdd419e479e25f490285e7538;hpb=040cf1c2a55625ab41f0b3b930f56beb8d9fed3f;p=xboard.git diff --git a/backend.c b/backend.c index b48ea5f..f6e75c4 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,7 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -57,6 +57,9 @@ #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); +int flock(int f, int code); +#define LOCK_EX 2 + #else #define DoSleep( n ) if( (n) >= 0) sleep(n) @@ -267,6 +270,9 @@ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ extern int chatCount; int chattingPartner; char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ +ChessSquare pieceSweep = EmptySquare; +ChessSquare promoSweep = EmptySquare, defaultPromoChoice; +int promoDefaultAltered; /* States for ics_getting_history */ #define H_FALSE 0 @@ -322,7 +328,7 @@ safeStrCpy( char *dst, const char *src, size_t count ) { dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated if(appData.debugMode) - fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count); + fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count); } return dst; @@ -656,6 +662,194 @@ ClearProgramStats() } void +CommonEngineInit() +{ // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings + if (appData.firstPlaysBlack) { + first.twoMachinesColor = "black\n"; + second.twoMachinesColor = "white\n"; + } else { + first.twoMachinesColor = "white\n"; + second.twoMachinesColor = "black\n"; + } + + first.other = &second; + second.other = &first; + + { float norm = 1; + if(appData.timeOddsMode) { + norm = appData.timeOdds[0]; + if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1]; + } + first.timeOdds = appData.timeOdds[0]/norm; + second.timeOdds = appData.timeOdds[1]/norm; + } + + if(programVersion) free(programVersion); + if (appData.noChessProgram) { + programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); + sprintf(programVersion, "%s", PACKAGE_STRING); + } else { + /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ + programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); + sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); + } +} + +void +UnloadEngine(ChessProgramState *cps) +{ + /* Kill off first chess program */ + if (cps->isr != NULL) + RemoveInputSource(cps->isr); + cps->isr = NULL; + + if (cps->pr != NoProc) { + ExitAnalyzeMode(); + DoSleep( appData.delayBeforeQuit ); + SendToProgram("quit\n", cps); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(cps->pr, cps->useSigterm); + } + cps->pr = NoProc; +} + +void +ClearOptions(ChessProgramState *cps) +{ + int i; + cps->nrOptions = cps->comboCnt = 0; + for(i=0; ioption[i].min = cps->option[i].max = cps->option[i].value = 0; + cps->option[i].textValue = 0; + } +} + +char *engineNames[] = { +"first", +"second" +}; + +InitEngine(ChessProgramState *cps, int n) +{ // [HGM] all engine initialiation put in a function that does one engine + + ClearOptions(cps); + + cps->which = engineNames[n]; + cps->maybeThinking = FALSE; + cps->pr = NoProc; + cps->isr = NULL; + cps->sendTime = 2; + cps->sendDrawOffers = 1; + + cps->program = appData.chessProgram[n]; + cps->host = appData.host[n]; + cps->dir = appData.directory[n]; + cps->initString = appData.engInitString[n]; + cps->computerString = appData.computerString[n]; + cps->useSigint = TRUE; + cps->useSigterm = TRUE; + cps->reuse = appData.reuse[n]; + cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second + cps->useSetboard = FALSE; + cps->useSAN = FALSE; + cps->usePing = FALSE; + cps->lastPing = 0; + cps->lastPong = 0; + cps->usePlayother = FALSE; + cps->useColors = TRUE; + cps->useUsermove = FALSE; + cps->sendICS = FALSE; + cps->sendName = appData.icsActive; + cps->sdKludge = FALSE; + cps->stKludge = FALSE; + TidyProgramName(cps->program, cps->host, cps->tidy); + cps->matchWins = 0; + safeStrCpy(cps->variants, appData.variant, MSG_SIZ); + cps->analysisSupport = 2; /* detect */ + cps->analyzing = FALSE; + cps->initDone = FALSE; + + /* New features added by Tord: */ + cps->useFEN960 = FALSE; + cps->useOOCastle = TRUE; + /* End of new features added by Tord. */ + cps->fenOverride = appData.fenOverride[n]; + + /* [HGM] time odds: set factor for each machine */ + cps->timeOdds = appData.timeOdds[n]; + + /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ + cps->accumulateTC = appData.accumulateTC[n]; + cps->maxNrOfSessions = 1; + + /* [HGM] debug */ + cps->debug = FALSE; + cps->supportsNPS = UNKNOWN; + + /* [HGM] options */ + cps->optionSettings = appData.engOptions[n]; + + cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */ + cps->isUCI = appData.isUCI[n]; /* [AS] */ + cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */ + + if (appData.protocolVersion[n] > PROTOVER + || appData.protocolVersion[n] < 1) + { + char buf[MSG_SIZ]; + int len; + + len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), + appData.protocolVersion[n]); + if( (len > MSG_SIZ) && appData.debugMode ) + fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); + + DisplayFatalError(buf, 0, 2); + } + else + { + cps->protocolVersion = appData.protocolVersion[n]; + } + + InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard +} + +ChessProgramState *savCps; + +void +LoadEngine() +{ + int i; + if(WaitForEngine(savCps, LoadEngine)) return; + CommonEngineInit(); // recalculate time odds + if(gameInfo.variant != StringToVariant(appData.variant)) { + // we changed variant when loading the engine; this forces us to reset + Reset(TRUE, savCps != &first); + EditGameEvent(); // for consistency with other path, as Reset changes mode + } + InitChessProgram(savCps, FALSE); + SendToProgram("force\n", savCps); + DisplayMessage("", ""); + if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); + for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps); + ThawUI(); + SetGNUMode(); +} + +void +ReplaceEngine(ChessProgramState *cps, int n) +{ + EditGameEvent(); + UnloadEngine(cps); + appData.noChessProgram = False; + appData.clockMode = True; + InitEngine(cps, n); + if(n) return; // only startup first engine immediately; second can wait + savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-( + LoadEngine(); +} + +void InitBackEnd1() { int matched, min, sec; @@ -737,137 +931,12 @@ InitBackEnd1() /* [AS] Adjudication threshold */ adjudicateLossThreshold = appData.adjudicateLossThreshold; - first.which = _("first"); - second.which = _("second"); - first.maybeThinking = second.maybeThinking = FALSE; - first.pr = second.pr = NoProc; - first.isr = second.isr = NULL; - first.sendTime = second.sendTime = 2; - first.sendDrawOffers = 1; - if (appData.firstPlaysBlack) { - first.twoMachinesColor = "black\n"; - second.twoMachinesColor = "white\n"; - } else { - first.twoMachinesColor = "white\n"; - second.twoMachinesColor = "black\n"; - } - first.program = appData.firstChessProgram; - second.program = appData.secondChessProgram; - first.host = appData.firstHost; - second.host = appData.secondHost; - first.dir = appData.firstDirectory; - second.dir = appData.secondDirectory; - first.other = &second; - second.other = &first; - first.initString = appData.initString; - second.initString = appData.secondInitString; - first.computerString = appData.firstComputerString; - second.computerString = appData.secondComputerString; - first.useSigint = second.useSigint = TRUE; - first.useSigterm = second.useSigterm = TRUE; - first.reuse = appData.reuseFirst; - second.reuse = appData.reuseSecond; - first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second - second.nps = appData.secondNPS; - first.useSetboard = second.useSetboard = FALSE; - first.useSAN = second.useSAN = FALSE; - first.usePing = second.usePing = FALSE; - first.lastPing = second.lastPing = 0; - first.lastPong = second.lastPong = 0; - first.usePlayother = second.usePlayother = FALSE; - first.useColors = second.useColors = TRUE; - first.useUsermove = second.useUsermove = FALSE; - first.sendICS = second.sendICS = FALSE; - first.sendName = second.sendName = appData.icsActive; - first.sdKludge = second.sdKludge = FALSE; - first.stKludge = second.stKludge = FALSE; - TidyProgramName(first.program, first.host, first.tidy); - TidyProgramName(second.program, second.host, second.tidy); - first.matchWins = second.matchWins = 0; - safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0])); - safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0])); - first.analysisSupport = second.analysisSupport = 2; /* detect */ - first.analyzing = second.analyzing = FALSE; - first.initDone = second.initDone = FALSE; - - /* New features added by Tord: */ - first.useFEN960 = FALSE; second.useFEN960 = FALSE; - first.useOOCastle = TRUE; second.useOOCastle = TRUE; - /* End of new features added by Tord. */ - first.fenOverride = appData.fenOverride1; - second.fenOverride = appData.fenOverride2; - - /* [HGM] time odds: set factor for each machine */ - first.timeOdds = appData.firstTimeOdds; - second.timeOdds = appData.secondTimeOdds; - { float norm = 1; - if(appData.timeOddsMode) { - norm = first.timeOdds; - if(norm > second.timeOdds) norm = second.timeOdds; - } - first.timeOdds /= norm; - second.timeOdds /= norm; - } - - /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ - first.accumulateTC = appData.firstAccumulateTC; - second.accumulateTC = appData.secondAccumulateTC; - first.maxNrOfSessions = second.maxNrOfSessions = 1; - - /* [HGM] debug */ - first.debug = second.debug = FALSE; - first.supportsNPS = second.supportsNPS = UNKNOWN; - - /* [HGM] options */ - first.optionSettings = appData.firstOptions; - second.optionSettings = appData.secondOptions; - - first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */ - second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */ - first.isUCI = appData.firstIsUCI; /* [AS] */ - second.isUCI = appData.secondIsUCI; /* [AS] */ - first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */ - second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */ - - if (appData.firstProtocolVersion > PROTOVER - || appData.firstProtocolVersion < 1) - { - char buf[MSG_SIZ]; - int len; - - len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), - appData.firstProtocolVersion); - if( (len > MSG_SIZ) && appData.debugMode ) - fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); - - DisplayFatalError(buf, 0, 2); - } - else - { - first.protocolVersion = appData.firstProtocolVersion; - } - - if (appData.secondProtocolVersion > PROTOVER - || appData.secondProtocolVersion < 1) - { - char buf[MSG_SIZ]; - int len; - - len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), - appData.secondProtocolVersion); - if( (len > MSG_SIZ) && appData.debugMode ) - fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); - - DisplayFatalError(buf, 0, 2); - } - else - { - second.protocolVersion = appData.secondProtocolVersion; - } + InitEngine(&first, 0); + InitEngine(&second, 1); + CommonEngineInit(); if (appData.icsActive) { appData.clockMode = TRUE; /* changes dynamically in ICS mode */ -// } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) { } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode appData.clockMode = FALSE; first.sendTime = second.sendTime = 0; @@ -883,15 +952,6 @@ InitBackEnd1() } #endif - if (appData.noChessProgram) { - programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); - sprintf(programVersion, "%s", PACKAGE_STRING); - } else { - /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ - programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); - sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); - } - if (!appData.icsActive) { char buf[MSG_SIZ]; int len; @@ -967,8 +1027,6 @@ InitBackEnd1() } } - InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard - InitEngineUCI( installDir, &second ); } int NextIntegerFromString( char ** str, long * value ) @@ -1173,6 +1231,40 @@ InitBackEnd2() } void +MatchEvent(int mode) +{ // [HGM] moved out of InitBackend3, to make it callable when match starts through menu + /* Set up machine vs. machine match */ + if (appData.noChessProgram) { + DisplayFatalError(_("Can't have a match with no chess programs"), + 0, 2); + return; + } + matchMode = mode; + matchGame = 1; + if (*appData.loadGameFile != NULLCHAR) { + int index = appData.loadGameIndex; // [HGM] autoinc + if(index<0) lastIndex = index = 1; + if (!LoadGameFromFile(appData.loadGameFile, + index, + appData.loadGameFile, FALSE)) { + DisplayFatalError(_("Bad game file"), 0, 1); + return; + } + } else if (*appData.loadPositionFile != NULLCHAR) { + int index = appData.loadPositionIndex; // [HGM] autoinc + if(index<0) lastIndex = index = 1; + if (!LoadPositionFromFile(appData.loadPositionFile, + index, + appData.loadPositionFile)) { + DisplayFatalError(_("Bad position file"), 0, 1); + return; + } + } + first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches + TwoMachinesEvent(); +} + +void InitBackEnd3 P((void)) { GameMode initialMode; @@ -1232,6 +1324,12 @@ InitBackEnd3 P((void)) DisplayMessage("", ""); if (StrCaseCmp(appData.initialMode, "") == 0) { initialMode = BeginningOfGame; + if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back + gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted + ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it + gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position" + ModeHighlight(); + } } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { initialMode = TwoMachinesPlay; } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { @@ -1258,34 +1356,7 @@ InitBackEnd3 P((void)) } if (appData.matchMode) { - /* Set up machine vs. machine match */ - if (appData.noChessProgram) { - DisplayFatalError(_("Can't have a match with no chess programs"), - 0, 2); - return; - } - matchMode = TRUE; - matchGame = 1; - if (*appData.loadGameFile != NULLCHAR) { - int index = appData.loadGameIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadGameFromFile(appData.loadGameFile, - index, - appData.loadGameFile, FALSE)) { - DisplayFatalError(_("Bad game file"), 0, 1); - return; - } - } else if (*appData.loadPositionFile != NULLCHAR) { - int index = appData.loadPositionIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile)) { - DisplayFatalError(_("Bad position file"), 0, 1); - return; - } - } - TwoMachinesEvent(); + MatchEvent(TRUE); } else if (*appData.cmailGameName != NULLCHAR) { /* Set up cmail mode */ ReloadCmailMsgEvent(TRUE); @@ -2861,7 +2932,7 @@ read_from_ics(isr, closure, data, count, error) if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel for(p=0; p= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) { talker[0] = '['; strcat(talker, "] "); Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE); chattingPartner = p; break; @@ -2916,9 +2987,9 @@ read_from_ics(isr, closure, data, count, error) } } // [HGM] chat: end of patch + backup = i; if (appData.zippyTalk || appData.zippyPlay) { /* [DM] Backup address for color zippy lines */ - backup = i; #if ZIPPY if (loggedOn == TRUE) if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) || @@ -3083,6 +3154,8 @@ read_from_ics(isr, closure, data, count, error) continue; } + if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't + if (looking_at(buf, &i, "\\ ")) { if (prevColor != ColorNormal) { if (oldi > next_out) { @@ -4368,7 +4441,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) // inherit info that ICS does not give from previous board + if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper + && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board for(k=0; k= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn; + if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare; + if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare; + do { + promoSweep -= step; + if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap + else if((int)promoSweep == -1) promoSweep = WhiteKing; + else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn; + else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing; + if(!step) step = 1; + } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || + appData.testLegality && (promoSweep == king || + gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); + ChangeDragPiece(promoSweep); +} + +int PromoScroll(int x, int y) +{ + int step = 0; + + if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE; + if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE; + if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; + if(!step) return FALSE; + lastX = x; lastY = y; + if((promoSweep < BlackPawn) == flipView) step = -step; + if(step > 0) selectFlag = 1; + if(!selectFlag) Sweep(step); + return FALSE; +} + +void +NextPiece(int step) +{ + ChessSquare piece = boards[currentMove][toY][toX]; + do { + pieceSweep -= step; + if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap + if((int)pieceSweep == -1) pieceSweep = BlackKing; + if(!step) step = -1; + } while(PieceToChar(pieceSweep) == '.'); + boards[currentMove][toY][toX] = pieceSweep; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][toY][toX] = piece; +} /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */ void AlphaRank(char *move, int n) @@ -5017,8 +5144,6 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f DrawPosition(TRUE, boards[currentMove]); } -static int lastX, lastY; - Boolean LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end) { @@ -5064,13 +5189,17 @@ MovePV(int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) int margin = h>>3, step = 0; - if(endPV < 0) return; // we must somehow check if right button is still down (might be released off board!) - if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else - if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else - if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1; + if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-( + if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return; + if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return; lastX = x; lastY = y; + + if(pieceSweep != EmptySquare) { NextPiece(step); return; } + if(endPV < 0) return; + if(y < margin) step = 1; else + if(y > h - margin) step = -1; if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0; currentMove += step; if(currentMove == forwardMostMove) ClearPremoveHighlights(); else @@ -5647,6 +5776,21 @@ SendBoard(cps, moveNum) setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } +ChessSquare +DefaultPromoChoice(int white) +{ + ChessSquare result; + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) + result = WhiteFerz; // no choice + else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) + result= WhiteKing; // in Suicide Q is the last thing we want + else if(gameInfo.variant == VariantSpartan) + result = white ? WhiteQueen : WhiteAngel; + else result = WhiteQueen; + if(!white) result = WHITE_TO_BLACK result; + return result; +} + static int autoQueen; // [HGM] oneclick int @@ -5730,12 +5874,13 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *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 - else *promoChoice = PieceToChar(BlackQueen); - return FALSE; - } + // 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 + && gameInfo.variant != VariantSuper) return FALSE; + if(autoQueen) return FALSE; // predetermined // suppress promotion popup on illegal moves that are not premoves premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) || @@ -5985,6 +6130,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) case BeginningOfGame: case AnalyzeMode: case Training: + if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { /* User is moving for Black */ @@ -6079,7 +6225,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) pup = boards[currentMove][toY][toX]; /* [HGM] If move started in holdings, it means a drop. Convert to standard form */ - if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) { if( pup != EmptySquare ) return; moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", @@ -6187,7 +6333,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ - if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end + if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + return 1; + } if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { @@ -6242,6 +6391,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) switch (gameMode) { case EditGame: + if(appData.testLegality) switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: @@ -6271,6 +6421,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw + promoDefaultAltered = FALSE; // [HGM] fall back on default choice if(bookHit) { // [HGM] book: simulate book reply static char bookMove[MSG_SIZ]; // a bit generous? @@ -6339,12 +6490,27 @@ Explode(Board board, int fromX, int fromY, int toX, int toY) ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging +int CanPromote(ChessSquare piece, int y) +{ + if(gameMode == EditPosition) return FALSE; // no promotions when editing position + // some variants have fixed promotion piece, no promotion at all, or another selection mechanism + if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi || + gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || + gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk) return FALSE; + return (piece == BlackPawn && y == 1 || + piece == WhitePawn && y == BOARD_HEIGHT-2 || + piece == BlackLance && y == 1 || + piece == WhiteLance && y == BOARD_HEIGHT-2 ); +} + void LeftClick(ClickType clickType, int xPix, int yPix) { int x, y; Boolean saveAnimate; - static int second = 0, promotionChoice = 0, dragging = 0; + static int second = 0, promotionChoice = 0, clearFlag = 0; char promoChoice = NULLCHAR; + ChessSquare piece; if(appData.seekGraph && appData.icsActive && loggedOn && (gameMode == BeginningOfGame || gameMode == IcsIdle)) { @@ -6364,6 +6530,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) x = BOARD_WIDTH - 1 - x; } + if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece + defaultPromoChoice = promoSweep; + promoSweep = EmptySquare; // terminate sweep + promoDefaultAltered = TRUE; + if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting + } + if(promotionChoice) { // we are waiting for a click to indicate promotion piece if(clickType == Release) return; // ignore upclick of click-click destination promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel @@ -6392,9 +6565,19 @@ void LeftClick(ClickType clickType, int xPix, int yPix) || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) ) return; + if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) + fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click + + if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for + int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack || + gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove)); + defaultPromoChoice = DefaultPromoChoice(side); + } + autoQueen = appData.alwaysPromoteToQueen; if (fromX == -1) { + int originalY = y; gatingPiece = EmptySquare; if (clickType != Press) { if(dragging) { // [HGM] from-square must have been reset due to game end since last press @@ -6403,18 +6586,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } return; } - if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) { + fromX = x; fromY = y; + if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || + // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection + appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { /* First square */ - if (OKToStartUserMove(x, y)) { - fromX = x; - fromY = y; + if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); DragPieceBegin(xPix, yPix); dragging = 1; + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { + promoSweep = defaultPromoChoice; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); + } if (appData.highlightDragging) { - SetHighlights(x, y, -1, -1); + SetHighlights(fromX, fromY, -1, -1); } - } + } else fromX = fromY = -1; return; } } @@ -6442,6 +6632,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); + promoDefaultAltered = FALSE; if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) { if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); @@ -6458,6 +6649,11 @@ void LeftClick(ClickType clickType, int xPix, int yPix) fromY = y; dragging = 1; MarkTargetSquares(0); DragPieceBegin(xPix, yPix); + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { + promoSweep = defaultPromoChoice; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + } } } if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on @@ -6469,6 +6665,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if (clickType == Release && x == fromX && y == fromY) { DragPieceEnd(xPix, yPix); dragging = 0; + if(clearFlag) { + // a deferred attempt to click-click move an empty square on top of a piece + boards[currentMove][y][x] = EmptySquare; + ClearHighlights(); + DrawPosition(FALSE, boards[currentMove]); + fromX = fromY = -1; clearFlag = 0; + return; + } if (appData.animateDragging) { /* Undo animation damage if any */ DrawPosition(FALSE, NULL); @@ -6488,12 +6692,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix) return; } + clearFlag = 0; + /* we now have a different from- and (possibly off-board) to-square */ /* Completed move */ toX = x; toY = y; saveAnimate = appData.animate; if (clickType == Press) { + if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { + // must be Edit Position mode with empty-square selected + fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag + if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click + return; + } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); @@ -6607,16 +6819,25 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) xSqr = EventToSquare(x, BOARD_WIDTH); ySqr = EventToSquare(y, BOARD_HEIGHT); - if (action == Release) UnLoadPV(); // [HGM] pv + if (action == Release) { + if(pieceSweep != EmptySquare) { + EditPositionMenuEvent(pieceSweep, toX, toY); + pieceSweep = EmptySquare; + } else UnLoadPV(); // [HGM] pv + } if (action != Press) return -2; // return code to be ignored switch (gameMode) { case IcsExamining: if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; case EditPosition: if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; - if (xSqr < 0 || ySqr < 0) return -1; - whichMenu = 0; // edit-position menu - break; + 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; case IcsObserving: if(!appData.icsEngineAnalyze) return -1; case IcsPlayingWhite: @@ -6810,7 +7031,6 @@ Adjudicate(ChessProgramState *cps) if(canAdjudicate && appData.checkMates) { if(engineOpponent) SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, "Xboard adjudication: King destroyed", GE_XBOARD ); return 1; @@ -6824,7 +7044,6 @@ Adjudicate(ChessProgramState *cps) if(canAdjudicate && appData.checkMates) { if(engineOpponent) SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, "Xboard adjudication: Bare king", GE_XBOARD ); return 1; @@ -6837,7 +7056,6 @@ Adjudicate(ChessProgramState *cps) /* but only adjudicate if adjudication enabled */ if(engineOpponent) SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, "Xboard adjudication: Bare king", GE_XBOARD ); return 1; @@ -6898,7 +7116,6 @@ Adjudicate(ChessProgramState *cps) if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested if(engineOpponent) SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( result, reason, GE_XBOARD ); return 1; } @@ -6917,7 +7134,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD ); return 1; } @@ -6938,7 +7154,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); return 1; } @@ -7021,7 +7236,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( result, details, GE_XBOARD ); return 1; } @@ -7058,7 +7272,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); return 1; } @@ -7081,7 +7294,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, p, GE_XBOARD ); return 1; } @@ -7092,7 +7304,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); return 1; } @@ -7271,7 +7482,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h &fromX, &fromY, &toX, &toY, &promoChar)) { /* Machine move could not be parsed; ignore it. */ snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"), - machineMove, cps->which); + machineMove, _(cps->which)); DisplayError(buf1, 0); snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); @@ -7328,11 +7539,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (cps->sendTime == 2) cps->sendTime = 1; if (cps->offeredDraw) cps->offeredDraw--; - /* currentMoveString is set as a side-effect of ParseOneMove */ - safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0])); - strcat(machineMove, "\n"); - safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0])); - /* [AS] Save move info*/ pvInfoList[ forwardMostMove ].score = programStats.score; pvInfoList[ forwardMostMove ].depth = programStats.depth; @@ -7369,7 +7575,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } } - if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends + if(Adjudicate(cps)) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + return; // [HGM] adjudicate: for all automatic game ends + } #if ZIPPY if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && @@ -7691,21 +7900,21 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. gameMode = EditGame; ModeHighlight(); } + /* [HGM] illegal-move claim should forfeit game when Xboard */ + /* only passes fully legal moves */ + if( appData.testLegality && gameMode == TwoMachinesPlay ) { + GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, + "False illegal-move claim", GE_XBOARD ); + return; // do not take back move we tested as valid + } currentMove = forwardMostMove-1; DisplayMove(currentMove-1); /* before DisplayMoveError */ SwitchClocks(forwardMostMove-1); // [HGM] race DisplayBothClocks(); snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"), - parseList[currentMove], cps->which); + parseList[currentMove], _(cps->which)); DisplayMoveError(buf1); DrawPosition(FALSE, boards[currentMove]); - - /* [HGM] illegal-move claim should forfeit game when Xboard */ - /* only passes fully legal moves */ - if( appData.testLegality && gameMode == TwoMachinesPlay ) { - GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, - "False illegal-move claim", GE_XBOARD ); - } return; } if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) { @@ -7728,9 +7937,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. cps->maybeThinking = FALSE; snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), - cps->which, cps->program, cps->host, message); + _(cps->which), cps->program, cps->host, message); RemoveInputSource(cps->isr); - DisplayFatalError(buf1, 0, 1); + if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else { + if(cps == &first) appData.noChessProgram = TRUE; + DisplayError(buf1, 0); + } return; } @@ -7751,7 +7963,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* Hint move could not be parsed!? */ snprintf(buf2, sizeof(buf2), _("Illegal hint move \"%s\"\nfrom %s chess program"), - buf1, cps->which); + buf1, _(cps->which)); DisplayError(buf2, 0); } } else { @@ -8418,7 +8630,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) int i; if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { - if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like + if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) + board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants } else if( board[fromY][fromX] == WhitePawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers @@ -8901,7 +9114,7 @@ InitChessProgram(cps, setup) if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || - gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon ) + gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus ) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if( gameInfo.variant == VariantCourier ) overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; @@ -9453,6 +9666,13 @@ GameEnds(result, resultDetails, whosays) first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); popupRequested++; // [HGM] crash: postpone to after resetting endingGame + if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match + first.twoMachinesColor = "black\n"; + second.twoMachinesColor = "white\n"; + } else { + first.twoMachinesColor = "white\n"; + second.twoMachinesColor = "black\n"; + } } } if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && @@ -9916,8 +10136,6 @@ LoadGameOneMove(readAhead) return FALSE; } else { /* currentMoveString is set as a side-effect of yylex */ - strcat(currentMoveString, "\n"); - safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0])); thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); @@ -10422,6 +10640,7 @@ LoadGame(f, gameNumber, title, useList) for (i = BOARD_HEIGHT - 1; i >= 0; i--) for (j = BOARD_LEFT; j < BOARD_RGHT; p++) switch (*p) { + case '{': case '[': case '-': case ' ': @@ -11907,16 +12126,18 @@ SettingsMenuIfReady() } int -WaitForSecond(DelayedEventCallback retry) +WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) { - if (second.pr == NULL) { - StartChessProgram(&second); - if (second.protocolVersion == 1) { + char buf[MSG_SIZ]; + if (cps->pr == NULL) { + StartChessProgram(cps); + if (cps->protocolVersion == 1) { retry(); } else { /* kludge: allow timeout for initial "feature" command */ FreezeUI(); - DisplayMessage("", _("Starting second chess program")); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); } return 1; @@ -11931,6 +12152,7 @@ TwoMachinesEvent P((void)) char buf[MSG_SIZ]; ChessProgramState *onmove; char *bookHit = NULL; + static int stalling = 0; if (appData.noChessProgram) return; @@ -11966,14 +12188,24 @@ TwoMachinesEvent P((void)) TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... ResurrectChessProgram(); /* in case first program isn't running */ - if(WaitForSecond(TwoMachinesEventIfReady)) return; - DisplayMessage("", ""); - InitChessProgram(&second, FALSE); - SendToProgram("force\n", &second); + if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position + DisplayMessage("", _("Waiting for first chess program")); ScheduleDelayedEvent(TwoMachinesEvent, 10); return; } + if(!stalling) { + InitChessProgram(&second, FALSE); + SendToProgram("force\n", &second); + } + if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash + if(!stalling) DisplayMessage("", _("Waiting for second chess program")); + stalling = 1; + ScheduleDelayedEvent(TwoMachinesEvent, 10); + return; + } + stalling = 0; + DisplayMessage("", ""); if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); if (appData.debugMode) { @@ -12609,6 +12841,32 @@ CallFlagEvent() } void +ClockClick(int which) +{ // [HGM] code moved to back-end from winboard.c + if(which) { // black clock + if (gameMode == EditPosition || gameMode == IcsExamining) { + if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); + SetBlackToPlayEvent(); + } else if (gameMode == EditGame || shiftKey) { + AdjustClock(which, -1); + } else if (gameMode == IcsPlayingWhite || + gameMode == MachinePlaysBlack) { + CallFlagEvent(); + } + } else { // white clock + if (gameMode == EditPosition || gameMode == IcsExamining) { + if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); + SetWhiteToPlayEvent(); + } else if (gameMode == EditGame || shiftKey) { + AdjustClock(which, -1); + } else if (gameMode == IcsPlayingBlack || + gameMode == MachinePlaysWhite) { + CallFlagEvent(); + } + } +} + +void DrawEvent() { /* Offer draw or accept pending draw offer from opponent */ @@ -13543,11 +13801,12 @@ SendToProgram(message, cps) outCount = OutputToProcess(cps->pr, message, count, &error); if (outCount < count && !exiting && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */ - snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which); + if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load + snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which)); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ - snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program); + snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); } else { gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; } @@ -13573,11 +13832,11 @@ ReceiveFromProgram(isr, closure, message, count, error) if (count <= 0) { if (count == 0) { snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"), - cps->which, cps->program); + _(cps->which), cps->program); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ - snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program); + snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); } else { gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; } @@ -13587,7 +13846,7 @@ ReceiveFromProgram(isr, closure, message, count, error) if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1; } else { snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"), - cps->which, cps->program); + _(cps->which), cps->program); RemoveInputSource(cps->isr); /* [AS] Program is misbehaving badly... kill it */ @@ -13705,7 +13964,7 @@ SendTimeControl(cps, mps, tc, inc, sd, st) SendToProgram(buf, cps); } - if(cps->nps > 0) { /* [HGM] nps */ + if(cps->nps >= 0) { /* [HGM] nps */ if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported! else { @@ -13858,11 +14117,11 @@ ParseOption(Option *opt, ChessProgramState *cps) } else if((p = strstr(opt->name, " -file "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots opt->textValue = p+7; - opt->type = TextBox; // FileName; + opt->type = FileName; // FileName; } else if((p = strstr(opt->name, " -path "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots opt->textValue = p+7; - opt->type = TextBox; // PathName; + opt->type = PathName; // PathName; } else if(p = strstr(opt->name, " -check ")) { if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); @@ -13924,6 +14183,7 @@ FeatureDone(cps, val) DelayedEventCallback cb = GetDelayedEvent(); if ((cb == InitBackEnd3 && cps == &first) || (cb == SettingsMenuIfReady && cps == &second) || + (cb == LoadEngine) || (cb == TwoMachinesEventIfReady && cps == &second)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000); @@ -13998,7 +14258,7 @@ ParseFeatures(args, cps) } if(cps->nrOptions >= MAX_OPTIONS) { cps->nrOptions--; - snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which); + snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which)); DisplayError(buf, 0); } continue; @@ -14117,6 +14377,55 @@ 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 ) || + gameMode == Training) PopUpMoveDialog(firstChar); +} + +void +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")); + } + } +} + +void DisplayMove(moveNumber) int moveNumber; { @@ -14534,15 +14843,15 @@ SwitchClocks(int newMoveNr) if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ -// if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = // use GUI time +// if(pvInfoList[forwardMostMove].time == -1) + pvInfoList[forwardMostMove].time = // use GUI time (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10; } else { if(whiteNPS >= 0) lastTickLength = 0; whiteTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ -// if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = +// if(pvInfoList[forwardMostMove].time == -1) + pvInfoList[forwardMostMove].time = (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10; } flagged = CheckFlags();