X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=2c245d6d52b844f26cfd36c7575246bbda4990a8;hb=0332dbee9a4458560fe2c35aef0356cabca2bebc;hp=257467385cadc6a752a44704bd96091dae5384e6;hpb=344885687fdf87558b7932280545f5f4ea78771c;p=xboard.git diff --git a/backend.c b/backend.c index 2574673..2c245d6 100644 --- a/backend.c +++ b/backend.c @@ -167,6 +167,7 @@ int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, /*char*/int promoChar)); void BackwardInner P((int target)); void ForwardInner P((int target)); +int Adjudicate P((ChessProgramState *cps)); void GameEnds P((ChessMove result, char *resultDetails, int whosays)); void EditPositionDone P((Boolean fakeRights)); void PrintOpponents P((FILE *fp)); @@ -187,6 +188,7 @@ void DisplayMove P((int moveNumber)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); +void KeepAlive P((void)); void StartClocks P((void)); void SwitchClocks P((void)); void StopClocks P((void)); @@ -240,10 +242,12 @@ int endPV = -1; static int exiting = 0; /* [HGM] moved to top */ static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/; int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */ +int rightsBoard[BOARD_RANKS][BOARD_FILES]; /* [HGM] editrights */ char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */ int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */ VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */ int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */ +Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */ int opponentKibitzes; int lastSavedGame; /* [HGM] save: ID of game */ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ @@ -356,6 +360,7 @@ PosFlags(index) case VariantNoCastle: case VariantShatranj: case VariantCourier: + case VariantMakruk: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -489,10 +494,10 @@ ChessSquare KnightmateArray[2][BOARD_FILES] = { }; ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */ - { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen, + { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen, - BlackKing, BlackBishop, BlackKnight, BlackRook } + { BlackLance, BlackAlfil, BlackMarshall, BlackAngel, + BlackKing, BlackMarshall, BlackAlfil, BlackLance } }; ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ @@ -502,6 +507,13 @@ ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatr BlackFerz, BlackAlfil, BlackKnight, BlackRook } }; +ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ + { WhiteRook, WhiteKnight, WhiteMan, WhiteKing, + WhiteFerz, WhiteMan, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackMan, BlackFerz, + BlackKing, BlackMan, BlackKnight, BlackRook } +}; + #if (BOARD_FILES>=10) ChessSquare ShogiArray[2][BOARD_FILES] = { @@ -888,6 +900,7 @@ 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 VariantBerolina: /* might work if TestLegality is off */ case VariantCapaRandom: /* should work */ case VariantJanus: /* should work */ @@ -1083,198 +1096,270 @@ InitBackEnd2() void InitBackEnd3 P((void)) { - GameMode initialMode; - char buf[MSG_SIZ]; - int err; - - InitChessProgram(&first, startedFromSetupPosition); - - - if (appData.icsActive) { -#ifdef WIN32 - /* [DM] Make a console window if needed [HGM] merged ifs */ - ConsoleCreate(); -#endif - err = establish(); - if (err != 0) { - if (*appData.icsCommPort != NULLCHAR) { - sprintf(buf, _("Could not open comm port %s"), - appData.icsCommPort); - } else { - snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"), - appData.icsHost, appData.icsPort); + GameMode initialMode; + char buf[MSG_SIZ]; + int err; + + InitChessProgram(&first, startedFromSetupPosition); + + + if (appData.icsActive) + { + err = establish(); + if (err != 0) + { + if (*appData.icsCommPort != NULLCHAR) + { + sprintf(buf, _("Could not open comm port %s"), + appData.icsCommPort); } - DisplayFatalError(buf, err, 1); - return; + else + { + snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"), + appData.icsHost, appData.icsPort); + } + DisplayFatalError(buf, err, 1); + return; } + SetICSMode(); telnetISR = AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR); fromUserISR = AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR); - } else if (appData.noChessProgram) { - SetNCPMode(); - } else { - SetGNUMode(); + if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command + ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); } - - if (*appData.cmailGameName != NULLCHAR) { - SetCmailMode(); - OpenLoopback(&cmailPR); - cmailISR = - AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR); + else if (appData.noChessProgram) + { + SetNCPMode(); + } + else + { + SetGNUMode(); } - - ThawUI(); - DisplayMessage("", ""); - if (StrCaseCmp(appData.initialMode, "") == 0) { + + if (*appData.cmailGameName != NULLCHAR) + { + SetCmailMode(); + OpenLoopback(&cmailPR); + cmailISR = + AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR); + } + + ThawUI(); + DisplayMessage("", ""); + if (StrCaseCmp(appData.initialMode, "") == 0) + { initialMode = BeginningOfGame; - } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) + { initialMode = TwoMachinesPlay; - } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) + { initialMode = AnalyzeFile; - } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) + { initialMode = AnalyzeMode; - } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) + { initialMode = MachinePlaysWhite; - } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) + { initialMode = MachinePlaysBlack; - } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) + { initialMode = EditGame; - } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) + { initialMode = EditPosition; - } else if (StrCaseCmp(appData.initialMode, "Training") == 0) { + } + else if (StrCaseCmp(appData.initialMode, "Training") == 0) + { initialMode = Training; - } else { + } + else + { sprintf(buf, _("Unknown initialMode %s"), appData.initialMode); DisplayFatalError(buf, 0, 2); return; } - - 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; + + 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; + 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; + } + 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(); - } else if (*appData.cmailGameName != NULLCHAR) { - /* Set up cmail mode */ - ReloadCmailMsgEvent(TRUE); - } else { - /* Set up other modes */ - if (initialMode == AnalyzeFile) { - if (*appData.loadGameFile == NULLCHAR) { - DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1); - return; - } + TwoMachinesEvent(); + } + else if (*appData.cmailGameName != NULLCHAR) + { + /* Set up cmail mode */ + ReloadCmailMsgEvent(TRUE); + } + else + { + /* Set up other modes */ + if (initialMode == AnalyzeFile) + { + if (*appData.loadGameFile == NULLCHAR) + { + DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1); + return; + } } - if (*appData.loadGameFile != NULLCHAR) { - (void) LoadGameFromFile(appData.loadGameFile, - appData.loadGameIndex, - appData.loadGameFile, TRUE); - } else if (*appData.loadPositionFile != NULLCHAR) { - (void) LoadPositionFromFile(appData.loadPositionFile, - appData.loadPositionIndex, - appData.loadPositionFile); - /* [HGM] try to make self-starting even after FEN load */ - /* to allow automatic setup of fairy variants with wtm */ - if(initialMode == BeginningOfGame && !blackPlaysFirst) { - gameMode = BeginningOfGame; - setboardSpoiledMachineBlack = 1; - } - /* [HGM] loadPos: make that every new game uses the setup */ - /* from file as long as we do not switch variant */ - if(!blackPlaysFirst) { - startedFromPositionFile = TRUE; - CopyBoard(filePosition, boards[0]); + if (*appData.loadGameFile != NULLCHAR) + { + (void) LoadGameFromFile(appData.loadGameFile, + appData.loadGameIndex, + appData.loadGameFile, TRUE); + } + else if (*appData.loadPositionFile != NULLCHAR) + { + (void) LoadPositionFromFile(appData.loadPositionFile, + appData.loadPositionIndex, + appData.loadPositionFile); + /* [HGM] try to make self-starting even after FEN load */ + /* to allow automatic setup of fairy variants with wtm */ + if(initialMode == BeginningOfGame && !blackPlaysFirst) + { + gameMode = BeginningOfGame; + setboardSpoiledMachineBlack = 1; } + /* [HGM] loadPos: make that every new game uses the setup */ + /* from file as long as we do not switch variant */ + if(!blackPlaysFirst) + { + startedFromPositionFile = TRUE; + CopyBoard(filePosition, boards[0]); + } } - if (initialMode == AnalyzeMode) { - if (appData.noChessProgram) { - DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2); - return; - } - if (appData.icsActive) { - DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2); - return; - } + if (initialMode == AnalyzeMode) + { + if (appData.noChessProgram) + { + DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2); + return; + } + if (appData.icsActive) + { + DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2); + return; + } AnalyzeModeEvent(); - } else if (initialMode == AnalyzeFile) { + } + else if (initialMode == AnalyzeFile) + { appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent ShowThinkingEvent(); AnalyzeFileEvent(); AnalysisPeriodicEvent(1); - } else if (initialMode == MachinePlaysWhite) { - if (appData.noChessProgram) { - DisplayFatalError(_("MachineWhite mode requires a chess engine"), - 0, 2); - return; - } - if (appData.icsActive) { - DisplayFatalError(_("MachineWhite mode does not work with ICS mode"), - 0, 2); - return; - } + } + else if (initialMode == MachinePlaysWhite) + { + if (appData.noChessProgram) + { + DisplayFatalError(_("MachineWhite mode requires a chess engine"), + 0, 2); + return; + } + if (appData.icsActive) + { + DisplayFatalError(_("MachineWhite mode does not work with ICS mode"), + 0, 2); + return; + } MachineWhiteEvent(); - } else if (initialMode == MachinePlaysBlack) { - if (appData.noChessProgram) { - DisplayFatalError(_("MachineBlack mode requires a chess engine"), - 0, 2); - return; - } - if (appData.icsActive) { - DisplayFatalError(_("MachineBlack mode does not work with ICS mode"), - 0, 2); - return; - } + } + else if (initialMode == MachinePlaysBlack) + { + if (appData.noChessProgram) + { + DisplayFatalError(_("MachineBlack mode requires a chess engine"), + 0, 2); + return; + } + if (appData.icsActive) + { + DisplayFatalError(_("MachineBlack mode does not work with ICS mode"), + 0, 2); + return; + } MachineBlackEvent(); - } else if (initialMode == TwoMachinesPlay) { - if (appData.noChessProgram) { - DisplayFatalError(_("TwoMachines mode requires a chess engine"), - 0, 2); - return; - } - if (appData.icsActive) { - DisplayFatalError(_("TwoMachines mode does not work with ICS mode"), - 0, 2); - return; - } + } + else if (initialMode == TwoMachinesPlay) + { + if (appData.noChessProgram) + { + DisplayFatalError(_("TwoMachines mode requires a chess engine"), + 0, 2); + return; + } + if (appData.icsActive) + { + DisplayFatalError(_("TwoMachines mode does not work with ICS mode"), + 0, 2); + return; + } TwoMachinesEvent(); - } else if (initialMode == EditGame) { + } + else if (initialMode == EditGame) + { EditGameEvent(); - } else if (initialMode == EditPosition) { + } + else if (initialMode == EditPosition) + { EditPositionEvent(); - } else if (initialMode == Training) { - if (*appData.loadGameFile == NULLCHAR) { - DisplayFatalError(_("Training mode requires a game file"), 0, 2); - return; - } + } + else if (initialMode == Training) + { + if (*appData.loadGameFile == NULLCHAR) + { + DisplayFatalError(_("Training mode requires a game file"), 0, 2); + return; + } TrainingEvent(); } } + + return; } /* @@ -1431,6 +1516,8 @@ read_from_player(isr, closure, message, count, error) void KeepAlive() { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out + if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1); + connectionAlive = FALSE; // only sticks if no response to 'date' command. SendToICS("date\n"); if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); } @@ -2047,6 +2134,203 @@ static int player2Rating = -1; ColorClass curColor = ColorNormal; int suppressKibitz = 0; +// [HGM] seekgraph +Boolean soughtPending = FALSE; +Boolean seekGraphUp; +#define MAX_SEEK_ADS 200 +#define SQUARE 0x80 +char *seekAdList[MAX_SEEK_ADS]; +int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS]; +float tcList[MAX_SEEK_ADS]; +char colorList[MAX_SEEK_ADS]; +int nrOfSeekAds = 0; +int minRating = 1010, maxRating = 2800; +int hMargin = 10, vMargin = 20, h, w; +extern int squareSize, lineGap; + +void +PlotSeekAd(int i) +{ + int x, y, color = 0, r = ratingList[i]; float tc = tcList[i]; + xList[i] = yList[i] = -100; // outside graph, so cannot be clicked + if(r < minRating+100 && r >=0 ) r = minRating+100; + if(r > maxRating) r = maxRating; + if(tc < 1.) tc = 1.; + if(tc > 95.) tc = 95.; + x = (w-hMargin)* log(tc)/log(100.) + hMargin; + y = ((double)r - minRating)/(maxRating - minRating) + * (h-vMargin-squareSize/8-1) + vMargin; + if(ratingList[i] < 0) y = vMargin + squareSize/4; + if(strstr(seekAdList[i], " u ")) color = 1; + if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color + !strstr(seekAdList[i], "bullet") && + !strstr(seekAdList[i], "blitz") && + !strstr(seekAdList[i], "standard") ) color = 2; + if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares + DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color); +} + +void +AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) +{ + char buf[MSG_SIZ], *ext = ""; + VariantClass v = StringToVariant(type); + if(strstr(type, "wild")) { + ext = type + 4; // append wild number + if(v == VariantFischeRandom) type = "chess960"; else + if(v == VariantLoadable) type = "setup"; else + type = VariantName(v); + } + sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext); + if(nrOfSeekAds < MAX_SEEK_ADS-1) { + if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]); + ratingList[nrOfSeekAds] = -1; // for if seeker has no rating + sscanf(rating, "%d", &ratingList[nrOfSeekAds]); + tcList[nrOfSeekAds] = base + (2./3.)*inc; + seekNrList[nrOfSeekAds] = nr; + zList[nrOfSeekAds] = 0; + seekAdList[nrOfSeekAds++] = StrSave(buf); + if(plot) PlotSeekAd(nrOfSeekAds-1); + } +} + +void +EraseSeekDot(int i) +{ + int x = xList[i], y = yList[i], d=squareSize/4, k; + DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1); + if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1); + // now replot every dot that overlapped + for(k=0; k x-d && yy <= y+d && yy > y-d) + DrawSeekDot(xx, yy, colorList[k]); + } +} + +void +RemoveSeekAd(int nr) +{ + int i; + for(i=0; i=minRating && i40 ? i%20 : i%10) == 0) { + char buf[MSG_SIZ]; + sprintf(buf, "%d", i); + DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2); + } + } + for(i=0; i0) zList[i] *= 0.8; // age priority + } + if(dist < 120) { + char buf[MSG_SIZ]; + second = (second > 1); + if(displayed != closest || second != lastSecond) { + DisplayMessage(second ? "!" : "", seekAdList[closest]); + lastSecond = second; displayed = closest; + } + sprintf(buf, "play %d\n", seekNrList[closest]); + if(click == Press) { + if(moving == 2) zList[closest] = 100; // right-click; push to back on press + lastDown = closest; + return TRUE; + } // on press 'hit', only show info + if(moving == 2) return TRUE; // ignore right up-clicks on dot + SendToICS(ics_prefix); + SendToICS(buf); // should this be "sought all"? + } else if(click == Release) { // release 'miss' is ignored + zList[lastDown] = 100; // make future selection of the rejected ad more difficult + if(moving == 2) { // right up-click + nrOfSeekAds = 0; // refresh graph + soughtPending = TRUE; + SendToICS(ics_prefix); + SendToICS("sought\n"); // should this be "sought all"? + } + return TRUE; + } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; } + // press miss or release hit 'pop down' seek graph + seekGraphUp = FALSE; + DrawPosition(TRUE, NULL); + } + return TRUE; +} + void read_from_ics(isr, closure, data, count, error) InputSourceRef isr; @@ -2084,6 +2368,8 @@ read_from_ics(isr, closure, data, count, error) char talker[MSG_SIZ]; // [HGM] chat int channel; + connectionAlive = TRUE; // [HGM] alive: I think, therefore I am... + if (appData.debugMode) { if (!error) { fprintf(debugFP, "= 0) // channel broadcast; look if there is a chatbox for this channel for(p=0; p= 3) // [HGM] chat: continuation of line for chat box + chattingPartner = savingComment - 3; // kludge to remember the box } else { started = STARTED_CHATTER; } @@ -2855,6 +3188,11 @@ read_from_ics(isr, closure, data, count, error) if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book + if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line + soughtPending = FALSE; + seekGraphUp = TRUE; + DrawSeekGraph(); + } if(suppressKibitz) next_out = i; savingComment = FALSE; suppressKibitz = 0; @@ -3704,16 +4042,16 @@ ParseBoard12(string) if(moveNum == 0 || gameInfo.variant != VariantFischeRandom) { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing; - for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[0][i] == WhiteRook) j = i; initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); - for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); @@ -3723,6 +4061,11 @@ ParseBoard12(string) for(k=BOARD_LEFT; k= 0 && toY >= 0) { boards[0][toY][toX] = boards[0][fromY][fromX]; @@ -5405,6 +5809,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) } } else boards[0][fromY][fromX] = EmptySquare; + rightsBoard[fromY][fromX] = rightsBoard[toY][toX] = 0; return AmbiguousMove; } return ImpossibleMove; @@ -5429,7 +5834,6 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) return WhiteDrop; /* Not needed to specify white or black yet */ } - userOfferedDraw = FALSE; /* [HGM] always test for legality, to get promotion info */ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), @@ -5562,7 +5966,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ - if (gameMode == BeginningOfGame) + + if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end + +if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { @@ -5585,38 +5992,37 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) StartClocks(); } ModeHighlight(); + } /* Relay move to ICS or chess engine */ - if (appData.icsActive) - { - if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || - gameMode == IcsExamining) - { - SendMoveToICS(moveType, fromX, fromY, toX, toY); - ics_user_moved = 1; - } + + if (appData.icsActive) { + if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || + gameMode == IcsExamining) { + if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { + SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS + SendToICS("draw "); + SendMoveToICS(moveType, fromX, fromY, toX, toY); + } + // also send plain move, in case ICS does not understand atomic claims + SendMoveToICS(moveType, fromX, fromY, toX, toY); + ics_user_moved = 1; + } + } else { + if (first.sendTime && (gameMode == BeginningOfGame || + gameMode == MachinePlaysWhite || + gameMode == MachinePlaysBlack)) { + SendTimeRemaining(&first, gameMode != MachinePlaysBlack); + } + if (gameMode != EditGame && gameMode != PlayFromGameFile) { + // [HGM] book: if program might be playing, let it use book + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); + first.maybeThinking = TRUE; + } else SendMoveToProgram(forwardMostMove-1, &first); + if (currentMove == cmailOldMove + 1) { + cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } - else - { - if (first.sendTime && (gameMode == BeginningOfGame || - gameMode == MachinePlaysWhite || - gameMode == MachinePlaysBlack)) - { - SendTimeRemaining(&first, gameMode != MachinePlaysBlack); - } - if (gameMode != EditGame && gameMode != PlayFromGameFile) - { - // [HGM] book: if program might be playing, let it use book - bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); - first.maybeThinking = TRUE; - } - else - SendMoveToProgram(forwardMostMove-1, &first); - if (currentMove == cmailOldMove + 1) - { - cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; - } } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ @@ -5652,10 +6058,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) default: break; } - + userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw + if(bookHit) { // [HGM] book: simulate book reply - static char bookMove[MSG_SIZ]; // a bit generous? + static char bookMove[MSG_SIZ]; // a bit generous? + programStats.nodes = programStats.depth = programStats.time = programStats.score = programStats.got_only_move = 0; @@ -5691,6 +6099,68 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } +int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 }; +int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 }; +Board promoBoard; +int promotionChoice = 0; + +void +PiecePopUp(int x, int y) +{ + int i, j, h, w, nWhite=0, nBlack=0; + ChessSquare list[EmptySquare]; + for(i=0; iBOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; } + for(i=0; i 0 - : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) { + : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1); // click in right holdings, for determining promotion piece - ChessSquare p = boards[currentMove][y][x]; + if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) { + p = promoBoard[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))); + char promoChar = PieceToChar(p); + if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '='; + FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar)); fromX = fromY = -1; + promotionChoice = 0; return; } } + promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel DrawPosition(FALSE, boards[currentMove]); return; } @@ -5776,6 +6263,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) return; if (fromX == -1) { + if(!appData.oneClick || !OnlyMove(&x, &y)) { if (clickType == Press) { /* First square */ if (OKToStartUserMove(x, y)) { @@ -5790,6 +6278,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } return; + } } /* fromX != -1 */ @@ -5885,13 +6374,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(x == BOARD_LEFT-2 && piece >= BlackPawn) { n = PieceToNumber(piece - (int)BlackPawn); - if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; } + if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; } boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece; boards[currentMove][BOARD_HEIGHT-1 - n][1]++; } else if(x == BOARD_RGHT+1 && piece < BlackPawn) { n = PieceToNumber(piece); - if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; } + if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; } boards[currentMove][n][BOARD_WIDTH-1] = piece; boards[currentMove][n][BOARD_WIDTH-2]++; } @@ -5910,18 +6399,18 @@ void LeftClick(ClickType clickType, int xPix, int yPix) SetHighlights(fromX, fromY, toX, toY); if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { // [HGM] super: promotion to captured piece selected from holdings - ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX]; - promotionChoice = TRUE; + ChessSquare p = boards[currentMove][fromY][fromX]; + promotionChoice = 1; + CopyBoard(promoBoard, boards[currentMove]); // kludge follows to temporarily execute move on display, without promoting yet - boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank - boards[currentMove][toY][toX] = p; - DrawPosition(FALSE, boards[currentMove]); - boards[currentMove][fromY][fromX] = p; // take back, but display stays - boards[currentMove][toY][toX] = q; + promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank + promoBoard[toY][toX] = p; + DrawPosition(FALSE, promoBoard); DisplayMessage("Click in holdings to choose piece", ""); return; } - PromotionPopUp(); + CopyBoard(promoBoard, boards[currentMove]); + PromoPopUp(boards[currentMove][fromY][fromX]); } else { UserMoveEvent(fromX, fromY, toX, toY, promoChoice); if (!appData.highlightLastMove || gotPremove) ClearHighlights(); @@ -5935,6 +6424,78 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } +int RightClick(ClickType action, int x, int y, int *xx, int *yy) +{ // front-end-free part taken out of PieceMenuPopup + int whichMenu; int xSqr, ySqr; + + if(seekGraphUp) { // [HGM] seekgraph + if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss + if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit + return -2; + } + + xSqr = EventToSquare(x, BOARD_WIDTH); + ySqr = EventToSquare(y, BOARD_HEIGHT); + if (flipView) + xSqr = BOARD_WIDTH - 1 - xSqr; + else + ySqr = BOARD_HEIGHT - 1 - ySqr; + if(promotionChoice == 3 && action == Release) { + EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY); + fromX = fromY = -1; + promotionChoice = 0; + return -1; + } + if (action == Release) 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; + case IcsObserving: + if(!appData.icsEngineAnalyze) return -1; + case IcsPlayingWhite: + case IcsPlayingBlack: + if(!appData.zippyPlay) goto noZip; + case AnalyzeMode: + case AnalyzeFile: + case MachinePlaysWhite: + case MachinePlaysBlack: + case TwoMachinesPlay: // [HGM] pv: use for showing PV + if (!appData.dropMenu) { + LoadPV(x, y); + return 2; // flag front-end to grab mouse events + } + if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode || + gameMode == AnalyzeFile || gameMode == IcsObserving) return -1; + case EditGame: + noZip: + if (xSqr < 0 || ySqr < 0) return -1; + if (!appData.dropMenu || appData.testLegality && + gameInfo.variant != VariantBughouse && + gameInfo.variant != VariantCrazyhouse) return -1; + whichMenu = 1; // drop menu + break; + default: + return -1; + } + + if (((*xx = xSqr) < 0) || + ((*yy = ySqr) < 0)) { + *xx = *yy = -1; + return -1; + } + + fromX = *xx; fromY = *yy; + if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu + + return whichMenu; +} + void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats ) { // char * hint = lastHint; @@ -5961,6 +6522,361 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp SetProgramStats( &stats ); } +int +Adjudicate(ChessProgramState *cps) +{ // [HGM] some adjudications useful with buggy engines + // [HGM] adjudicate: made into separate routine, which now can be called after every move + // In any case it determnes if the game is a claimable draw (filling in EP_STATUS). + // Actually ending the game is now based on the additional internal condition canAdjudicate. + // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed. + int k, count = 0; static int bare = 1; + ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first)); + Boolean canAdjudicate = !appData.icsActive; + + // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops + if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + if( appData.testLegality ) + { /* [HGM] Some more adjudications for obstinate engines */ + int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, + NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0, + NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; + static int moveCount = 6; + ChessMove result; + char *reason = NULL; + + + /* Count what is on board. */ + for(i=0; i 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, + "Xboard adjudication: Bare king", GE_XBOARD ); + return 1; + } + } + } else bare = 1; + + + // don't wait for engine to announce game end if we can judge ourselves + switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) { + case MT_CHECK: + if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time + int i, checkCnt = 0; // (should really be done by making nr of checks part of game state) + for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) { + if(MateTest(boards[i], PosFlags(i)) == MT_CHECK) + checkCnt++; + if(checkCnt >= 2) { + reason = "Xboard adjudication: 3rd check"; + boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; + break; + } + } + } + case MT_NONE: + default: + break; + case MT_STALEMATE: + case MT_STAINMATE: + reason = "Xboard adjudication: Stalemate"; + if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt + boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw + if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: + boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win + else if(gameInfo.variant == VariantSuicide) // in suicide it depends + boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE : + ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? + EP_CHECKMATE : EP_WINS); + else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) + boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses + } + break; + case MT_CHECKMATE: + reason = "Xboard adjudication: Checkmate"; + boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); + break; + } + + switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) { + case EP_STALEMATE: + result = GameIsDrawn; break; + case EP_CHECKMATE: + result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break; + case EP_WINS: + result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break; + default: + result = (ChessMove) 0; + } + 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; + } + + /* Next absolutely insufficient mating material. */ + if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible + (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || + NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color + { /* KBK, KNK, KK of KBKB with like Bishops */ + + /* always flag draws, for judging claims */ + boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW; + + if(canAdjudicate && appData.materialDraws) { + /* but only adjudicate them if adjudication enabled */ + if(engineOpponent) { + 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; + } + } + + /* Then some trivial draws (only adjudicate, cannot be claimed) */ + if(NrPieces == 4 && + ( NrWR == 1 && NrBR == 1 /* KRKR */ + || NrWQ==1 && NrBQ==1 /* KQKQ */ + || NrWN==2 || NrBN==2 /* KNNK */ + || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ + ) ) { + if(canAdjudicate && --moveCount < 0 && appData.trivialDraws) + { /* if the first 3 moves do not show a tactical win, declare draw */ + if(engineOpponent) { + 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; + } + } else moveCount = 6; + } + } + + if (appData.debugMode) { int i; + fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", + forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS], + appData.drawRepeats); + for( i=forwardMostMove; i>=backwardMostMove; i-- ) + fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]); + + } + + // Repetition draws and 50-move rule can be applied independently of legality testing + + /* Check for rep-draws */ + count = 0; + for(k = forwardMostMove-2; + k>=backwardMostMove && k>=forwardMostMove-100 && + (signed char)boards[k][EP_STATUS] < EP_UNKNOWN && + (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE; + k-=2) + { int rights=0; + if(CompareBoards(boards[k], boards[forwardMostMove])) { + /* compare castling rights */ + if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] && + (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) ) + rights++; /* King lost rights, while rook still had them */ + if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */ + if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] || + boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] ) + rights++; /* but at least one rook lost them */ + } + if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] && + (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) ) + rights++; + if( boards[forwardMostMove][CASTLING][5] != NoRights ) { + if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] || + boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] ) + rights++; + } + if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2 + && appData.drawRepeats > 1) { + /* adjudicate after user-specified nr of repeats */ + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + // [HGM] xiangqi: check for forbidden perpetuals + int m, ourPerpetual = 1, hisPerpetual = 1; + for(m=forwardMostMove; m>k; m-=2) { + if(MateTest(boards[m], PosFlags(m)) != MT_CHECK) + ourPerpetual = 0; // the current mover did not always check + if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK) + hisPerpetual = 0; // the opponent did not always check + } + if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", + ourPerpetual, hisPerpetual); + if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual checking", GE_XBOARD ); + return 1; + } + if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet + break; // (or we would have caught him before). Abort repetition-checking loop. + // Now check for perpetual chases + if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase + hisPerpetual = PerpetualChase(k, forwardMostMove); + ourPerpetual = PerpetualChase(k+1, forwardMostMove); + if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual chasing", GE_XBOARD ); + return 1; + } + if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet + break; // Abort repetition-checking loop. + } + // if neither of us is checking or chasing all the time, or both are, it is draw + } + GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); + return 1; + } + if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ + boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW; + } + } + + /* Now we test for 50-move draws. Determine ply count */ + count = forwardMostMove; + /* look for last irreversble move */ + while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove ) + count--; + /* if we hit starting position, add initial plies */ + if( count == backwardMostMove ) + count -= initialRulePlies; + count = forwardMostMove - count; + if( count >= 100) + boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW; + /* this is used to judge if draw claims are legal */ + if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { + if(engineOpponent) { + 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; + } + + /* if draw offer is pending, treat it as a draw claim + * when draw condition present, to allow engines a way to + * claim draws before making their move to avoid a race + * condition occurring after their move + */ + if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) { + char *p = NULL; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW) + p = "Draw claim: 50-move rule"; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW) + p = "Draw claim: 3-fold repetition"; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW) + p = "Draw claim: insufficient mating material"; + if( p != NULL && canAdjudicate) { + if(engineOpponent) { + 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; + } + } + + if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { + if(engineOpponent) { + 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; + } + return 0; +} + char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; @@ -5989,7 +6905,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) cps->bookSuspend = FALSE; // after a 'go' we are never suspended } else { // 'go' might be sent based on 'firstMove' after this routine returns if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return - SendToProgram("go\n", cps); + SendToProgram("go\n", cps); cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss } return bookHit; // notify caller of hit, so it can take action to send move to opponent @@ -6193,39 +7109,11 @@ 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--; -#if ZIPPY - if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && - first.initDone) { - SendMoveToICS(moveType, fromX, fromY, toX, toY); - ics_user_moved = 1; - if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ - char buf[3*MSG_SIZ]; - - sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", - programStats.score / 100., - programStats.depth, - programStats.time / 100., - (unsigned int)programStats.nodes, - (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.), - programStats.movelist); - SendToICS(buf); -if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes); - } - } -#endif /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(machineMove, currentMoveString); strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); - /* [AS] Save move info and clear stats for next move */ - pvInfoList[ forwardMostMove ].score = programStats.score; - pvInfoList[ forwardMostMove ].depth = programStats.depth; - pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats - ClearProgramStats(); - thinkOutput[0] = NULLCHAR; - hiddenThinkOutputState = 0; - MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ @@ -6249,344 +7137,49 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if( count >= adjudicateLossPlies ) { ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication", + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication", GE_XBOARD ); return; } } - if( gameMode == TwoMachinesPlay ) { - // [HGM] some adjudications useful with buggy engines - int k, count = 0; static int bare = 1; - if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { - - - if( appData.testLegality ) - { /* [HGM] Some more adjudications for obstinate engines */ - int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, - NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0, - NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; - static int moveCount = 6; - ChessMove result; - char *reason = NULL; - - /* Count what is on board. */ - for(i=0; iother); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, - "Xboard adjudication: King destroyed", GE_XBOARD ); - return; - } - } - - /* Bare King in Shatranj (loses) or Losers (wins) */ - if( NrW == 1 || NrPieces - NrW == 1) { - if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first) - boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable - if(appData.checkMates) { - SendMoveToProgram(forwardMostMove-1, cps->other); // 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; - } - } else - if( gameInfo.variant == VariantShatranj && --bare < 0) - { /* bare King */ - boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm - if(appData.checkMates) { - /* but only adjudicate if adjudication enabled */ - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, - "Xboard adjudication: Bare king", GE_XBOARD ); - return; - } - } - } else bare = 1; - - - // don't wait for engine to announce game end if we can judge ourselves - switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) { - case MT_CHECK: - if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time - int i, checkCnt = 0; // (should really be done by making nr of checks part of game state) - for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) { - if(MateTest(boards[i], PosFlags(i)) == MT_CHECK) - checkCnt++; - if(checkCnt >= 2) { - reason = "Xboard adjudication: 3rd check"; - boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; - break; - } - } - } - case MT_NONE: - default: - break; - case MT_STALEMATE: - case MT_STAINMATE: - reason = "Xboard adjudication: Stalemate"; - if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt - boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw - if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: - boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win - else if(gameInfo.variant == VariantSuicide) // in suicide it depends - boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE : - ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? - EP_CHECKMATE : EP_WINS); - else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) - boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses - } - break; - case MT_CHECKMATE: - reason = "Xboard adjudication: Checkmate"; - boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); - break; - } - - switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) { - case EP_STALEMATE: - result = GameIsDrawn; break; - case EP_CHECKMATE: - result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break; - case EP_WINS: - result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break; - default: - result = (ChessMove) 0; - } - if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( result, reason, GE_XBOARD ); - return; - } - - /* Next absolutely insufficient mating material. */ - if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible - (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || - NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color - { /* KBK, KNK, KK of KBKB with like Bishops */ - - /* always flag draws, for judging claims */ - boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW; - - if(appData.materialDraws) { - /* but only adjudicate them if adjudication enabled */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* 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; - } - } - - /* Then some trivial draws (only adjudicate, cannot be claimed) */ - if(NrPieces == 4 && - ( NrWR == 1 && NrBR == 1 /* KRKR */ - || NrWQ==1 && NrBQ==1 /* KQKQ */ - || NrWN==2 || NrBN==2 /* KNNK */ - || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ - ) ) { - if(--moveCount < 0 && appData.trivialDraws) - { /* if the first 3 moves do not show a tactical win, declare draw */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); - return; - } - } else moveCount = 6; - } - } - - if (appData.debugMode) { int i; - fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", - forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS], - appData.drawRepeats); - for( i=forwardMostMove; i>=backwardMostMove; i-- ) - fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]); - +#if ZIPPY + if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && + first.initDone) { + if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { + SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS + SendToICS("draw "); + SendMoveToICS(moveType, fromX, fromY, toX, toY); } + SendMoveToICS(moveType, fromX, fromY, toX, toY); + ics_user_moved = 1; + if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ + char buf[3*MSG_SIZ]; - /* Check for rep-draws */ - count = 0; - for(k = forwardMostMove-2; - k>=backwardMostMove && k>=forwardMostMove-100 && - (signed char)boards[k][EP_STATUS] < EP_UNKNOWN && - (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE; - k-=2) - { int rights=0; - if(CompareBoards(boards[k], boards[forwardMostMove])) { - /* compare castling rights */ - if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] && - (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) ) - rights++; /* King lost rights, while rook still had them */ - if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */ - if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] || - boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] ) - rights++; /* but at least one rook lost them */ - } - if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] && - (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) ) - rights++; - if( boards[forwardMostMove][CASTLING][5] != NoRights ) { - if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] || - boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] ) - rights++; - } - if( rights == 0 && ++count > appData.drawRepeats-2 - && appData.drawRepeats > 1) { - /* adjudicate after user-specified nr of repeats */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - if(gameInfo.variant == VariantXiangqi && appData.testLegality) { - // [HGM] xiangqi: check for forbidden perpetuals - int m, ourPerpetual = 1, hisPerpetual = 1; - for(m=forwardMostMove; m>k; m-=2) { - if(MateTest(boards[m], PosFlags(m)) != MT_CHECK) - ourPerpetual = 0; // the current mover did not always check - if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK) - hisPerpetual = 0; // the opponent did not always check - } - if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", - ourPerpetual, hisPerpetual); - if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual checking", GE_XBOARD ); - return; - } - if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet - break; // (or we would have caught him before). Abort repetition-checking loop. - // Now check for perpetual chases - if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase - hisPerpetual = PerpetualChase(k, forwardMostMove); - ourPerpetual = PerpetualChase(k+1, forwardMostMove); - if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual chasing", GE_XBOARD ); - return; - } - if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet - break; // Abort repetition-checking loop. - } - // if neither of us is checking or chasing all the time, or both are, it is draw - } - GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); - return; - } - if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ - boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW; - } - } - - /* Now we test for 50-move draws. Determine ply count */ - count = forwardMostMove; - /* look for last irreversble move */ - while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove ) - count--; - /* if we hit starting position, add initial plies */ - if( count == backwardMostMove ) - count -= initialRulePlies; - count = forwardMostMove - count; - if( count >= 100) - boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW; - /* this is used to judge if draw claims are legal */ - if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); - return; - } - - /* if draw offer is pending, treat it as a draw claim - * when draw condition present, to allow engines a way to - * claim draws before making their move to avoid a race - * condition occurring after their move - */ - if( cps->other->offeredDraw || cps->offeredDraw ) { - char *p = NULL; - if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW) - p = "Draw claim: 50-move rule"; - if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW) - p = "Draw claim: 3-fold repetition"; - if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW) - p = "Draw claim: insufficient mating material"; - if( p != NULL ) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - GameEnds( GameIsDrawn, p, GE_XBOARD ); - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - return; - } - } - - - if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - - GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", + programStats.score / 100., + programStats.depth, + programStats.time / 100., + (unsigned int)programStats.nodes, + (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.), + programStats.movelist); + SendToICS(buf); +if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes); + } + } +#endif - return; - } - } + /* [AS] Save move info and clear stats for next move */ + pvInfoList[ forwardMostMove-1 ].score = programStats.score; + pvInfoList[ forwardMostMove-1 ].depth = programStats.depth; + pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats + ClearProgramStats(); + thinkOutput[0] = NULLCHAR; + hiddenThinkOutputState = 0; bookHit = NULL; if (gameMode == TwoMachinesPlay) { @@ -7577,6 +8170,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; /* [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 */ @@ -7623,7 +8217,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier) + if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk) && promoChar != 0) promoChar = PieceToChar(WhiteFerz); if (fromX == toX && fromY == toY) return; @@ -7674,7 +8268,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; } else if (board[fromY][fromX] == WhitePawn - && toY == BOARD_HEIGHT-1 + && toY >= BOARD_HEIGHT-promoRank && gameInfo.variant != VariantXiangqi ) { /* white pawn promotion */ @@ -7738,13 +8332,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; } else if (board[fromY][fromX] == BlackPawn - && toY == 0 + && toY < promoRank && gameInfo.variant != VariantXiangqi ) { /* black pawn promotion */ - board[0][toX] = CharToPiece(ToLower(promoChar)); - if (board[0][toX] == EmptySquare) { - board[0][toX] = BlackQueen; + 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 */ @@ -10994,76 +11588,84 @@ MachineWhiteEvent() void MachineBlackEvent() { - char buf[MSG_SIZ]; - char *bookHit = NULL; - - if (appData.noChessProgram || (gameMode == MachinePlaysBlack)) - return; - - - if (gameMode == PlayFromGameFile || - gameMode == TwoMachinesPlay || - gameMode == Training || - gameMode == AnalyzeMode || - gameMode == EndOfGame) - EditGameEvent(); - - if (gameMode == EditPosition) - EditPositionDone(TRUE); - - if (WhiteOnMove(currentMove)) { - DisplayError(_("It is not Black's turn"), 0); - return; + char buf[MSG_SIZ]; + char *bookHit = NULL; + + if (appData.noChessProgram || (gameMode == MachinePlaysBlack)) + return; + + + if (gameMode == PlayFromGameFile + || gameMode == TwoMachinesPlay + || gameMode == Training + || gameMode == AnalyzeMode + || gameMode == EndOfGame) + EditGameEvent(); + + if (gameMode == EditPosition) + EditPositionDone(TRUE); + + if (WhiteOnMove(currentMove)) + { + DisplayError(_("It is not Black's turn"), 0); + return; } - - if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) - ExitAnalyzeMode(); - - if (gameMode == EditGame || gameMode == AnalyzeMode || - gameMode == AnalyzeFile) - TruncateGame(); - - ResurrectChessProgram(); /* in case it isn't running */ - gameMode = MachinePlaysBlack; - pausing = FALSE; - ModeHighlight(); - SetGameInfo(); - sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black); - DisplayTitle(buf); - if (first.sendName) { + + if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) + ExitAnalyzeMode(); + + if (gameMode == EditGame || gameMode == AnalyzeMode + || gameMode == AnalyzeFile) + TruncateGame(); + + ResurrectChessProgram(); /* in case it isn't running */ + gameMode = MachinePlaysBlack; + pausing = FALSE; + ModeHighlight(); + SetGameInfo(); + sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black); + DisplayTitle(buf); + if (first.sendName) + { sprintf(buf, "name %s\n", gameInfo.white); SendToProgram(buf, &first); } - if (first.sendTime) { - if (first.useColors) { - SendToProgram("white\n", &first); /*gnu kludge*/ - } + if (first.sendTime) + { + if (first.useColors) + { + SendToProgram("white\n", &first); /*gnu kludge*/ + } SendTimeRemaining(&first, FALSE); } - if (first.useColors) { + if (first.useColors) + { SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately } - bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move - SetMachineThinkingEnables(); - first.maybeThinking = TRUE; - StartClocks(); - - if (appData.autoFlipView && flipView) { + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move + SetMachineThinkingEnables(); + first.maybeThinking = TRUE; + StartClocks(); + + if (appData.autoFlipView && flipView) + { flipView = !flipView; DrawPosition(FALSE, NULL); DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged; } - if(bookHit) { // [HGM] book: simulate book reply - static char bookMove[MSG_SIZ]; // a bit generous? - - programStats.nodes = programStats.depth = programStats.time = - programStats.score = programStats.got_only_move = 0; - sprintf(programStats.movelist, "%s (xbook)", bookHit); - - strcpy(bookMove, "move "); - strcat(bookMove, bookHit); - HandleMachineMove(bookMove, &first); + if(bookHit) + { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.nodes = programStats.depth = programStats.time + = programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); } + return; } @@ -11361,7 +11963,6 @@ EditGameEvent() SetGameInfo(); } - void EditPositionEvent() { @@ -11378,7 +11979,14 @@ EditPositionEvent() SetGameInfo(); if (currentMove > 0) CopyBoard(boards[0], boards[currentMove]); - + { int i, r, f; // [HGM] editrights: take note of existing rights + for(r=0; r=0; f--) + if(rightsBoard[0][f] == 2) { if(kf != NoRights) err=10; boards[0][CASTLING][2] = kf = f; } + for(f=BOARD_RGHT-1; f>=0; f--) + if(rightsBoard[0][f] == 1) { err++; boards[0][CASTLING][f=0; f--) + if(rightsBoard[BOARD_HEIGHT-1][f] == 2) { if(kf != NoRights) err=10; boards[0][CASTLING][5] = kf = f; } + for(f=BOARD_RGHT-1; f>=0; f--) + if(rightsBoard[BOARD_HEIGHT-1][f] == 1) { err++; boards[0][CASTLING][3+(f nrCastlingRights) DisplayError("unclear castling rights", 0); + boards[0][EP_STATUS] = EP_NONE; +#if 0 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1; if(boards[0][0][BOARD_WIDTH>>1] == king) { boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights; @@ -11420,6 +12043,7 @@ EditPositionDone(Boolean fakeRights) boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights; boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights; } else boards[0][CASTLING][5] = NoRights; +#endif } SendToProgram("force\n", &first); if (blackPlaysFirst) { @@ -11517,6 +12141,7 @@ EditPositionMenuEvent(selection, x, y) { char buf[MSG_SIZ]; ChessSquare piece = boards[0][y][x]; + int rights = 0; // [HGM] editrights: most new pieces get no castling rights if (gameMode != EditPosition && gameMode != IcsExamining) return; @@ -11540,6 +12165,7 @@ EditPositionMenuEvent(selection, x, y) } } else { boards[0][y][x] = p; + rightsBoard[y][x] = 0; // [HGM] editrights: clear all castling rights } } } @@ -11557,6 +12183,18 @@ EditPositionMenuEvent(selection, x, y) SetBlackToPlayEvent(); break; + case NoRights: + rightsBoard[y][x] = 0; + break; + + case GrantRights: + { ChessSquare p = boards[0][y][x]; + rightsBoard[y][x] = 1; + if(p == WhiteKing || p == WhiteUnicorn || p == BlackKing || p == BlackUnicorn) + rightsBoard[y][x] = 2; + } + break; + case EmptySquare: if (gameMode == IcsExamining) { if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings @@ -11598,16 +12236,29 @@ EditPositionMenuEvent(selection, x, y) case BlackQueen: if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || - gameInfo.variant == VariantCourier ) + gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk ) selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); goto defaultlabel; + case WhiteRook: // [HGM] editrights: corner Rooks get castling rights by default + if(y == 0 && (x == BOARD_LEFT || x == BOARD_RGHT-1)) rights = 1; + goto defaultlabel; + + case BlackRook: + if(y == BOARD_HEIGHT-1 && (x == BOARD_LEFT || x == BOARD_RGHT-1)) rights = 1; + goto defaultlabel; + case WhiteKing: case BlackKing: if(gameInfo.variant == VariantXiangqi) selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir); if(gameInfo.variant == VariantKnightmate) selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn); + case WhiteUnicorn: + case BlackUnicorn: + if(y == (selection <= WhiteKing ? 0 : BOARD_HEIGHT-1) && (x == BOARD_WIDTH>>1)) + rights = 2; // [HGM] editrights: King on right-center file gets rights default: defaultlabel: if (gameMode == IcsExamining) { @@ -11620,18 +12271,19 @@ EditPositionMenuEvent(selection, x, y) int n; if(x == BOARD_LEFT-2 && selection >= BlackPawn) { n = PieceToNumber(selection - BlackPawn); - if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; } + if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; } boards[0][BOARD_HEIGHT-1-n][0] = selection; boards[0][BOARD_HEIGHT-1-n][1]++; } else if(x == BOARD_RGHT+1 && selection < BlackPawn) { n = PieceToNumber(selection); - if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; } + if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; } boards[0][n][BOARD_WIDTH-1] = selection; boards[0][n][BOARD_WIDTH-2]++; } } else boards[0][y][x] = selection; + rightsBoard[y][x] = rights; // [HGM] editrights: set default rights of created piece DrawPosition(TRUE, boards[0]); } break; @@ -11793,6 +12445,7 @@ DrawEvent() SendToICS(ics_prefix); SendToICS("draw\n"); + userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play } else if (cmailMsgLoaded) { if (currentMove == cmailOldMove && commentList[cmailOldMove] != NULL && @@ -14023,7 +14676,7 @@ PositionToFEN(move, overrideCastling) } if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { + gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { /* En passant target square */ if (move > backwardMostMove) { fromX = moveList[move - 1][0] - AAA; @@ -14198,10 +14851,12 @@ ParseFEN(board, blackPlaysFirst, fen) } /* assume possible unless obviously impossible */ if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights; if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights; - if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights; + if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn + && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights; if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights; if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights; - if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights; + if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn + && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights; FENrulePlies = 0; while(*p==' ') p++; @@ -14216,12 +14871,18 @@ ParseFEN(board, blackPlaysFirst, fen) (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) || ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { - char c = *p++; int whiteKingFile=-1, blackKingFile=-1; + char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights; for(i=BOARD_LEFT; i> 1; // for these variant scanning fails + if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn + && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights; + if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn + && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights; switch(c) { case'K': for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--); @@ -14229,7 +14890,7 @@ ParseFEN(board, blackPlaysFirst, fen) board[CASTLING][2] = whiteKingFile; break; case'Q': - for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i