X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=ad78134f59cdde954a0369d1ae8830f71e74ba9d;hb=d0955e6b0d0f5a00a65ed05a14fd8fa73121ab3c;hp=80356736968233e024367a769c6df8d6a32db610;hpb=efc16c67bb82730127e429d77d2f670923e827c9;p=xboard.git diff --git a/backend.c b/backend.c index 8035673..0c12671 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,8 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free + * Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -55,14 +56,32 @@ #ifdef WIN32 #include -int flock(int f, int code); -#define LOCK_EX 2 -#define SLASH '\\' + int flock(int f, int code); +# define LOCK_EX 2 +# define SLASH '\\' + +# ifdef ARC_64BIT +# define EGBB_NAME "egbbdll64.dll" +# else +# define EGBB_NAME "egbbdll.dll" +# endif #else -#include -#define SLASH '/' +# include +# define SLASH '/' + +# include +# ifdef ARC_64BIT +# define EGBB_NAME "egbbso64.so" +# else +# define EGBB_NAME "egbbso.so" +# endif + // kludge to allow Windows code in back-end by converting it to corresponding Linux code +# define CDECL +# define HMODULE void * +# define LoadLibrary(x) dlopen(x, RTLD_LAZY) +# define GetProcAddress dlsym #endif @@ -130,6 +149,7 @@ extern int gettimeofday(struct timeval *, struct timezone *); #endif #include "backendz.h" #include "evalgraph.h" +#include "engineoutput.h" #include "gettext.h" #ifdef ENABLE_NLS @@ -173,7 +193,6 @@ void GameEnds P((ChessMove result, char *resultDetails, int whosays)); void EditPositionDone P((Boolean fakeRights)); void PrintOpponents P((FILE *fp)); void PrintPosition P((FILE *fp, int move)); -void StartChessProgram P((ChessProgramState *cps)); void SendToProgram P((char *message, ChessProgramState *cps)); void SendMoveToProgram P((int moveNum, ChessProgramState *cps)); void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure, @@ -232,7 +251,6 @@ static int NonStandardBoardSize P((VariantClass v, int w, int h, int s)); #endif ChessProgramState *WhitePlayer(); -void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c int VerifyDisplayMode P(()); char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment @@ -243,6 +261,7 @@ void ics_update_width P((int new_width)); extern char installDir[MSG_SIZ]; VariantClass startVariant; /* [HGM] nicks: initial variant */ Boolean abortMatch; +int deadRanks; extern int tinyLayout, smallLayout; ChessProgramStats programStats; @@ -271,11 +290,15 @@ int chattingPartner; char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */ char lastMsg[MSG_SIZ]; +char lastTalker[MSG_SIZ]; ChessSquare pieceSweep = EmptySquare; ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */ static int initPing = -1; +int border; /* [HGM] width of board rim, needed to size seek graph */ +char bestMove[MSG_SIZ], avoidMove[MSG_SIZ]; +int solvingTime, totalTime; /* States for ics_getting_history */ #define H_FALSE 0 @@ -365,7 +388,7 @@ u64ToDouble (u64 value) by this function. */ int -PosFlags (index) +PosFlags (int index) { int flags = F_ALL_CASTLE_OK; if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE; @@ -394,9 +417,15 @@ PosFlags (index) case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; + case VariantChu: + case VariantChuChess: + case VariantLion: + flags |= F_NULL_MOVE; + break; default: break; } + if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer return flags; } @@ -415,6 +444,7 @@ char *currentDebugFile; // [HGM] debug split: to remember name char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ*10]; +char promoRestrict[MSG_SIZ]; ChessProgramState first, second, pairing; @@ -487,7 +517,7 @@ AppData appData; Board boards[MAX_MOVES]; /* [HGM] Following 7 needed for accurate legality tests: */ signed char castlingRank[BOARD_FILES]; // and corresponding ranks -signed char initialRights[BOARD_FILES]; +unsigned char initialRights[BOARD_FILES]; int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) @@ -535,8 +565,8 @@ ChessSquare KnightmateArray[2][BOARD_FILES] = { ChessSquare SpartanArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackAlfil, BlackMarshall, BlackKing, BlackDragon, - BlackDragon, BlackKing, BlackAngel, BlackAlfil } + { BlackAlfil, BlackDragon, BlackKing, BlackTower, + BlackTower, BlackKing, BlackAngel, BlackAlfil } }; ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */ @@ -662,18 +692,18 @@ ChessSquare CourierArray[2][BOARD_FILES] = { BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook } }; ChessSquare ChuArray[6][BOARD_FILES] = { - { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing, - WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance }, - { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil, - BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance }, - { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall, - WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon }, - { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel, - BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon }, - { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion, - WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon }, - { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen, - BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon } + { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing, + WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance }, + { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil, + BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance }, + { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall, + WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe }, + { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel, + BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe }, + { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion, + WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger }, + { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen, + BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger } }; #else // !(BOARD_FILES>=12) #define CourierArray CapablancaArray @@ -759,8 +789,7 @@ UnloadEngine (ChessProgramState *cps) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", cps); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(cps->pr, cps->useSigterm); + DestroyChildProcess(cps->pr, 4 + cps->useSigterm); } cps->pr = NoProc; if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which); @@ -823,11 +852,12 @@ InitEngine (ChessProgramState *cps, int n) if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ); TidyProgramName(cps->program, cps->host, cps->tidy); cps->matchWins = 0; - ASSIGN(cps->variants, appData.variant); + ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant); cps->analysisSupport = 2; /* detect */ cps->analyzing = FALSE; cps->initDone = FALSE; cps->reload = FALSE; + cps->pseudo = appData.pseudo[n]; /* New features added by Tord: */ cps->useFEN960 = FALSE; @@ -845,6 +875,7 @@ InitEngine (ChessProgramState *cps, int n) /* [HGM] debug */ cps->debug = FALSE; + cps->drawDepth = appData.drawDepth[n]; cps->supportsNPS = UNKNOWN; cps->memSize = FALSE; cps->maxCores = FALSE; @@ -928,7 +959,7 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " - "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " + "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" " "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false"; void @@ -966,6 +997,7 @@ Load (ChessProgramState *cps, int i) SwapEngines(i); ReplaceEngine(cps, i); FloatToFront(&appData.recentEngineList, engineLine); + if(gameMode == BeginningOfGame) Reset(TRUE, TRUE); return; } p = engineName; @@ -1174,6 +1206,11 @@ InitBackEnd1 () DisplayFatalError(buf, 0, 2); return; + case VariantNormal: /* definitely works! */ + if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant + safeStrCpy(engineVariant, appData.variant, MSG_SIZ); + return; + } case VariantXiangqi: /* [HGM] repetition rules not implemented */ case VariantFairy: /* [HGM] TestLegality definitely off! */ case VariantGothic: /* [HGM] should work */ @@ -1186,7 +1223,6 @@ InitBackEnd1 () case VariantFalcon: /* [HGM] untested */ case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!) offboard interposition not understood */ - case VariantNormal: /* definitely works! */ case VariantWildCastle: /* pieces not automatically shuffled */ case VariantNoCastle: /* pieces not automatically shuffled */ case VariantFischeRandom: /* [HGM] works and shuffles pieces */ @@ -1551,7 +1587,7 @@ MatchEvent (int mode) } matchMode = mode; matchGame = roundNr = 1; - first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches + first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches NextMatchGame(); } @@ -1564,6 +1600,23 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err, len; + if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine + !strcmp(appData.variant, "normal") && // no explicit variant request + appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested + !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine + !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960 + char c, *q = first.variants, *p = strchr(q, ','); + if(p) *p = NULLCHAR; + if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however + int w, h, s; + if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any) + appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1; + ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine + Reset(TRUE, FALSE); // and re-initialize + } + if(p) *p = ','; + } + InitChessProgram(&first, startedFromSetupPosition); if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */ @@ -1690,7 +1743,13 @@ InitBackEnd3 P((void)) if(!blackPlaysFirst) { startedFromPositionFile = TRUE; CopyBoard(filePosition, boards[0]); + CopyBoard(initialPosition, boards[0]); } + } else if(*appData.fen != NULLCHAR) { + if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) { + startedFromPositionFile = TRUE; + Reset(TRUE, TRUE); + } } if (initialMode == AnalyzeMode) { if (appData.noChessProgram) { @@ -1930,7 +1989,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count DisplayFatalError(_("Error reading from keyboard"), error, 1); } else if (gotEof++ > 0) { RemoveInputSource(isr); - DisplayFatalError(_("Got end of file from keyboard"), 0, 0); + DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end } } @@ -2060,14 +2119,14 @@ StringToVariant (char *e) int wnum = -1; VariantClass v = VariantNormal; int i, found = FALSE; - char buf[MSG_SIZ]; + char buf[MSG_SIZ], c; int len; if (!e) return v; /* [HGM] skip over optional board-size prefixes */ - if( sscanf(e, "%dx%d_", &i, &i) == 2 || - sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) { + if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 || + sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) { while( *e++ != '_'); } @@ -2077,7 +2136,7 @@ StringToVariant (char *e) } else for (i=0; i= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue; + if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue; v = (VariantClass) i; found = TRUE; break; @@ -2673,8 +2732,8 @@ DrawSeekGraph () { int i; if(!seekGraphUp) return FALSE; - h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap; - w = BOARD_WIDTH * (squareSize + lineGap) + lineGap; + h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border; + w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border; DrawSeekBackground(0, 0, w, h); DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin); @@ -2793,7 +2852,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int int backup; /* [DM] For zippy color lines */ char *p; char talker[MSG_SIZ]; // [HGM] chat - int channel; + int channel, collective=0; connectionAlive = TRUE; // [HGM] alive: I think, therefore I am... @@ -3035,8 +3094,18 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int char mess[MSG_SIZ]; snprintf(mess, MSG_SIZ, "%s%s", talker, parse); OutputChatMessage(chattingPartner, mess); + if(collective == 1) { // broadcasted talk also goes to private chatbox of talker + int p; + talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter + for(p=0; p= 0) // channel broadcast; look if there is a chatbox for this channel for(p=0; p= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) { talker[0] = '['; strcat(talker, "] "); - Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE); + Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE); chattingPartner = p; break; } } else if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox for(p=0; p') {// shout, c-shout or it; look if there is a 'shouts' chatbox if(buf[i-8] == '-' && buf[i-3] == 't') for(p=0; p') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); } else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); } @@ -3296,18 +3370,23 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int } if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual for(p=0; p 0 && buf[oldi-1] == '\n') oldi--; - if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); started = STARTED_COMMENT; parse_pos = 0; parse[0] = NULLCHAR; savingComment = 3 + chattingPartner; // counts as TRUE - suppressKibitz = TRUE; - continue; + if(collective == 3) i = oldi; else { + suppressKibitz = TRUE; + if(oldi > 0 && buf[oldi-1] == '\n') oldi--; + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + continue; + } } } // [HGM] chat: end of patch @@ -3317,7 +3396,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int #if ZIPPY if (loggedOn == TRUE) if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) || - (appData.zippyPlay && ZippyMatch(buf, &backup))); + (appData.zippyPlay && ZippyMatch(buf, &backup))) + ; #endif } // [DM] 'else { ' deleted if ( @@ -3391,7 +3471,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int parse[parse_pos] = NULLCHAR; started = STARTED_COMMENT; savingComment = TRUE; - } else { + } else if(collective != 3) { started = STARTED_CHATTER; savingComment = FALSE; } @@ -4091,6 +4171,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int fprintf(debugFP, "Sending premove:\n"); SendToICS(str); } else if (gotPremove) { + int oldFMM = forwardMostMove; gotPremove = 0; ClearPremoveHighlights(); if (appData.debugMode) @@ -4098,6 +4179,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int UserMoveEvent(premoveFromX, premoveFromY, premoveToX, premoveToY, premovePromoChar); + if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move + if(moveList[oldFMM-1][1] != '@') + SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE, + moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE); + else // (drop) + SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE); + } } } @@ -4796,7 +4884,7 @@ ParseBoard12 (char *string) default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[moveNum - 1], "+"); break; case MT_CHECKMATE: @@ -4818,13 +4906,13 @@ ParseBoard12 (char *string) if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover if(old == new) continue; - if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones + if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones else if(new == WhiteWazir || new == BlackWazir) { if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon) - boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion + boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion else boards[moveNum][k][j] = old; // preserve type of Gold } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!) - boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece + boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece } } else { /* Move from ICS was illegal!? Punt. */ @@ -5058,8 +5146,7 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) /* Added by Tord: Send castle moves in "O-O" in FRC games if required by * the engine. It would be nice to have a better way to identify castle * moves here. */ - if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) - && cps->useOOCastle) { + if(appData.fischerCastling && cps->useOOCastle) { int fromX = moveList[moveNum][0] - AAA; int fromY = moveList[moveNum][1] - ONE; int toX = moveList[moveNum][2] - AAA; @@ -5074,10 +5161,27 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) else SendToProgram(moveList[moveNum], cps); } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square - snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves - moveList[moveNum][5], moveList[moveNum][6] - '0', - moveList[moveNum][5], moveList[moveNum][6] - '0', - moveList[moveNum][2], moveList[moveNum][3] - '0'); + char *m = moveList[moveNum]; + static char c[2]; + *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar + if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves + m[2], m[3] - '0', + m[5], m[6] - '0', + m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0'); + else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix + *c = m[9]; if(*c == '\n') *c = NULLCHAR; + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves + m[7], m[8] - '0', + m[7], m[8] - '0', + m[5], m[6] - '0', + m[5], m[6] - '0', + m[2], m[3] - '0', c); + } else + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves + m[5], m[6] - '0', + m[5], m[6] - '0', + m[2], m[3] - '0', c); SendToProgram(buf, cps); } else if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed @@ -5154,7 +5258,7 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char case WhitePromotion: case BlackPromotion: if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) + gameInfo.variant == VariantMakruk) snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, PieceToChar(WhiteFerz)); @@ -5260,10 +5364,11 @@ UploadGameEvent () SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n"); } -int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove +int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove +int legNr = 1; void -CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7]) +CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9]) { if (rf == DROP_RANK) { if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass @@ -5273,10 +5378,17 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char if (promoChar == 'x' || promoChar == NULLCHAR) { sprintf(move, "%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt); - if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); + if(killX >= 0 && killY >= 0) { + sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); + if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y); + } } else { sprintf(move, "%c%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); + if(killX >= 0 && killY >= 0) { + sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); + if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar); + } } } } @@ -5299,12 +5411,22 @@ int dragging; static ClickType lastClickType; int +PieceInString (char *s, ChessSquare piece) +{ + char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece); + while((p = strchr(s, ID))) { + if(!suffix || p[1] == suffix) return TRUE; + s = p; + } + return FALSE; +} + +int Partner (ChessSquare *p) { // change piece into promotion partner if one shogi-promotes to the other - int stride = gameInfo.variant == VariantChu ? 22 : 11; - ChessSquare partner; - partner = (*p/stride & 1 ? *p - stride : *p + stride); + ChessSquare partner = promoPartner[*p]; if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0; + if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX]; *p = partner; return 1; } @@ -5324,11 +5446,13 @@ Sweep (int step) if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step; if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap else if((int)promoSweep == -1) promoSweep = WhiteKing; - else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn; - else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing; + else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn; + else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing; if(!step) step = -1; - } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || + } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other + promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it + promoSweep == pawn || appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess && (promoSweep == WhiteLion || promoSweep == BlackLion))); if(toX >= 0) { @@ -5452,11 +5576,18 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro case BlackASideCastleFR: /* End of code added by Tord */ case IllegalMove: /* bug or odd chess variant */ + if(currentMoveString[1] == '@') { // illegal drop + *fromX = WhiteOnMove(moveNum) ? + (int) CharToPiece(ToUpper(currentMoveString[0])) : + (int) CharToPiece(ToLower(currentMoveString[0])); + goto drop; + } *fromX = currentMoveString[0] - AAA; *fromY = currentMoveString[1] - ONE; *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; *promoChar = currentMoveString[4]; + if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)]; if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT || *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) { if (appData.debugMode) { @@ -5478,6 +5609,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro *fromX = *moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); + drop: *fromY = DROP_RANK; *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; @@ -5573,21 +5705,26 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) } int -MultiPV (ChessProgramState *cps) +MultiPV (ChessProgramState *cps, int kind) { // check if engine supports MultiPV, and if so, return the number of the option that sets it int i; - for(i=0; inrOptions; i++) - if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin) - return i; + for(i=0; inrOptions; i++) { + char *s = cps->option[i].name; + if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i; + if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV") + && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2; + } return -1; } Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game +static int multi, pv_margin; +static ChessProgramState *activeCps; Boolean LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) { - int startPV, multi, lineStart, origIndex = index; + int startPV, lineStart, origIndex = index; char *p, buf2[MSG_SIZ]; ChessProgramState *cps = (pane ? &second : &first); @@ -5601,21 +5738,32 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) do{ while(buf[index] && buf[index] != '\n') index++; } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line buf[index] = 0; - if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) { - int n = cps->option[multi].value; - if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++; + if(lineStart == 0 && gameMode == AnalyzeMode) { + int n = 0; + if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++; + if(n == 0) { // click not on "fewer" or "more" + if((multi = -2 - MultiPV(cps, 2)) >= 0) { + pv_margin = cps->option[multi].value; + activeCps = cps; // non-null signals margin adjustment + } + } else if((multi = MultiPV(cps, 1)) >= 0) { + n += cps->option[multi].value; if(n < 1) n = 1; snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n); if(cps->option[multi].value != n) SendToProgram(buf2, cps); cps->option[multi].value = n; *start = *end = 0; return FALSE; + } } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked ExcludeClick(origIndex - lineStart); return FALSE; + } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked + Collapse(origIndex - lineStart); + return FALSE; } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; - extendGame = (gameMode == AnalyzeMode && appData.autoExtend); + extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5); return TRUE; } @@ -5653,6 +5801,16 @@ void UnLoadPV () { int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded! + if(activeCps) { + if(pv_margin != activeCps->option[multi].value) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin); + SendToProgram(buf, activeCps); + activeCps->option[multi].value = pv_margin; + } + activeCps = NULL; + return; + } if(endPV < 0) return; if(appData.autoCopyPV) CopyFENToClipboard(); endPV = -1; @@ -5681,6 +5839,17 @@ MovePV (int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15); + if(activeCps) { // adjusting engine's multi-pv margin + if(x > lastX) pv_margin++; else + if(x < lastX) pv_margin -= (pv_margin > 0); + if(x != lastX) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "margin = %d", pv_margin); + DisplayMessage(buf, ""); + } + lastX = x; + return; + } // 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) < threshold && abs(y - lastY) < threshold) return; @@ -5862,23 +6031,70 @@ SetUpShuffle (Board board, int number) } int -SetCharTable (char *table, const char * map) +ptclen (const char *s, char *escapes) +{ + int n = 0; + if(!*escapes) return strlen(s); + while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++; + return n; +} + +int +SetCharTableEsc (unsigned char *table, const char * map, char * escapes) /* [HGM] moved here from winboard.c because of its general usefulness */ /* Basically a safe strcpy that uses the last character as King */ { int result = FALSE; int NrPieces; + unsigned char partner[EmptySquare]; - if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare + if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare && NrPieces >= 12 && !(NrPieces&1)) { - int i; /* [HGM] Accept even length from 12 to 34 */ + int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */ for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.'; - for( i=0; ialphaRank) { /* [HGM] shogi: translate coords */ message[1] = BOARD_RGHT - 1 - j + '1'; @@ -6307,12 +6535,12 @@ SendBoard (ChessProgramState *cps, int moveNum) && ((int) *bp >= (int) BlackPawn)) { if(j == BOARD_LEFT-2) snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]); - else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)), - AAA + j, ONE + i); + else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)), + AAA + j, ONE + i - '0'); if(message[0] == '+' || message[0] == '~') { - snprintf(message, MSG_SIZ,"%c%c%c+\n", - PieceToChar((ChessSquare)(DEMOTED *bp)), - AAA + j, ONE + i); + snprintf(message, MSG_SIZ,"%c%c%d+\n", + PieceToChar((ChessSquare)(DEMOTED(*bp))), + AAA + j, ONE + i - '0'); } if(cps->alphaRank) { /* [HGM] shogi: translate coords */ message[1] = BOARD_RGHT - 1 - j + '1'; @@ -6442,8 +6670,10 @@ DefaultPromoChoice (int white) { ChessSquare result; if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) + gameInfo.variant == VariantMakruk) result = WhiteFerz; // no choice + else if(gameInfo.variant == VariantASEAN) + result = WhiteRook; // no choice else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) result= WhiteKing; // in Suicide Q is the last thing we want else if(gameInfo.variant == VariantSpartan) @@ -6474,18 +6704,17 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i piece = boards[currentMove][fromY][fromX]; if(gameInfo.variant == VariantChu) { - int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece; - promotionZoneSize = BOARD_HEIGHT/3; - highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion; - } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) { promotionZoneSize = BOARD_HEIGHT/3; + highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing; + } else if(gameInfo.variant == VariantShogi) { + promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8); highestPromotingPiece = (int)WhiteAlfil; } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) { promotionZoneSize = 3; } - // Treat Lance as Pawn when it is not representing Amazon - if(gameInfo.variant != VariantSuper) { + // Treat Lance as Pawn when it is not representing Amazon or Lance + if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) { if(piece == WhiteLance) piece = WhitePawn; else if(piece == BlackLance) piece = BlackPawn; } @@ -6535,9 +6764,13 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i // we either have a choice what to promote to, or (in Shogi) whether to promote if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) { - *promoChoice = PieceToChar(BlackFerz); // no choice - return FALSE; + gameInfo.variant == VariantMakruk) { + ChessSquare p=BlackFerz; // no choice + while(p < EmptySquare) { //but make sure we use piece that exists + *promoChoice = PieceToChar(p++); + if(*promoChoice != '.') break; + } + if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice } // no sense asking what we must promote to if it is going to explode... if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) { @@ -6636,6 +6869,7 @@ OKToStartUserMove (int x, int y) case PlayFromGameFile: if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode case EditGame: + case AnalyzeMode: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); return FALSE; @@ -6751,9 +6985,11 @@ int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; int doubleClick; +Boolean addToBookFlag; +static Board rightsBoard, nullBoard; void -UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) +UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) { ChessMove moveType; ChessSquare pup; @@ -6837,6 +7073,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } + DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square) return; } break; @@ -6858,6 +7095,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } + DrawPosition(TRUE, boards[currentMove]); return; } break; @@ -6869,10 +7107,20 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) /* EditPosition, empty square, or different color piece; click-click move is possible */ if (toX == -2 || toY == -2) { - boards[0][fromY][fromX] = EmptySquare; + boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare); DrawPosition(FALSE, boards[currentMove]); return; } else if (toX >= 0 && toY >= 0) { + if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) { + ChessSquare p = boards[0][rf][ff]; + if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else + if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else + if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) { + int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R + DisplayMessage("", n ? _("rights granted") : _("rights revoked")); + gatingPiece = p; + } + } else rightsBoard[toY][toX] = 0; // revoke rights on moving boards[0][toY][toX] = boards[0][fromY][fromX]; if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings if(boards[0][fromY][0] != EmptySquare) { @@ -6887,13 +7135,14 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) } } else boards[0][fromY][fromX] = gatingPiece; + ClearHighlights(); DrawPosition(FALSE, boards[currentMove]); return; } return; } - if(toX < 0 || toY < 0) return; + if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return; pup = boards[currentMove][toY][toX]; /* [HGM] If move started in holdings, it means a drop. Convert to standard form */ @@ -6905,7 +7154,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings - while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; + while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; fromY = DROP_RANK; } @@ -6913,7 +7162,9 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar); - if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove; + if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove; + + if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { @@ -6927,6 +7178,19 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle ClearPremoveHighlights(); // was included else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights + DrawPosition(FALSE, NULL); + return; + } + + if(addToBookFlag) { // adding moves to book + char buf[MSG_SIZ], move[MSG_SIZ]; + CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move); + if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0', + killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar); + snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move); + AddBookMove(buf); + addToBookFlag = FALSE; + ClearHighlights(); return; } @@ -7134,12 +7398,12 @@ MarkByFEN(char *fen) int r, f; if(!appData.markers || !appData.highlightDragging) return; for(r=0; r= 'A' && *fen <= 'Z') legal[r][f] = 1; else + if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a'; if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else if(*fen == 'T') marker[r][f++] = 0; else @@ -7168,24 +7432,28 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO { typedef char Markers[BOARD_RANKS][BOARD_FILES]; Markers *m = (Markers *) closure; - if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2)) + if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : + kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4)) (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare || kind == WhiteCapturesEnPassant - || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0); - else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3; + || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3; + else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3; } +static int hoverSavedValid; + void MarkTargetSquares (int clear) { int x, y, sum=0; if(clear) { // no reason to ever suppress clearing - for(x=0; x1) capt++; @@ -7220,12 +7488,12 @@ CanPromote (ChessSquare piece, int y) // some variants have fixed promotion piece, no promotion at all, or another selection mechanism if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || - gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE; + (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE; return (piece == BlackPawn && y <= zone || piece == WhitePawn && y >= BOARD_HEIGHT-1-zone || - piece == BlackLance && y == 1 || - piece == WhiteLance && y == BOARD_HEIGHT-2 ); + piece == BlackLance && y <= zone || + piece == WhiteLance && y >= BOARD_HEIGHT-1-zone ); } void @@ -7237,11 +7505,13 @@ HoverEvent (int xPix, int yPix, int x, int y) if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click if(x == oldX && y == oldY) return; // only do something if we enter new square oldFromX = fromX; oldFromY = fromY; - if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change + if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change for(r=0; r= BOARD_RGHT) return; } - if (clickType == Release && x == fromX && y == fromY && killX < 0) { + if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) { + gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move + DragPieceBegin(xPix, yPix, FALSE); dragging = 1; + return; + } + + if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) { DragPieceEnd(xPix, yPix); dragging = 0; if(clearFlag) { // a deferred attempt to click-click move an empty square on top of a piece @@ -7461,16 +7750,16 @@ LeftClick (ClickType clickType, int xPix, int yPix) /* Undo animation damage if any */ DrawPosition(FALSE, NULL); } - if (second || sweepSelecting) { + if (second) { /* Second up/down in same square; just abort move */ - if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]); - second = sweepSelecting = 0; + second = 0; fromX = fromY = -1; gatingPiece = EmptySquare; - MarkTargetSquares(1); ClearHighlights(); gotPremove = 0; ClearPremoveHighlights(); + MarkTargetSquares(-1); + DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing) } else { /* First upclick in same square; start click-click mode */ SetHighlights(x, y, -1, -1); @@ -7480,7 +7769,8 @@ LeftClick (ClickType clickType, int xPix, int yPix) clearFlag = 0; - if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) { + if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && + fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) { if(dragging) DragPieceEnd(xPix, yPix), dragging = 0; DisplayMessage(_("only marked squares are legal"),""); DrawPosition(TRUE, NULL); @@ -7509,14 +7799,16 @@ LeftClick (ClickType clickType, int xPix, int yPix) return; } if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target - killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate + killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate } else if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { if(appData.sweepSelect) { promoSweep = defaultPromoChoice; - if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece; + if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece); selectFlag = 0; lastX = xPix; lastY = yPix; + ReportClick("put", x, y); // extra put to prompt engine for 'choice' command + saveFlash = appData.flashCount; appData.flashCount = 0; Sweep(0); // Pawn that is going to promote: preview promotion piece sweepSelecting = 1; DisplayMessage("", _("Pull pawn backwards to under-promote")); @@ -7530,13 +7822,16 @@ LeftClick (ClickType clickType, int xPix, int yPix) } else { ClearHighlights(); } + MarkTargetSquares(1); } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square + *promoRestrict = 0; appData.flashCount = saveFlash; if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } else { ClearHighlights(); } + MarkTargetSquares(1); } else { #if 0 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square @@ -7547,12 +7842,16 @@ LeftClick (ClickType clickType, int xPix, int yPix) ClearHighlights(); } #endif + if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+') + defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]); if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece; if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square dragging *= 2; // flag button-less dragging if we are dragging MarkTargetSquares(1); - if(x == killX && y == killY) killX = killY = -1; else { - killX = x; killY = y; //remeber this square as intermediate + if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill + else { + kill2X = killX; kill2Y = killY; + killX = x; killY = y; // remember this square as intermediate ReportClick("put", x, y); // and inform engine ReportClick("lift", x, y); MarkTargetSquares(0); @@ -7562,6 +7861,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; + MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square! } // moves into holding are invalid for now (except in EditPosition, adapting to-square) @@ -7598,7 +7898,12 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece)); - if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { + if(legal[toY][toX] == 2) { // highlight-induced promotion + if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral + else promoChoice = ToLower(PieceToChar(defaultPromoChoice)); + } + + if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); MarkTargetSquares(1); if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { @@ -7614,22 +7919,24 @@ LeftClick (ClickType clickType, int xPix, int yPix) DisplayMessage("Click in holdings to choose piece", ""); return; } + DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup PromotionPopUp(promoChoice); } else { int oldMove = currentMove; + flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece UserMoveEvent(fromX, fromY, toX, toY, promoChoice); if (!appData.highlightLastMove || gotPremove) ClearHighlights(); - if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY); + if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL); if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed Explode(boards[currentMove-1], fromX, fromY, toX, toY)) DrawPosition(TRUE, boards[currentMove]); - MarkTargetSquares(1); fromX = fromY = -1; + flashing = 0; } appData.animate = saveAnimate; if (appData.animate || appData.animateDragging) { /* Undo animation damage if needed */ - DrawPosition(FALSE, NULL); +// DrawPosition(FALSE, NULL); } } @@ -7725,6 +8032,23 @@ RightClick (ClickType action, int x, int y, int *fromX, int *fromY) } void +Wheel (int dir, int x, int y) +{ + if(gameMode == EditPosition) { + int xSqr = EventToSquare(x, BOARD_WIDTH); + int ySqr = EventToSquare(y, BOARD_HEIGHT); + if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return; + if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr; + do { + boards[currentMove][ySqr][xSqr] += dir; + if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing; + if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn; + } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.'); + DrawPosition(FALSE, boards[currentMove]); + } else if(dir > 0) ForwardEvent(); else BackwardEvent(); +} + +void SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats) { // char * hint = lastHint; @@ -7748,6 +8072,9 @@ SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each + if( gameMode == AnalyzeMode && stats.pv && stats.pv[0] + && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell(); + SetProgramStats( &stats ); } @@ -7942,7 +8269,7 @@ Adjudicate (ChessProgramState *cps) // most tests only when we understand the game, i.e. legality-checking on if( appData.testLegality ) { /* [HGM] Some more adjudications for obstinate engines */ - int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i; + int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i; static int moveCount = 6; ChessMove result; char *reason = NULL; @@ -8008,6 +8335,7 @@ Adjudicate (ChessProgramState *cps) case MT_NONE: default: break; + case MT_STEALMATE: case MT_STALEMATE: case MT_STAINMATE: reason = "Xboard adjudication: Stalemate"; @@ -8246,11 +8574,66 @@ Adjudicate (ChessProgramState *cps) return 0; } +typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square); +typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options); +static int egbbCode[] = { 6, 5, 4, 3, 2, 1 }; + +static int +BitbaseProbe () +{ + int pieces[10], squares[10], cnt=0, r, f, res; + static int loaded; + static PPROBE_EGBB probeBB; + if(!appData.testLegality) return 10; + if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12; + if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12; + if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game + for(r=0; r= BlackPawn); + int type = piece - black*BlackPawn; + if(piece == EmptySquare) continue; + if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece + if(type == WhiteKing) type = WhiteQueen + 1; + type = egbbCode[type]; + squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT; + pieces[cnt] = type + black*6; + if(++cnt > 5) return 11; + } + pieces[cnt] = squares[cnt] = 0; + // probe EGBB + if(loaded == 2) return 13; // loading failed before + if(loaded == 0) { + char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ]; + HMODULE lib; + PLOAD_EGBB loadBB; + loaded = 2; // prepare for failure + if(!path) return 13; // no egbb installed + strncpy(buf, path + 8, MSG_SIZ); + if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf); + snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME); + lib = LoadLibrary(buf); + if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; } + loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen"); + probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen"); + if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; } + p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD + loaded = 1; // success! + } + res = probeBB(forwardMostMove & 1, pieces, squares); + return res > 0 ? 1 : res < 0 ? -1 : 0; +} + char * SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; + if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth); + SendToProgram(buf, cps); + } //first determine if the incoming move brings opponent into his book if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move @@ -8328,19 +8711,19 @@ DeferredBookMove (void) static int savedWhitePlayer, savedBlackPlayer, pairingReceived; static ChessProgramState *stalledEngine; -static char stashedInputMove[MSG_SIZ]; +static char stashedInputMove[MSG_SIZ], abortEngineThink; void HandleMachineMove (char *message, ChessProgramState *cps) { - static char firstLeg[20]; + static char firstLeg[20], legs; char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; char realname[MSG_SIZ]; int fromX, fromY, toX, toY; ChessMove moveType; char promoChar, roar; char *p, *pv=buf1; - int machineWhite, oldError; + int oldError; char *bookHit; if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) { @@ -8411,24 +8794,29 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h return; } + if(cps->usePing) { + /* This method is only useful on engines that support ping */ + if(abortEngineThink) { + if (appData.debugMode) { + fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which); + } + SendToProgram("undo\n", cps); + return; + } + if (cps->lastPing != cps->lastPong) { - if (gameMode == BeginningOfGame) { /* Extra move from before last new; ignore */ if (appData.debugMode) { fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); } - } else { - if (appData.debugMode) { - fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", - cps->which, gameMode); - } - - SendToProgram("undo\n", cps); - } return; } + } else { + + int machineWhite = FALSE; + switch (gameMode) { case BeginningOfGame: /* Extra move from before last reset; ignore */ @@ -8474,24 +8862,35 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } return; } + } if(cps->alphaRank) AlphaRank(machineMove, 4); // [HGM] lion: (some very limited) support for Alien protocol - killX = killY = -1; + killX = killY = kill2X = kill2Y = -1; if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move + if(legs++) return; // middle leg contains only redundant info, ignore (but count it) safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives return; - } else if(firstLeg[0]) { // there was a previous leg; - // only support case where same piece makes two step (and don't even test that!) + } + if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move) + char *q = strchr(p+1, ','); // second comma? + safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off + if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs! + safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20); + } + if(firstLeg[0]) { // there was a previous leg; + // only support case where same piece makes two step char buf[20], *p = machineMove+1, *q = buf+1, f; safeStrCpy(buf, machineMove, 20); while(isdigit(*q)) q++; // find start of to-square safeStrCpy(machineMove, firstLeg, 20); - while(isdigit(*p)) p++; + while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove) + if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg + else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling) safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global - firstLeg[0] = NULLCHAR; + firstLeg[0] = NULLCHAR; legs = 0; } if (!ParseOneMove(machineMove, forwardMostMove, &moveType, @@ -8500,10 +8899,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"), machineMove, _(cps->which)); DisplayMoveError(buf1); - snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d", - machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType); + snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType); if (gameMode == TwoMachinesPlay) { - GameEnds(machineWhite ? BlackWins : WhiteWins, + GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, buf1, GE_XBOARD); } return; @@ -8521,10 +8920,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if(moveType == IllegalMove) { snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); - GameEnds(machineWhite ? BlackWins : WhiteWins, + GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, buf1, GE_XBOARD); return; - } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) + } else if(!appData.fischerCastling) /* [HGM] Kludge to handle engines that send FRC-style castling when they shouldn't (like TSCP-Gothic) */ switch(moveType) { @@ -8566,9 +8965,20 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h free(fen); GameEnds(GameUnfinished, NULL, GE_XBOARD); } + if(appData.epd) { + if(solvingTime >= 0) { + snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]); + totalTime += solvingTime; first.matchWins++; solvingTime = -1; + } else { + snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : ""); + if(solvingTime == -2) second.matchWins++; + } + OutputKibitz(2, buf1); + GameEnds(GameUnfinished, NULL, GE_XBOARD); + } /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ - if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { + if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { int count = 0; while( count < adjudicateLossPlies ) { @@ -8578,7 +8988,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h score = -score; /* Flip score for winning side */ } - if( score > adjudicateLossThreshold ) { + if( score > appData.adjudicateLossThreshold ) { break; } @@ -8622,7 +9032,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h (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 @@ -8716,24 +9125,56 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ]; if(appData.icsActive || forwardMostMove != 0 || cps != &first) return; *buf = NULLCHAR; - if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); - if(startedFromSetupPosition) return; + if(sscanf(message, "setup (%s", buf) == 1) { + s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES); + ASSIGN(appData.pieceToCharTable, buf); + } dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName); if(dummy >= 3) { while(message[s] && message[s++] != ' '); if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand || dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant + if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand; if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant InitPosition(1); // calls InitDrawingSizes to let new parameters take effect - if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition + if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition + startedFromSetupPosition = FALSE; } } + if(startedFromSetupPosition) return; ParseFEN(boards[0], &dummy, message+s, FALSE); DrawPosition(TRUE, boards[0]); + CopyBoard(initialPosition, boards[0]); startedFromSetupPosition = TRUE; return; } + if(sscanf(message, "piece %s %s", buf2, buf1) == 2) { + ChessSquare piece = WhitePawn; + char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0; + if(*p == '+') promoted++, ID = *++p; + if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++; + piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece); + if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR + /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon + /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra + /* For variants we don't have */ && gameInfo.variant != VariantBerolina + /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder + /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown + && gameInfo.variant != VariantGreat + && gameInfo.variant != VariantFairy ) return; + if(piece < EmptySquare) { + pieceDefs = TRUE; + ASSIGN(pieceDesc[piece], buf1); + if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); } + } + return; + } + if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) { + promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict)); + Sweep(0); + return; + } /* [HGM] Allow engine to set up a position. Don't ask me why one would * want this, I was asked to put it in, and obliged. */ @@ -8845,15 +9286,20 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(initPing == cps->lastPong) { if(gameInfo.variant == VariantUnknown) { DisplayError(_("Engine did not send setup for non-standard variant"), 0); - *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery? + *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery? GameEnds(GameUnfinished, NULL, GE_XBOARD); } initPing = -1; } + if(cps->lastPing == cps->lastPong && abortEngineThink) { + abortEngineThink = FALSE; + DisplayMessage("", ""); + ThawUI(); + } return; } if(!strncmp(message, "highlight ", 10)) { - if(appData.testLegality && appData.markers) return; + if(appData.testLegality && !*engineVariant && appData.markers) return; MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares return; } @@ -8997,6 +9443,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. Don't use it. */ cps->sendTime = 0; } + if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers + if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times + sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return; + } /* * If chess program startup fails, exit with an error message. @@ -9261,9 +9711,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (!ignore) { ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines + int solved = 0; buf1[0] = NULLCHAR; if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n", &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { + char score_buf[MSG_SIZ]; + + if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read + nodes += u64Const(0x100000000); if (plyext != ' ' && plyext != '\t') { time *= 100; @@ -9283,6 +9738,37 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1); + if(*bestMove) { // rememer time best EPD move was first found + int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2; + ChessMove mt; char *p = bestMove; + int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2); + solved = 0; + while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) { + if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) { + solvingTime = (solvingTime < 0 ? time : solvingTime); + solved = 1; + break; + } + while(*p && *p != ' ') p++; + while(*p == ' ') p++; + } + if(!solved) solvingTime = -1; + } + if(*avoidMove && !solved) { + int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2; + ChessMove mt; char *p = avoidMove, solved = 1; + int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2); + while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) { + if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) { + solved = 0; solvingTime = -2; + break; + } + while(*p && *p != ' ') p++; + while(*p == ' ') p++; + } + if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime); + } + if(serverMoves && (time > 100 || time == 0 && plylev > 7)) { char buf[MSG_SIZ]; FILE *f; @@ -9353,11 +9839,18 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. [AS] Protect the thinkOutput buffer from overflow... this is only useful if buf1 hasn't overflowed first! */ - snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s", + if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1; + if(curscore >= MATE_SCORE) + snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE); + else if(curscore <= -MATE_SCORE) + snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE); + else + snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0); + snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s", plylev, (gameMode == TwoMachinesPlay ? ToUpper(cps->twoMachinesColor[0]) : ' '), - ((double) curscore) / 100.0, + score_buf, prefixHint ? lastHint : "", prefixHint ? " " : "" ); @@ -9683,7 +10176,7 @@ ParseGameHistory (char *game) default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+"); break; case MT_CHECKMATE: @@ -9699,15 +10192,16 @@ ParseGameHistory (char *game) void ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) { - ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0; + ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0; int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 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 */ if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A; - oldEP = (signed char)board[EP_STATUS]; + oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK]; board[EP_STATUS] = EP_NONE; + board[EP_FILE] = board[EP_RANK] = 100; if (fromY == DROP_RANK) { /* must be first */ @@ -9717,13 +10211,17 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } piece = board[toY][toX] = (ChessSquare) fromX; } else { - ChessSquare victim; +// ChessSquare victim; int i; - if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something - victim = board[killY][killX], + if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something +// victim = board[killY][killX], + killed = board[killY][killX], board[killY][killX] = EmptySquare, board[EP_STATUS] = EP_CAPTURE; + if( kill2X >= 0 && kill2Y >= 0) + killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare; + } if( board[toY][toX] != EmptySquare ) { board[EP_STATUS] = EP_CAPTURE; @@ -9734,14 +10232,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } - if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { - if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) - board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants - } else - if( board[fromY][fromX] == WhitePawn ) { + pawn = board[fromY][fromX]; + if( pawn == WhiteLance || pawn == BlackLance ) { + if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) { + if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set + else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge + } + } + if( pawn == WhitePawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers board[EP_STATUS] = EP_PAWN_MOVE; - if( toY-fromY==2) { + if( toY-fromY>=2) { + board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2); if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn && gameInfo.variant != VariantBerolina || toX < fromX) board[EP_STATUS] = toX | berolina; @@ -9750,10 +10252,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[EP_STATUS] = toX; } } else - if( board[fromY][fromX] == BlackPawn ) { + if( pawn == BlackPawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers board[EP_STATUS] = EP_PAWN_MOVE; - if( toY-fromY== -2) { + if( toY-fromY<= -2) { + board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2); if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && gameInfo.variant != VariantBerolina || toX < fromX) board[EP_STATUS] = toX | berolina; @@ -9763,6 +10266,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } + if(fromY == 0) board[TOUCHED_W] |= 1<= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled + && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own + board[toY][toX] = piece; board[fromY][fromX] = EmptySquare; + board[toY][toX + (killX < fromX ? 1 : -1)] = killed; + board[EP_STATUS] = EP_NONE; // capture was fake! + } else + if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) { + board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping + board[toY][toX] = piece; + board[EP_STATUS] = EP_NONE; // capture was fake! + } else /* Code added by Tord: */ /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */ if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook || board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) { + board[EP_STATUS] = EP_NONE; // capture was fake! board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if((toX > fromX) != (piece == WhiteRook)) { @@ -9796,6 +10316,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook || board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) { + board[EP_STATUS] = EP_NONE; board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if((toX > fromX) != (piece == BlackRook)) { @@ -9805,40 +10326,49 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } /* End of code added by Tord */ + } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) { + board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling + board[toY][toX] = piece; } else if (board[fromY][fromX] == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX > fromX+1) { + for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++) + ; // castle with nearest piece + board[fromY][toX-1] = board[fromY][rookX]; + board[fromY][rookX] = EmptySquare; board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; - board[fromY][BOARD_RGHT-1] = EmptySquare; } else if (board[fromY][fromX] == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { + for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--) + ; // castle with nearest piece + board[fromY][toX+1] = board[fromY][rookX]; + board[fromY][rookX] = EmptySquare; board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - board[toY][toX+1] = board[fromY][BOARD_LEFT]; - board[fromY][BOARD_LEFT] = EmptySquare; } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) + board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); - if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ - board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); + if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */ + board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX])); board[fromY][fromX] = EmptySquare; } else if ((fromY >= BOARD_HEIGHT>>1) - && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina - && (board[fromY][fromX] == WhitePawn) + && (pawn == WhitePawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhitePawn; - captured = board[toY - 1][toX]; - board[toY - 1][toX] = EmptySquare; + board[toY][toX] = piece; + if(toY == epRank - 128 + 1) + captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare; + else + captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare; } else if ((fromY == BOARD_HEIGHT-4) && (toX == fromX) && gameInfo.variant == VariantBerolina @@ -9855,17 +10385,21 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } else if (board[fromY][fromX] == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX > fromX+1) { + for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++) + ; + board[fromY][toX-1] = board[fromY][rookX]; + board[fromY][rookX] = EmptySquare; board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; - board[fromY][BOARD_RGHT-1] = EmptySquare; } else if (board[fromY][fromX] == king && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { + for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--) + ; + board[fromY][toX+1] = board[fromY][rookX]; + board[fromY][rookX] = EmptySquare; board[fromY][fromX] = EmptySquare; board[toY][toX] = king; - board[toY][toX+1] = board[fromY][BOARD_LEFT]; - board[fromY][BOARD_LEFT] = EmptySquare; } else if (fromY == 7 && fromX == 3 && board[fromY][fromX] == BlackKing && toY == 7 && toX == 5) { @@ -9881,25 +10415,27 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) + board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY < promoRank && promoChar ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); - if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ - board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); + if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */ + board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX])); board[fromY][fromX] = EmptySquare; } else if ((fromY < BOARD_HEIGHT>>1) - && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina - && (board[fromY][fromX] == BlackPawn) + && (pawn == BlackPawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackPawn; - captured = board[toY + 1][toX]; - board[toY + 1][toX] = EmptySquare; + board[toY][toX] = piece; + if(toY == epRank - 128 - 1) + captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare; + else + captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare; } else if ((fromY == 3) && (toX == fromX) && gameInfo.variant == VariantBerolina @@ -9959,10 +10495,10 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) p = (int) captured; if (p >= (int) BlackPawn) { p -= (int)BlackPawn; - if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { - /* in Shogi restore piece to its original first */ - captured = (ChessSquare) (DEMOTED captured); - p = DEMOTED p; + if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') { + /* Restore shogi-promoted piece to its original first */ + captured = (ChessSquare) (DEMOTED(captured)); + p = DEMOTED(p); } p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; } @@ -9970,9 +10506,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured; } else { p -= (int)WhitePawn; - if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { - captured = (ChessSquare) (DEMOTED captured); - p = DEMOTED p; + if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') { + captured = (ChessSquare) (DEMOTED(captured)); + p = DEMOTED(p); } p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; } @@ -9994,18 +10530,19 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[toY][toX] = EmptySquare; } } + if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) { board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating } else if(promoChar == '+') { /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */ - board[toY][toX] = (ChessSquare) (CHUPROMOTED piece); + board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece)); if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight)) board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); - if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified - && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available + if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified + && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available board[toY][toX] = newPiece; } if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) @@ -10031,13 +10568,17 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) ChessSquare p = boards[forwardMostMove][toY][toX]; // forwardMostMove++; // [HGM] bare: moved downstream + if(kill2X >= 0) x = kill2X, y = kill2Y; else if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), - fromY, fromX, y, x, promoChar, + fromY, fromX, y, x, (killX < 0)*promoChar, s); + if(kill2X >= 0 && kill2Y >= 0) + sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture if(killX >= 0 && killY >= 0) - sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0'); + sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x', + toX + AAA, toY + ONE - '0', promoChar); if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ int timeLeft; static int lastLoadFlag=0; int king, piece; @@ -10124,7 +10665,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+"); break; case MT_CHECKMATE: @@ -10151,7 +10692,7 @@ ShowMove (int fromX, int fromY, int toX, int toY) currentMove = forwardMostMove; } - killX = killY = -1; // [HGM] lion: used up + killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up if (instant) return; @@ -10287,6 +10828,7 @@ InitChessProgram (ChessProgramState *cps, int setup) SendToProgram(buf, cps); } + setboardSpoiledMachineBlack = FALSE; SendToProgram(cps->initString, cps); if (gameInfo.variant != VariantNormal && gameInfo.variant != VariantLoadable @@ -10295,8 +10837,21 @@ InitChessProgram (ChessProgramState *cps, int setup) b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy); + if (b == NULL) { - DisplayFatalError(variantError, 0, 1); + VariantClass v; + char c, *q = cps->variants, *p = strchr(q, ','); + if(p) *p = NULLCHAR; + v = StringToVariant(q); + DisplayError(variantError, 0); + if(v != VariantUnknown && cps == &first) { + int w, h, s; + if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any) + appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1; + ASSIGN(appData.variant, q); + Reset(TRUE, FALSE); + } + if(p) *p = ','; return; } @@ -10535,8 +11090,10 @@ Substitute (char *participants, int expunge) p++; q++; } if(*p) { // difference - while(*p && *p++ != '\n'); - while(*q && *q++ != '\n'); + while(*p && *p++ != '\n') + ; + while(*q && *q++ != '\n') + ; changed = nPlayers; changes = 1 + (strcmp(p, q) != 0); } @@ -10698,7 +11255,9 @@ SwapEngines (int n) SWAP(fenOverride, p) SWAP(NPS, h) SWAP(accumulateTC, h) + SWAP(drawDepth, h) SWAP(host, p) + SWAP(pseudo, h) } int @@ -10916,6 +11475,11 @@ NextMatchGame () res = LoadGameOrPosition(matchGame); // setup game appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too! if(!res) return; // abort when bad game/pos file + if(appData.epd) {// in EPD mode we make sure first engine is to move + firstWhite = !(forwardMostMove & 1); + first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement + second.twoMachinesColor = firstWhite ? "black\n" : "white\n"; + } TwoMachinesEvent(); } @@ -10979,7 +11543,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) result, resultDetails ? resultDetails : "(null)", whosays); } - fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit) @@ -11086,16 +11650,17 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course... && result != GameIsDrawn) - { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn); + { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn); for(j=BOARD_LEFT; j= 0 && p <= (int)WhiteKing) k++; + oppoKings += (p + color == WhiteKing + BlackPawn - color); } if (appData.debugMode) { fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n", result, resultDetails ? resultDetails : "(null)", whosays, k, color); } - if(k <= 1) { + if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed result = GameIsDrawn; snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails); resultDetails = buf; @@ -11249,8 +11814,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(first.pr, first.useSigterm); + DestroyChildProcess(first.pr, 4 + first.useSigterm); first.reload = TRUE; } first.pr = NoProc; @@ -11275,8 +11839,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(second.pr, second.useSigterm); + DestroyChildProcess(second.pr, 4 + second.useSigterm); second.reload = TRUE; } second.pr = NoProc; @@ -11324,6 +11887,16 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) return; } else { gameMode = nextGameMode; + if(appData.epd) { + snprintf(buf, MSG_SIZ, "-------------------------------------- "); + OutputKibitz(2, buf); + snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.); + OutputKibitz(2, buf); + snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins); + if(second.matchWins) OutputKibitz(2, buf); + snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1)); + OutputKibitz(2, buf); + } snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"), first.tidy, second.tidy, first.matchWins, second.matchWins, @@ -11446,6 +12019,9 @@ Reset (int redraw, int init) fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n", redraw, init, gameMode); } + pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves + deadRanks = 0; // assume entire board is used + for(i=0; i fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + if(piece == pieceList[1] && fromY == toY) { + if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT; moveDatabase[movePtr++].piece = Q_WCASTL; quickBoard[sq] = piece; piece = quickBoard[from]; quickBoard[from] = 0; moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling + quickBoard[sq] = 0; // remove Rook + moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square + moveDatabase[movePtr++].piece = Q_WCASTL; + quickBoard[sq] = pieceList[1]; // put King + piece = rook; + moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1; + } } else - if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + if(piece == pieceList[2] && fromY == toY) { + if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4); moveDatabase[movePtr++].piece = Q_BCASTL; quickBoard[sq] = piece; piece = quickBoard[from]; quickBoard[from] = 0; moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling + quickBoard[sq] = 0; // remove Rook + moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); + moveDatabase[movePtr++].piece = Q_BCASTL; + quickBoard[sq] = pieceList[2]; // put King + piece = rook; + moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1; + } } else if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) { quickBoard[(fromY<<4)+toX] = 0; @@ -12188,12 +12795,26 @@ QuickCompare (Board board, int *minCounts, int *maxCounts) int QuickScan (Board board, Move *move) { // reconstruct game,and compare all positions in it - int cnt=0, stretch=0, total = MakePieceList(board, counts); + int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts); do { int piece = move->piece; int to = move->to, from = pieceList[piece]; + if(found < 0) { // if already found just scan to game end for final piece count + if(QuickCompare(soughtBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) || + flipSearch && (QuickCompare(flipBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse)) + ) { + static int lastCounts[EmptySquare+1]; + int i; + if(stretch) for(i=0; i= appData.stretch)) found = cnt + 1 - stretch; + if(found >= 0 && !appData.minPieces) return found; + } if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4 - if(!piece) return -1; + if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found); if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType) piece = (++move)->piece; from = pieceList[piece]; @@ -12218,23 +12839,12 @@ QuickScan (Board board, Move *move) } } if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture - if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for + if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for quickBoard[from] = 0; aftercastle: quickBoard[to] = piece; pieceList[piece] = to; cnt++; turn ^= 3; - if(QuickCompare(soughtBoard, minSought, maxSought) || - appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) || - flipSearch && (QuickCompare(flipBoard, minSought, maxSought) || - appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse)) - ) { - static int lastCounts[EmptySquare+1]; - int i; - if(stretch) for(i=0; i= appData.stretch)) return cnt + 1 - stretch; move++; } while(1); } @@ -12394,10 +13004,13 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) char buf[MSG_SIZ]; int gn = gameNumber; ListGame *lg = NULL; - int numPGNTags = 0; + int numPGNTags = 0, i; int err, pos = -1; GameMode oldGameMode; - VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ + VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ + char oldName[MSG_SIZ]; + + safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant; if (appData.debugMode) fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode); @@ -12409,7 +13022,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) if (gameMode != BeginningOfGame) { Reset(FALSE, TRUE); } - killX = killY = -1; // [HGM] lion: in case we did not Reset + killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset gameFileFP = f; if (lastLoadGameFP != NULL && lastLoadGameFP != f) { @@ -12580,6 +13193,8 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) if (appData.debugMode) fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm); + for(i=0; i= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare; + // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout) + fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare; if (pn >= 2) { if (fenMode || line[0] == '#') pn--; @@ -12962,10 +13583,17 @@ LoadPosition (FILE *f, int positionNumber, char *title) } if (fenMode) { + char *p; if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) { DisplayError(_("Bad FEN position in file"), 0); return FALSE; } + if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move + sscanf(p+4, "%[^;]", bestMove); + } else *bestMove = NULLCHAR; + if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move + sscanf(p+4, "%[^;]", avoidMove); + } else *avoidMove = NULLCHAR; } else { (void) fgets(line, MSG_SIZ, f); (void) fgets(line, MSG_SIZ, f); @@ -13187,9 +13815,9 @@ GetOutOfBookInfo (char * buf) } } -/* Save game in PGN style and close the file */ -int -SaveGamePGN (FILE *f) +/* Save game in PGN style */ +static void +SaveGamePGN2 (FILE *f) { int i, offset, linelen, newblock; // char *movetext; @@ -13304,6 +13932,10 @@ SaveGamePGN (FILE *f) snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0); } + if(appData.cumulativeTimePGN) { + snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000); + } + snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}", pvInfoList[i].score >= 0 ? "+" : "", pvInfoList[i].score / 100.0, @@ -13349,7 +13981,13 @@ SaveGamePGN (FILE *f) } else { fprintf(f, "%s\n\n", PGNResult(gameInfo.result)); } +} +/* Save game in PGN style and close the file */ +int +SaveGamePGN (FILE *f) +{ + SaveGamePGN2(f); fclose(f); lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving return TRUE; @@ -13828,6 +14466,7 @@ ExitEvent (int status) return; } + if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE); if (telnetISR != NULL) { @@ -13854,14 +14493,12 @@ ExitEvent (int status) DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ ); + DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ ); } if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ ); + DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ ); } if (first.isr != NULL) { RemoveInputSource(first.isr); @@ -14092,7 +14729,10 @@ AnalyzeModeEvent () first.maybeThinking = FALSE; /* avoid killing GNU Chess */ EngineOutputPopUp(); } - if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; + if (!appData.icsEngineAnalyze) { + gameMode = AnalyzeMode; + ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header + } pausing = FALSE; ModeHighlight(); SetGameInfo(); @@ -14411,12 +15051,15 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + if(!appData.epd) { if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) { - startingEngine = FALSE; + startingEngine = matchMode = FALSE; DisplayError("second engine does not play this", 0); + gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked + EditGameEvent(); // switch back to EditGame mode return; } @@ -14427,6 +15070,7 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + } GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load if(appData.matchPause>10000 || appData.matchPause<10) appData.matchPause = 10000; /* [HGM] make pause adjustable */ @@ -14438,6 +15082,7 @@ TwoMachinesEvent P((void)) // we are now committed to starting the game stalling = 0; DisplayMessage("", ""); + if(!appData.epd) { if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); if (appData.debugMode) { @@ -14447,6 +15092,7 @@ TwoMachinesEvent P((void)) for (i = backwardMostMove; i < forwardMostMove; i++) { SendMoveToProgram(i, &second); } + } gameMode = TwoMachinesPlay; pausing = startingEngine = FALSE; @@ -14465,11 +15111,13 @@ TwoMachinesEvent P((void)) snprintf(buf, MSG_SIZ, "name %s\n", second.tidy); SendToProgram(buf, &first); } + if(!appData.epd) { SendToProgram(second.computerString, &second); if (second.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", first.tidy); SendToProgram(buf, &second); } + } ResetClocks(); if (!first.sendTime || !second.sendTime) { @@ -14578,6 +15226,16 @@ EditGameEvent () case MachinePlaysBlack: case BeginningOfGame: SendToProgram("force\n", &first); + if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking + if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking + char buf[MSG_SIZ]; + abortEngineThink = TRUE; + snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing); + SendToProgram(buf, &first); + DisplayMessage("Aborting engine think", ""); + FreezeUI(); + } + } SetUserThinkingEnables(); break; case PlayFromGameFile: @@ -14654,10 +15312,10 @@ EditGameEvent () SetGameInfo(); } - void EditPositionEvent () { + int i; if (gameMode == EditPosition) { EditGameEvent(); return; @@ -14669,8 +15327,11 @@ EditPositionEvent () gameMode = EditPosition; ModeHighlight(); SetGameInfo(); + CopyBoard(rightsBoard, nullBoard); if (currentMove > 0) CopyBoard(boards[0], boards[currentMove]); + for(i=0; i>1; - if(boards[0][0][BOARD_WIDTH>>1] == king) { - boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights; - boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights; - } else boards[0][CASTLING][2] = NoRights; - if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { - boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights; - boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights; - } else boards[0][CASTLING][5] = NoRights; - if(gameInfo.variant == VariantSChess) { - int i; - for(i=BOARD_LEFT; i=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p. + if(rightsBoard[r][f]) { + ChessSquare p = boards[0][r][f]; + if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f; + else if(p == king) boards[0][CASTLING][2] = f; + else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f; + else rightsBoard[r][f] = 2; // mark for second pass + } + } + for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks + if(rightsBoard[r][f] == 2) { + ChessSquare p = boards[0][r][f]; + if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else + if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f; } } } @@ -14818,11 +15480,14 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) ChessSquare piece = boards[0][y][x]; static Board erasedBoard, currentBoard, menuBoard, nullBoard; static int lastVariant; + int baseRank = BOARD_HEIGHT-1, hasRights = 0; if (gameMode != EditPosition && gameMode != IcsExamining) return; switch (selection) { case ClearBoard: + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress + MarkTargetSquares(1); CopyBoard(currentBoard, boards[0]); CopyBoard(menuBoard, initialPosition); if (gameMode == IcsExamining && ics_type == ICS_FICS) { @@ -14842,20 +15507,22 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) AAA + x, ONE + y); SendToICS(buf); } - } else { + } else if(boards[0][y][x] != DarkSquare) { if(boards[0][y][x] != p) nonEmpty++; boards[0][y][x] = p; } } - menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p; } + CopyBoard(rightsBoard, nullBoard); if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards - for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates - ChessSquare p = menuBoard[0][x]; - for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare; - p = menuBoard[BOARD_HEIGHT-1][x]; - for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare; + int r, i; + for(r = 0; r < BOARD_HEIGHT; r++) { + for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates + ChessSquare p = menuBoard[r][x]; + for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare; + } } + menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted DisplayMessage("Clicking clock again restores position", ""); if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]); if(!nonEmpty) { // asked to clear an empty board @@ -14870,6 +15537,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) } else CopyBoard(erasedBoard, currentBoard); + for(i=0; i= (int)WhitePawn && piece < (int)WhiteMan || piece >= (int)BlackPawn && piece < (int)BlackMan ) { - selection = (ChessSquare) (PROMOTED piece); + selection = (ChessSquare) (PROMOTED(piece)); } else if(piece == EmptySquare) selection = WhiteSilver; else selection = (ChessSquare)((int)piece - 1); goto defaultlabel; @@ -14917,7 +15586,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) case DemotePiece: if(piece > (int)WhiteMan && piece <= (int)WhiteKing || piece > (int)BlackMan && piece <= (int)BlackKing ) { - selection = (ChessSquare) (DEMOTED piece); + selection = (ChessSquare) (DEMOTED(piece)); } else if(piece == EmptySquare) selection = BlackSilver; else selection = (ChessSquare)((int)piece + 1); goto defaultlabel; @@ -14932,12 +15601,21 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); goto defaultlabel; + case WhiteRook: + baseRank = 0; + case BlackRook: + if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1; + if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1; + goto defaultlabel; + case WhiteKing: + baseRank = 0; 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); + if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1; default: defaultlabel: if (gameMode == IcsExamining) { @@ -14946,6 +15624,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) PieceToChar(selection), AAA + x, ONE + y); SendToICS(buf); } else { + rightsBoard[y][x] = hasRights; if(x < BOARD_LEFT || x >= BOARD_RGHT) { int n; if(x == BOARD_LEFT-2 && selection >= BlackPawn) { @@ -15116,7 +15795,8 @@ ClockClick (int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetBlackToPlayEvent(); - } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame || + gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) { UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move } else if (shiftKey) { AdjustClock(which, -1); @@ -15128,7 +15808,8 @@ ClockClick (int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetWhiteToPlayEvent(); - } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame || + gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) { UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move } else if (shiftKey) { AdjustClock(which, -1); @@ -15272,6 +15953,7 @@ ForwardInner (int target) seekGraphUp = FALSE; MarkTargetSquares(1); + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); @@ -15295,7 +15977,12 @@ ForwardInner (int target) fromX = moveList[target - 1][0] - AAA; fromY = moveList[target - 1][1] - ONE; if (target == currentMove + 1) { + if(moveList[target - 1][4] == ';') { // multi-leg + killX = moveList[target - 1][5] - AAA; + killY = moveList[target - 1][6] - ONE; + } AnimateMove(boards[currentMove], fromX, fromY, toX, toY); + killX = killY = -1; } if (appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); @@ -15381,6 +16068,7 @@ BackwardInner (int target) if (gameMode == EditPosition) return; seekGraphUp = FALSE; MarkTargetSquares(1); + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress if (currentMove <= backwardMostMove) { ClearHighlights(); DrawPosition(full_redraw, boards[currentMove]); @@ -15640,6 +16328,36 @@ HintEvent () hintRequested = TRUE; } +int +SaveSelected (FILE *g, int dummy, char *dummy2) +{ + ListGame * lg = (ListGame *) gameList.head; + int nItem, cnt=0; + FILE *f; + + if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) { + DisplayError(_("Game list not loaded or empty"), 0); + return 0; + } + + creatingBook = TRUE; // suppresses stuff during load game + + /* Get list size */ + for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){ + if(lg->position >= 0) { // selected? + LoadGame(f, nItem, "", TRUE); + SaveGamePGN2(g); // leaves g open + cnt++; DoEvents(); + } + lg = (ListGame *) lg->node.succ; + } + + fclose(g); + creatingBook = FALSE; + + return cnt; +} + void CreateBookEvent () { @@ -15665,8 +16383,11 @@ CreateBookEvent () /* Get list size */ for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){ - LoadGame(f, nItem, "", TRUE); - AddGameToBook(TRUE); + if(lg->position >= 0) { + LoadGame(f, nItem, "", TRUE); + AddGameToBook(TRUE); + DoEvents(); + } lg = (ListGame *) lg->node.succ; } @@ -15723,7 +16444,7 @@ PrintPosition (FILE *fp, int move) for (i = BOARD_HEIGHT - 1; i >= 0; i--) { for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { char c = PieceToChar(boards[move][i][j]); - fputc(c == 'x' ? '.' : c, fp); + fputc(c == '?' ? '.' : c, fp); fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp); } } @@ -16103,7 +16824,11 @@ GetInfoFromComment (int index, char * text) pvInfoList[index-1].score = score; pvInfoList[index-1].time = 10*time; // centi-sec if(*sep == '}') *sep = 0; else *--sep = '{'; - if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both + if(p != text) { + while(*p++ = *sep++) + ; + sep = text; + } // squeeze out space between PV and comment, and return both } return sep; } @@ -16365,6 +17090,10 @@ EngineDefinedVariant (ChessProgramState *cps, int n) v = StringToVariant(s); if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants + if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants + !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") || + !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") || + !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++; if(--n < 0) safeStrCpy(buf, s, MSG_SIZ); } if(p) *p++ = ','; @@ -16436,6 +17165,7 @@ ParseOption (Option *opt, ChessProgramState *cps) char *p, *q, buf[MSG_SIZ]; int n, min = (-1)<<31, max = 1<<31, def; + opt->target = &opt->value; // OK for spin/slider and checkbox if(p = strstr(opt->name, " -spin ")) { if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE; if(max < min) max = min; // enforce consistency @@ -16458,14 +17188,17 @@ ParseOption (Option *opt, ChessProgramState *cps) } else if((p = strstr(opt->name, " -string "))) { opt->textValue = p+9; opt->type = TextBox; + opt->target = &opt->textValue; } else if((p = strstr(opt->name, " -file "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots - opt->textValue = p+7; + opt->target = opt->textValue = p+7; opt->type = FileName; // FileName; + opt->target = &opt->textValue; } else if((p = strstr(opt->name, " -path "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots - opt->textValue = p+7; + opt->target = opt->textValue = p+7; opt->type = PathName; // PathName; + opt->target = &opt->textValue; } else if(p = strstr(opt->name, " -check ")) { if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); @@ -16529,9 +17262,9 @@ FeatureDone (ChessProgramState *cps, int val) (cb == TwoMachinesEventIfReady)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000); - } + } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list cps->initDone = val; - if(val) cps->reload = FALSE; + if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val); } /* Parse feature command from engine */ @@ -17083,11 +17816,13 @@ ResetClocks () #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */ +static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness... + /* Decrement running clock by amount of time that has passed */ void DecrementClocks () { - long timeRemaining; + long tRemaining; long lastTickLength, fudge; TimeMark now; @@ -17104,28 +17839,32 @@ DecrementClocks () if (WhiteOnMove(forwardMostMove)) { if(whiteNPS >= 0) lastTickLength = 0; - timeRemaining = whiteTimeRemaining -= lastTickLength; - if(timeRemaining < 0 && !appData.icsActive) { + tRemaining = whiteTimeRemaining -= lastTickLength; + if( tRemaining < 0 && !appData.icsActive) { GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession; if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next whiteStartMove = forwardMostMove; whiteTC = nextSession; - lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC); + lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC); } } + if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining; DisplayWhiteClock(whiteTimeRemaining - fudge, WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove)); + timeSuffix = 0; } else { if(blackNPS >= 0) lastTickLength = 0; - timeRemaining = blackTimeRemaining -= lastTickLength; - if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next + tRemaining = blackTimeRemaining -= lastTickLength; + if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC); if(suddenDeath) { blackStartMove = forwardMostMove; - lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession); + lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession); } } + if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining; DisplayBlackClock(blackTimeRemaining - fudge, !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove)); + timeSuffix = 0; } if (CheckFlags()) return; @@ -17140,7 +17879,7 @@ DecrementClocks () } tickStartTM = now; - intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge; + intendedTickLength = NextTickLength( tRemaining - fudge) + fudge; StartClockTimer(intendedTickLength); /* if the time remaining has fallen below the alarm threshold, sound the @@ -17156,9 +17895,9 @@ DecrementClocks () ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove)) )) return; - if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) { + if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) { alarmSounded = FALSE; - } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { + } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) { PlayAlarmSound(); alarmSounded = TRUE; } @@ -17299,7 +18038,7 @@ TimeString (long ms) { long second, minute, hour, day; char *sign = ""; - static char buf[32]; + static char buf[40], moveTime[8]; if (ms > 0 && ms <= 9900) { /* convert milliseconds to tenths, rounding up */ @@ -17326,13 +18065,16 @@ TimeString (long ms) minute = second / 60; second = second % 60; + if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time + else *moveTime = NULLCHAR; + if (day > 0) - snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ", - sign, day, hour, minute, second); + snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ", + sign, day, hour, minute, second, moveTime); else if (hour > 0) - snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second); + snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime); else - snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second); + snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime); return buf; } @@ -17446,7 +18188,7 @@ char * PositionToFEN (int move, char *overrideCastling, int moveCounts) { int i, j, fromX, fromY, toX, toY; - int whiteToPlay; + int whiteToPlay, haveRights = nrCastlingRights; char buf[MSG_SIZ]; char *p, *q; int emptycount; @@ -17457,7 +18199,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) p = buf; /* Piece placement data */ - for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) { if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); } emptycount = 0; for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { @@ -17473,12 +18215,13 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) if(PieceToChar(piece) == '+') { /* [HGM] write promoted pieces as '+' (Shogi) */ *p++ = '+'; - piece = (ChessSquare)(DEMOTED piece); + piece = (ChessSquare)(CHUDEMOTED(piece)); } *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece)); + if(*p = PieceSuffix(piece)) p++; if(p[-1] == '~') { /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ - p[-1] = PieceToChar((ChessSquare)(DEMOTED piece)); + p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece))); *p++ = '~'; } } @@ -17519,12 +18262,41 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) *p++ = whiteToPlay ? 'w' : 'b'; *p++ = ' '; + if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling + haveRights = 0; q = p; + for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) { + piece = boards[move][0][i]; + if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move + if(!(boards[move][TOUCHED_W] & 1<=BOARD_LEFT; i--) { + piece = boards[move][BOARD_HEIGHT-1][i]; + if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move + if(!(boards[move][TOUCHED_B] & 1<=BOARD_LEFT; i--) + if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a'; + } else { /* [HGM] write directly from rights */ if(boards[move][CASTLING][2] != NoRights && boards[move][CASTLING][0] != NoRights ) @@ -17532,36 +18304,40 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) if(boards[move][CASTLING][2] != NoRights && boards[move][CASTLING][1] != NoRights ) *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a'; + } + if(handB) { + for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) + if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA; + } else { if(boards[move][CASTLING][5] != NoRights && boards[move][CASTLING][3] != NoRights ) *p++ = boards[move][CASTLING][3] + AAA; if(boards[move][CASTLING][5] != NoRights && boards[move][CASTLING][4] != NoRights ) *p++ = boards[move][CASTLING][4] + AAA; + } } else { /* [HGM] write true castling rights */ if( nrCastlingRights == 6 ) { int q, k=0; - if(boards[move][CASTLING][0] == BOARD_RGHT-1 && + if(boards[move][CASTLING][0] != NoRights && boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K'; - q = (boards[move][CASTLING][1] == BOARD_LEFT && + q = (boards[move][CASTLING][1] != NoRights && boards[move][CASTLING][2] != NoRights ); - if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces - for(i=j=0; i=BOARD_LEFT+q && j; i--) + if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q + for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--) if((boards[move][0][i] != WhiteKing || k+q == 0) && boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a'; } if(q) *p++ = 'Q'; k = 0; - if(boards[move][CASTLING][3] == BOARD_RGHT-1 && + if(boards[move][CASTLING][3] != NoRights && boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k'; - q = (boards[move][CASTLING][4] == BOARD_LEFT && + q = (boards[move][CASTLING][4] != NoRights && boards[move][CASTLING][5] != NoRights ); - if(gameInfo.variant == VariantSChess) { - for(i=j=0; i=BOARD_LEFT+q && j; i--) + if(handB) { + for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--) if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) && boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA; } @@ -17632,15 +18408,17 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) Boolean ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) { - int i, j, k, w=0; + int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1; char *p, c; int emptycount, virgin[BOARD_FILES]; - ChessSquare piece; + ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing); p = fen; + for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j= 0; i--) { + for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) { j = 0; for (;;) { if (*p == '/' || *p == ' ' || *p == '[' ) { @@ -17649,14 +18427,14 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; if (*p == '/') p++; - else if(autoSize) { // we stumbled unexpectedly into end of board + else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board for(k=i; k= 10) +#if(BOARD_FILES >= 10)*0 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */ p++; emptycount=10; if (j + emptycount > gameInfo.boardWidth) return FALSE; @@ -17671,22 +18449,41 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) if (j + emptycount > gameInfo.boardWidth) return FALSE; while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; + } else if (*p == '<') { + if(i == BOARD_HEIGHT-1) shuffle = 1; + else if (i != 0 || !shuffle) return FALSE; + p++; + } else if (shuffle && *p == '>') { + p++; // for now ignore closing shuffle range, and assume rank-end + } else if (*p == '?') { + if (j >= gameInfo.boardWidth) return FALSE; + if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank + board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder } else if (*p == '+' || isalpha(*p)) { + char *q, *s = SUFFIXES; if (j >= gameInfo.boardWidth) return FALSE; if(*p=='+') { - piece = CharToPiece(*++p); + char c = *++p; + if(q = strchr(s, p[1])) p++; + piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0)); if(piece == EmptySquare) return FALSE; /* unknown piece */ - piece = (ChessSquare) (CHUPROMOTED piece ); p++; + piece = (ChessSquare) (CHUPROMOTED(piece)); p++; if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */ - } else piece = CharToPiece(*p++); + } else { + char c = *p++; + if(q = strchr(s, *p)) p++; + piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0)); + } if(piece==EmptySquare) return FALSE; /* unknown piece */ if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */ - piece = (ChessSquare) (PROMOTED piece); + piece = (ChessSquare) (PROMOTED(piece)); if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */ p++; } board[i][(j++)+gameInfo.holdingsWidth] = piece; + if(piece == king) wKingRank = i; + if(piece == WHITE_TO_BLACK king) bKingRank = i; } else { return FALSE; } @@ -17694,7 +18491,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) } while (*p == '/' || *p == ' ') p++; - if(autoSize) appData.NrFiles = w, InitPosition(TRUE); + if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE); /* [HGM] by default clear Crazyhouse holdings, if present */ if(gameInfo.holdingsWidth) { @@ -17709,7 +18506,9 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) /* [HGM] look for Crazyhouse holdings here */ while(*p==' ') p++; if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') { + int swap=0, wcnt=0, bcnt=0; if(*p == '[') p++; + if(*p == '<') swap++, p++; if(*p == '-' ) p++; /* empty holdings */ else { if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */ /* if we would allow FEN reading to set board size, we would */ @@ -17722,18 +18521,46 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) if( i >= gameInfo.holdingsSize ) return FALSE; board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */ board[BOARD_HEIGHT-1-i][1]++; /* black counts */ + bcnt++; } else { i = (int)piece - (int)WhitePawn; i = PieceToNumber((ChessSquare)i); if( i >= gameInfo.holdingsSize ) return FALSE; board[i][BOARD_WIDTH-1] = piece; /* white holdings */ board[i][BOARD_WIDTH-2]++; /* black holdings */ + wcnt++; + } + } + if(subst) { // substitute back-rank question marks by holdings pieces + for(j=BOARD_LEFT; j= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible + for(k=0, m=n; k= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank; + if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank; + } + /* set defaults in case FEN is incomplete */ board[EP_STATUS] = EP_UNKNOWN; + board[TOUCHED_W] = board[TOUCHED_B] = 0; for(i=0; i= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') { /* castling indicator present, so default becomes no castlings */ @@ -17782,49 +18632,53 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) } } while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' || - (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) && + (appData.fischerCastling || gameInfo.variant == VariantSChess) && ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) || ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { int c = *p++, 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; + if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn + && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights; + if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn + && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights; switch(c) { case'K': - for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--); + for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--); board[CASTLING][0] = i != whiteKingFile ? i : NoRights; board[CASTLING][2] = whiteKingFile; if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W; if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W; + if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1; break; case'Q': - for(i=BOARD_LEFT; i>1|| i != BOARD_LEFT) fischer = 1; break; case'k': - for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--); + for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--); board[CASTLING][3] = i != blackKingFile ? i : NoRights; board[CASTLING][5] = blackKingFile; if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B; if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B; + if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1; break; case'q': - for(i=BOARD_LEFT; i>1|| i != BOARD_LEFT) fischer = 1; case '-': break; default: /* FRC castlings */ @@ -17858,7 +18712,9 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) } for(i=0; i