X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=f6e75c49f0e69aa380491ecdccc2ddb9739b3116;hb=2b59fb6b9575ad038bae89a48063084c8b3243a9;hp=c0d36aa364926ad4eb8906b5a24719e9a3e3b2d0;hpb=67e15f886057db1a570ab8e29e7a8a6277736fb3;p=xboard.git diff --git a/backend.c b/backend.c index c0d36aa..f6e75c4 100644 --- a/backend.c +++ b/backend.c @@ -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) @@ -268,7 +271,7 @@ extern int chatCount; int chattingPartner; char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ ChessSquare pieceSweep = EmptySquare; -ChessSquare promoSweep = EmptySquare, defaultPromoChoice, savePiece = EmptySquare; +ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; /* States for ics_getting_history */ @@ -325,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; @@ -659,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; @@ -740,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; @@ -886,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; @@ -970,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 ) @@ -1176,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; @@ -1235,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) { @@ -1261,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); @@ -2919,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) || @@ -3086,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) { @@ -4814,9 +4884,7 @@ ProcessICSInitScript(f) } -static int lastX, lastY, selectFlag, dragging, sweepX, sweepY; -static ChessSquare substitute = EmptySquare; -static char defaultPromoChar; +static int lastX, lastY, selectFlag, dragging; void Sweep(int step) @@ -4826,7 +4894,7 @@ Sweep(int step) if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare; if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn; if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare; - if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare; + if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare; do { promoSweep -= step; if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap @@ -4837,30 +4905,22 @@ Sweep(int step) } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || appData.testLegality && (promoSweep == king || gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); - boards[currentMove][sweepY][sweepX] = promoSweep; - DrawPosition(FALSE, boards[currentMove]); + ChangeDragPiece(promoSweep); } int PromoScroll(int x, int y) { int step = 0; + if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE; - if(!selectFlag) { - if(y - lastY < 4 && lastY - y < 4) return FALSE; // assume dragging until significant distance - if(substitute != EmptySquare && ((promoSweep >= BlackPawn) == flipView ? y <= lastY : y >= lastY)) { // we started dragging - defaultPromoChar = ToLower(PieceToChar(promoSweep)); // fix choice - promoSweep = EmptySquare; - return FALSE; - } - DragPieceEnd(x, y); dragging = 0; - selectFlag = 1; // we committed to sweep-selecting - } - if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return TRUE; + 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 TRUE; + if(!step) return FALSE; lastX = x; lastY = y; - Sweep(step); - return TRUE; + if((promoSweep < BlackPawn) == flipView) step = -step; + if(step > 0) selectFlag = 1; + if(!selectFlag) Sweep(step); + return FALSE; } void @@ -5127,9 +5187,10 @@ UnLoadPV() void MovePV(int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) - int margin = h>>3, step = 0, dist; + int margin = h>>3, step = 0; // we must somehow check if right button is still down (might be released off board!) + 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; @@ -5813,14 +5874,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *promoChoice = PieceToChar(BlackQueen); // Queen as good as any return FALSE; } - // with sweep-selection we take the selected default - if(appData.sweepSelect) { - *promoChoice = defaultPromoChar; - 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 @@ -6433,18 +6492,23 @@ 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 || - gameInfo.variant != VariantSuper && - (piece == BlackLance && y == 1 || - piece == WhiteLance && 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, canPromote; + int x, y; Boolean saveAnimate; - static int second = 0, promotionChoice = 0; + static int second = 0, promotionChoice = 0, clearFlag = 0; char promoChoice = NULLCHAR; ChessSquare piece; @@ -6467,21 +6531,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece - defaultPromoChar = ToLower(PieceToChar(defaultPromoChoice = promoSweep)); - if(gameInfo.variant == VariantShogi) defaultPromoChar = (promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+'); + defaultPromoChoice = promoSweep; promoSweep = EmptySquare; // terminate sweep promoDefaultAltered = TRUE; - if(savePiece != EmptySquare) { - boards[currentMove][sweepY][sweepX] = savePiece; savePiece = EmptySquare; - clickType = Press; x = toX; y = toY; // fake up-click on to-square to finish one-click move - } else x = fromX, y = fromY; // and fake up-click on same square otherwise - } - - if(substitute != EmptySquare) { - boards[currentMove][fromY][fromX] = substitute; - substitute = EmptySquare; - DragPieceEnd(xPix, yPix); dragging = 0; - DrawPosition(FALSE, boards[currentMove]); + 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 @@ -6513,17 +6566,18 @@ void LeftClick(ClickType clickType, int xPix, int yPix) return; if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) - fromX = fromY = -1; // second click on piece after altering default treated as first click + 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 + 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 @@ -6532,31 +6586,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } return; } - if(appData.oneClick && OnlyMove(&x, &y, FALSE)) { - if(appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY)) { - promoSweep = defaultPromoChoice; - savePiece = boards[currentMove][sweepY = toY = y][sweepX = toX = x]; - selectFlag = 0; lastX = xPix; lastY = yPix; - Sweep(0); // Pawn that is going to promote: preview promotion piece - return; - } - } else { + 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)) { - sweepX = fromX = x; - sweepY = fromY = y; + if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); - if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { + DragPieceBegin(xPix, yPix); dragging = 1; + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; - substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix; + selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); } - DragPieceBegin(xPix, yPix); dragging = 1; if (appData.highlightDragging) { - SetHighlights(x, y, -1, -1); + SetHighlights(fromX, fromY, -1, -1); } - } + } else fromX = fromY = -1; return; } } @@ -6597,15 +6645,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix) y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) gatingPiece = boards[currentMove][fromY][fromX]; else gatingPiece = EmptySquare; - sweepX = fromX = x; - sweepY = fromY = y; dragging = 1; + fromX = x; + fromY = y; dragging = 1; MarkTargetSquares(0); + DragPieceBegin(xPix, yPix); if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { promoSweep = defaultPromoChoice; - substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix; + selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece } - DragPieceBegin(xPix, yPix); } } if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on @@ -6617,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); @@ -6636,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); @@ -6707,14 +6771,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) DisplayMessage("Click in holdings to choose piece", ""); return; } - if(appData.sweepSelect && clickType == Press) { - lastX = xPix; lastY = yPix; - ChessSquare piece = boards[currentMove][fromY][fromX]; - promoSweep = defaultPromoChoice; - if(gameInfo.variant == VariantShogi) promoSweep = PROMOTED piece; - sweepX = toX; sweepY = toY; - selectFlag = 1; Sweep(0); - } else PromotionPopUp(); + PromotionPopUp(); } else { int oldMove = currentMove; UserMoveEvent(fromX, fromY, toX, toY, promoChoice); @@ -7882,7 +7939,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), _(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; } @@ -12066,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; @@ -12090,6 +12152,7 @@ TwoMachinesEvent P((void)) char buf[MSG_SIZ]; ChessProgramState *onmove; char *bookHit = NULL; + static int stalling = 0; if (appData.noChessProgram) return; @@ -12125,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) { @@ -13728,6 +13801,7 @@ 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 */ + 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) { @@ -14109,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); @@ -14302,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; {