X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=859a47f22594cc42cc1e1b41c612a696d5086392;hb=86517e539f33650be656c482020a878fec0723c9;hp=f3cffc5fbc7279fb084ae5b68af95ac1f703750c;hpb=7e4f4f5718bf4efee8be4abfc981d18cfd6f8438;p=xboard.git diff --git a/backend.c b/backend.c index f3cffc5..859a47f 100644 --- a/backend.c +++ b/backend.c @@ -56,6 +56,7 @@ #else #define DoSleep( n ) +typedef int BOOL; #endif @@ -214,12 +215,19 @@ int string_to_rating P((char *str)); void ParseFeatures P((char* args, ChessProgramState *cps)); void InitBackEnd3 P((void)); void FeatureDone P((ChessProgramState* cps, int val)); -void InitChessProgram P((ChessProgramState *cps)); +void InitChessProgram P((ChessProgramState *cps, int setup)); -void GetInfoFromComment( int, char * ); +char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment extern int tinyLayout, smallLayout; static ChessProgramStats programStats; +static int exiting = 0; /* [HGM] moved to top */ +static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0; +extern int startedFromPositionFile; +int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */ +char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */ +int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */ +VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */ /* States for ics_getting_history */ #define H_FALSE 0 @@ -292,7 +300,12 @@ static char * safeStrCat( char * dst, const char * src, size_t count ) } /* Fake up flags for now, as we aren't keeping track of castling - availability yet */ + availability yet. [HGM] Change of logic: the flag now only + indicates the type of castlings allowed by the rule of the game. + The actual rights themselves are maintained in the array + castlingRights, as part of the game history, and are not probed + by this function. + */ int PosFlags(index) { @@ -310,7 +323,12 @@ PosFlags(index) case VariantKriegspiel: flags |= F_KRIEGSPIEL_CAPTURE; break; + case VariantCapaRandom: + case VariantFischeRandom: + flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */ case VariantNoCastle: + case VariantShatranj: + case VariantCourier: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -383,6 +401,7 @@ int have_sent_ICS_logon = 0; int movesPerSession; long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement; long timeControl_2; /* [AS] Allow separate time controls */ +char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0; TimeMark programStartTime; @@ -405,10 +424,13 @@ Board boards[MAX_MOVES]; char epStatus[MAX_MOVES]; char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1 char castlingRank[BOARD_SIZE]; // and corresponding ranks -char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE]; +char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE]; int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; char FENepStatus; +FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) +int loadFlag = 0; +int shuffleOpenings; ChessSquare FIDEArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, @@ -424,7 +446,6 @@ ChessSquare twoKingsArray[2][BOARD_SIZE] = { BlackKing, BlackKing, BlackKnight, BlackRook } }; -#ifdef FAIRY ChessSquare KnightmateArray[2][BOARD_SIZE] = { { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen, WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook }, @@ -440,10 +461,10 @@ ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King }; ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */ - { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz, - WhiteKing, WhiteAlfil, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackAlfil, BlackFerz, - BlackKing, BlackAlfil, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing, + WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackKing, + BlackFerz, BlackAlfil, BlackKnight, BlackRook } }; @@ -463,23 +484,41 @@ ChessSquare XiangqiArray[2][BOARD_SIZE] = { }; ChessSquare CapablancaArray[2][BOARD_SIZE] = { - { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen, + { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen, + { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook } }; +ChessSquare JanusArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, + WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook }, + { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, + BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook } +}; + #ifdef GOTHIC ChessSquare GothicArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, - WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook }, + WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, - BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook } + BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook } }; #else // !GOTHIC #define GothicArray CapablancaArray #endif // !GOTHIC +#ifdef FALCON +ChessSquare FalconArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, + WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, + BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook } +}; +#else // !FALCON +#define FalconArray CapablancaArray +#endif // !FALCON + #else // !(BOARD_SIZE>=10) #define XiangqiPosition FIDEArray #define CapablancaArray FIDEArray @@ -496,7 +535,6 @@ ChessSquare CourierArray[2][BOARD_SIZE] = { #else // !(BOARD_SIZE>=12) #define CourierArray CapablancaArray #endif // !(BOARD_SIZE>=12) -#endif // FAIRY Board initialPosition; @@ -525,7 +563,7 @@ ClearProgramStats() programStats.nr_moves = 0; programStats.moves_left = 0; programStats.nodes = 0; - programStats.time = 100; + programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output programStats.score = 0; programStats.got_only_move = 0; programStats.got_fail = 0; @@ -640,6 +678,8 @@ InitBackEnd1() first.useSigterm = second.useSigterm = TRUE; first.reuse = appData.reuseFirst; second.reuse = appData.reuseSecond; + first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second + second.nps = appData.secondNPS; first.useSetboard = second.useSetboard = FALSE; first.useSAN = second.useSAN = FALSE; first.usePing = second.usePing = FALSE; @@ -666,6 +706,27 @@ InitBackEnd1() first.useOOCastle = TRUE; second.useOOCastle = TRUE; /* End of new features added by Tord. */ + /* [HGM] time odds: set factor for each machine */ + first.timeOdds = appData.firstTimeOdds; + second.timeOdds = appData.secondTimeOdds; + { int norm = 1; + if(appData.timeOddsMode) { + norm = first.timeOdds; + if(norm > second.timeOdds) norm = second.timeOdds; + } + first.timeOdds /= norm; + second.timeOdds /= norm; + } + + /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ + first.accumulateTC = appData.firstAccumulateTC; + second.accumulateTC = appData.secondAccumulateTC; + first.maxNrOfSessions = second.maxNrOfSessions = 1; + + /* [HGM] debug */ + first.debug = second.debug = FALSE; + first.supportsNPS = second.supportsNPS = UNKNOWN; + first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */ second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */ first.isUCI = appData.firstIsUCI; /* [AS] */ @@ -762,14 +823,15 @@ InitBackEnd1() case VariantCapablanca: /* [HGM] should work */ case VariantCourier: /* [HGM] initial forced moves not implemented */ case VariantShogi: /* [HGM] drops not tested for legality */ - case VariantShowgi: /* [HGM] not a valid variant */ case VariantKnightmate: /* [HGM] should work */ + case VariantCylinder: /* [HGM] untested */ + 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: /* Fabien: pieces not automatically shuffled */ + case VariantFischeRandom: /* [HGM] works and shuffles pieces */ case VariantLosers: /* should work except for win condition, and doesn't know captures are mandatory */ case VariantSuicide: /* should work except for win condition, @@ -779,7 +841,9 @@ InitBackEnd1() case VariantTwoKings: /* should work */ case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ - case VariantShatranj: /* might work if TestLegality is off */ + case VariantShatranj: /* should work except for all win conditions */ + case VariantBerolina: /* might work if TestLegality is off */ + case VariantCapaRandom: /* should work */ break; } } @@ -827,22 +891,51 @@ int NextTimeControlFromString( char ** str, long * value ) return result; } -int GetTimeControlForWhite() -{ - int result = timeControl; +int NextSessionFromString( char ** str, int *moves, long * tc, long *inc) +{ /* [HGM] routine added to read '+moves/time' for secondary time control */ + int result = -1; long temp, temp2; + if(**str != '+') return -1; // old params remain in force! + (*str)++; + if( NextTimeControlFromString( str, &temp ) ) return -1; + + if(**str != '/') { + /* time only: incremental or sudden-death time control */ + if(**str == '+') { /* increment follows; read it */ + (*str)++; + if(result = NextIntegerFromString( str, &temp2)) return -1; + *inc = temp2 * 1000; + } else *inc = 0; + *moves = 0; *tc = temp * 1000; + return 0; + } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */ + + (*str)++; /* classical time control */ + result = NextTimeControlFromString( str, &temp2); + if(result == 0) { + *moves = temp/60; + *tc = temp2 * 1000; + *inc = 0; + } return result; } -int GetTimeControlForBlack() -{ - int result = timeControl; +int GetTimeQuota(int movenr) +{ /* [HGM] get time to add from the multi-session time-control string */ + int moves=1; /* kludge to force reading of first session */ + long time, increment; + char *s = fullTimeControlString; - if( timeControl_2 > 0 ) { - result = timeControl_2; - } + if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString); + do { + if(moves) NextSessionFromString(&s, &moves, &time, &increment); + if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment); + if(movenr == -1) return time; /* last move before new session */ + if(!moves) return increment; /* current session is incremental */ + if(movenr >= 0) movenr -= moves; /* we already finished this session */ + } while(movenr >= -1); /* try again for next session */ - return result; + return 0; // no new time quota on this move } int @@ -865,6 +958,19 @@ ParseTimeControl(tc, ti, mps) #else long tc1; long tc2; + char buf[MSG_SIZ]; + + if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; + if(ti > 0) { + if(mps) + sprintf(buf, "+%d/%s+%d", mps, tc, ti); + else sprintf(buf, "+%s+%d", tc, ti); + } else { + if(mps) + sprintf(buf, "+%d/%s", mps, tc); + else sprintf(buf, "+%s", tc); + } + fullTimeControlString = StrSave(buf); if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { return FALSE; @@ -935,7 +1041,7 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err; - InitChessProgram(&first); + InitChessProgram(&first, startedFromSetupPosition); if (appData.icsActive) { err = establish(); @@ -1038,6 +1144,19 @@ InitBackEnd3 P((void)) (void) LoadPositionFromFile(appData.loadPositionFile, appData.loadPositionIndex, appData.loadPositionFile); + /* [HGM] try to make self-starting even after FEN load */ + /* to allow automatic setup of fairy variants with wtm */ + if(initialMode == BeginningOfGame && !blackPlaysFirst) { + gameMode = BeginningOfGame; + setboardSpoiledMachineBlack = 1; + } + /* [HGM] loadPos: make that every new game uses the setup */ + /* from file as long as we do not switch variant */ + if(!blackPlaysFirst) { int i; + startedFromPositionFile = TRUE; + CopyBoard(filePosition, boards[0]); + for(i=0; i= BlackPawn ) { holdingsColumn = 0; countsColumn = 1; holdingsStartRow = BOARD_HEIGHT-1; - direction = 1; + direction = -1; } else { holdingsColumn = BOARD_WIDTH-1; countsColumn = BOARD_WIDTH-2; @@ -1723,16 +1852,105 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece) while( (p=*holdings++) != NULLCHAR ) { piece = CharToPiece( ToUpper(p) ); if(piece == EmptySquare) continue; - j = (int) piece - (int) WhitePawn; + /*j = (int) piece - (int) WhitePawn;*/ + j = PieceToNumber(piece); if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */ if(j < 0) continue; /* should not happen */ - piece = (ChessSquare) ( (int)piece + (int)lowestPiece ); - board[holdingsStartRow+i*direction][holdingsColumn] = piece; - board[holdingsStartRow+i*direction][countsColumn]++; + piece = (ChessSquare) ( j + (int)lowestPiece ); + board[holdingsStartRow+j*direction][holdingsColumn] = piece; + board[holdingsStartRow+j*direction][countsColumn]++; } } + +void +VariantSwitch(Board board, VariantClass newVariant) +{ + int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j; + int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove; + Board tempBoard; int saveCastling[BOARD_SIZE], saveEP; + + startedFromPositionFile = FALSE; + if(gameInfo.variant == newVariant) return; + + /* [HGM] This routine is called each time an assignment is made to + * gameInfo.variant during a game, to make sure the board sizes + * are set to match the new variant. If that means adding or deleting + * holdings, we shift the playing board accordingly + * This kludge is needed because in ICS observe mode, we get boards + * of an ongoing game without knowing the variant, and learn about the + * latter only later. This can be because of the move list we requested, + * in which case the game history is refilled from the beginning anyway, + * but also when receiving holdings of a crazyhouse game. In the latter + * case we want to add those holdings to the already received position. + */ + + + if (appData.debugMode) { + fprintf(debugFP, "Switch board from %s to %s\n", + VariantName(gameInfo.variant), VariantName(newVariant)); + setbuf(debugFP, NULL); + } + shuffleOpenings = 0; /* [HGM] shuffle */ + gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */ + switch(newVariant) { + case VariantShogi: + newWidth = 9; newHeight = 9; + gameInfo.holdingsSize = 7; + case VariantBughouse: + case VariantCrazyhouse: + newHoldingsWidth = 2; break; + default: + newHoldingsWidth = gameInfo.holdingsSize = 0; + } + + if(newWidth != gameInfo.boardWidth || + newHeight != gameInfo.boardHeight || + newHoldingsWidth != gameInfo.holdingsWidth ) { + + /* shift position to new playing area, if needed */ + if(newHoldingsWidth > gameInfo.holdingsWidth) { + for(i=0; i=BOARD_LEFT; j--) + board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = + board[i][j]; + for(i=0; i 0) { /* If last read ended with a partial line that we couldn't parse, prepend it to the new read and try again. */ @@ -2331,11 +2553,11 @@ read_from_ics(isr, closure, data, count, error) "* * match, initial time: * minute*, increment: * second")) { /* Header for a move list -- second line */ /* Initial board will follow if this is a wild game */ - if (gameInfo.event != NULL) free(gameInfo.event); sprintf(str, "ICS %s %s match", star_match[0], star_match[1]); gameInfo.event = StrSave(str); - gameInfo.variant = StringToVariant(gameInfo.event); + /* [HGM] we switched variant. Translate boards if needed. */ + VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event)); continue; } @@ -2708,6 +2930,7 @@ read_from_ics(isr, closure, data, count, error) /* Send "new" early, in case this command takes a long time to finish, so that we'll be ready for the next challenge. */ + gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant' Reset(TRUE, TRUE); } #endif /*ZIPPY*/ @@ -2800,11 +3023,17 @@ read_from_ics(isr, closure, data, count, error) started = STARTED_NONE; parse[parse_pos] = NULLCHAR; if (appData.debugMode) - fprintf(debugFP, "Parsing holdings: %s\n", parse); + fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n", + parse, currentMove); if (sscanf(parse, " game %d", &gamenum) == 1 && gamenum == ics_gamenum) { if (gameInfo.variant == VariantNormal) { - gameInfo.variant = VariantCrazyhouse; /*temp guess*/ + /* [HGM] We seem to switch variant during a game! + * Presumably no holdings were displayed, so we have + * to move the position two files to the right to + * create room for them! + */ + VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ if (ics_getting_history == H_FALSE) { @@ -2819,6 +3048,9 @@ read_from_ics(isr, closure, data, count, error) new_piece); white_holding[strlen(white_holding)-1] = NULLCHAR; black_holding[strlen(black_holding)-1] = NULLCHAR; + /* [HGM] copy holdings to board holdings area */ + CopyHoldings(boards[currentMove], white_holding, WhitePawn); + CopyHoldings(boards[currentMove], black_holding, BlackPawn); #if ZIPPY if (appData.zippyPlay && first.initDone) { ZippyHoldings(white_holding, black_holding, @@ -2837,10 +3069,7 @@ read_from_ics(isr, closure, data, count, error) gameInfo.black, black_holding); } - /* [HGM] copy holdings to board holdings area */ - CopyHoldings(boards[currentMove], white_holding, WhitePawn); - CopyHoldings(boards[currentMove], black_holding, BlackPawn); - DrawPosition(FALSE, NULL); + DrawPosition(FALSE, boards[currentMove]); DisplayTitle(str); } /* Suppress following prompt */ @@ -3056,21 +3285,13 @@ ParseBoard12(string) timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); - gameInfo.variant = StringToVariant(gameInfo.event); - gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */ - switch(gameInfo.variant) { - case VariantShogi: - case VariantShowgi: - gameInfo.boardWidth = gameInfo.boardHeight = 9; - gameInfo.holdingsSize += 2; - case VariantBughouse: - case VariantCrazyhouse: - gameInfo.boardWidth = gameInfo.boardHeight = 8; - gameInfo.holdingsWidth = 2; break; - default: - gameInfo.boardWidth = gameInfo.boardHeight = 8; - gameInfo.holdingsWidth = 0; - } + VariantSwitch(board, StringToVariant(gameInfo.event) ); + if (appData.debugMode) { + fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event); + fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant)); + setbuf(debugFP, NULL); + } + gameInfo.outOfBook = NULL; /* Do we have the ratings? */ @@ -3132,14 +3353,69 @@ ParseBoard12(string) } /* Parse the board */ - for (k = 0; k < 8; k++) + for (k = 0; k < 8; k++) { for (j = 0; j < 8; j++) - board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]); + board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]); + if(gameInfo.holdingsWidth > 1) { + board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare; + board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;; + } + } CopyBoard(boards[moveNum], board); if (moveNum == 0) { startedFromSetupPosition = !CompareBoards(board, initialPosition); - } + if(startedFromSetupPosition) + initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */ + } + + /* [HGM] Set castling rights. Take the outermost Rooks, + to make it also work for FRC opening positions. Note that board12 + is really defective for later FRC positions, as it has no way to + indicate which Rook can castle if they are on the same side of King. + For the initial position we grant rights to the outermost Rooks, + and remember thos rights, and we then copy them on positions + later in an FRC game. This means WB might not recognize castlings with + Rooks that have moved back to their original position as illegal, + but in ICS mode that is not its job anyway. + */ + if(moveNum == 0 || gameInfo.variant != VariantFischeRandom) + { int i, j; + + for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + if(board[0][i] == WhiteRook) j = i; + initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); + for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; + initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); + + for(k=BOARD_LEFT; k 0) { + if (appData.debugMode) { + if (appData.debugMode) { int f = forwardMostMove; + fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f, + castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); + } + fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str); + fprintf(debugFP, "moveNum = %d\n", moveNum); + fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT); + setbuf(debugFP, NULL); + } if (moveNum <= backwardMostMove) { /* We don't know what the board looked like before this move. Punt. */ @@ -3222,7 +3508,8 @@ ParseBoard12(string) default: break; case MT_CHECK: - strcat(parseList[moveNum - 1], "+"); + if(gameInfo.variant != VariantShogi) + strcat(parseList[moveNum - 1], "+"); break; case MT_CHECKMATE: strcat(parseList[moveNum - 1], "#"); @@ -3243,6 +3530,10 @@ ParseBoard12(string) fromX = fromY = toX = toY = -1; } else { /* Move from ICS was illegal!? Punt. */ + if (appData.debugMode) { + fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str); + fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); + } #if 0 if (appData.testLegality && appData.debugMode) { sprintf(str, "Illegal move \"%s\" from ICS", move_str); @@ -3255,6 +3546,10 @@ ParseBoard12(string) moveList[moveNum - 1][0] = NULLCHAR; fromX = fromY = toX = toY = -1; } + if (appData.debugMode) { + fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]); + setbuf(debugFP, NULL); + } #if ZIPPY /* Send move to chess program (BEFORE animating it). */ @@ -3289,6 +3584,7 @@ ParseBoard12(string) sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str); DisplayError(str, 0); } else { + if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand! SendMoveToProgram(moveNum - 1, &first); } } @@ -3328,15 +3624,28 @@ ParseBoard12(string) if (gameInfo.variant != VariantBughouse && gameInfo.variant != VariantCrazyhouse) { if (tinyLayout || smallLayout) { - sprintf(str, "%s(%d) %s(%d) {%d %d}", + if(gameInfo.variant == VariantNormal) + sprintf(str, "%s(%d) %s(%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); + else + sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", + gameInfo.white, white_stren, gameInfo.black, black_stren, + basetime, increment, (int) gameInfo.variant); } else { - sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", + if(gameInfo.variant == VariantNormal) + sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); + else + sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", + gameInfo.white, white_stren, gameInfo.black, black_stren, + basetime, increment, VariantName(gameInfo.variant)); } DisplayTitle(str); + if (appData.debugMode) { + fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant); + } } @@ -3393,6 +3702,7 @@ SendMoveToProgram(moveNum, cps) ChessProgramState *cps; { char buf[MSG_SIZ]; + if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -3406,25 +3716,24 @@ SendMoveToProgram(moveNum, cps) } else { sprintf(buf, "%s\n", parseList[moveNum]); } - /* [HGM] decrement all digits to code ranks starting from 0 */ - if(BOARD_HEIGHT>8) { - char *p = buf; - while(*p) { if(*p < 'A') (*p)--; p++; } - } SendToProgram(buf, cps); } else { /* 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 && cps->useOOCastle) { + if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) + && cps->useOOCastle) { + if (appData.debugMode) { + fprintf(debugFP, "Tord's FRC castling code\n"); + } int fromX = moveList[moveNum][0] - AAA; int fromY = moveList[moveNum][1] - ONE; int toX = moveList[moveNum][2] - AAA; int toY = moveList[moveNum][3] - ONE; - if((boards[currentMove][fromY][fromX] == WhiteKing - && boards[currentMove][toY][toX] == WhiteRook) - || (boards[currentMove][fromY][fromX] == BlackKing - && boards[currentMove][toY][toX] == BlackRook)) { + if((boards[moveNum][fromY][fromX] == WhiteKing + && boards[moveNum][toY][toX] == WhiteRook) + || (boards[moveNum][fromY][fromX] == BlackKing + && boards[moveNum][toY][toX] == BlackRook)) { if(toX > fromX) SendToProgram("O-O\n", cps); else SendToProgram("O-O-O\n", cps); } @@ -3433,6 +3742,21 @@ SendMoveToProgram(moveNum, cps) else SendToProgram(moveList[moveNum], cps); /* End of additions by Tord */ } + + /* [HGM] setting up the opening has brought engine in force mode! */ + /* Send 'go' if we are in a mode where machine should play. */ + if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) && + (gameMode == TwoMachinesPlay || +#ifdef ZIPPY + gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite || +#endif + gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) { + SendToProgram("go\n", cps); + if (appData.debugMode) { + fprintf(debugFP, "(extra)\n"); + } + } + setboardSpoiledMachineBlack = 0; } void @@ -3478,13 +3802,16 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackPromotionKnight: case WhitePromotionKing: case BlackPromotionKing: -#ifdef FAIRY case WhitePromotionChancellor: case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: -#endif - sprintf(user_move, "%c%c%c%c=%c\n", + if(gameInfo.variant == VariantShatranj) + sprintf(user_move, "%c%c%c%c=%c\n", + AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, + PieceToChar(WhiteFerz)); + else + sprintf(user_move, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, PieceToChar(PromoPiece(moveType))); break; @@ -3544,15 +3871,50 @@ ProcessICSInitScript(f) void AlphaRank(char *move, int n) { - char *p = move, c; + char *p = move, c; int x, y; if( !appData.alphaRank ) return; - while(c = *p) { - if(c>='0' && c<='9') *p += 'a'-'0'; else - if(c>='a' && c<='z') *p -= 'a'-'0'; - p++; - if(--n < 1) break; + if (appData.debugMode) { + fprintf(debugFP, "alphaRank(%s,%d)\n", move, n); + } + + if(move[1]=='*' && + move[2]>='0' && move[2]<='9' && + move[3]>='a' && move[3]<='x' ) { + move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA; + move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE; + } else + if(move[0]>='0' && move[0]<='9' && + move[1]>='a' && move[1]<='x' && + move[2]>='0' && move[2]<='9' && + move[3]>='a' && move[3]<='x' ) { + /* input move, Shogi -> normal */ + move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA; + move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE; + move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA; + move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE; + } else + if(move[1]=='@' && + move[3]>='0' && move[3]<='9' && + move[2]>='a' && move[2]<='x' ) { + move[1] = '*'; + move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1'; + move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a'; + } else + if( + move[0]>='a' && move[0]<='x' && + move[3]>='0' && move[3]<='9' && + move[2]>='a' && move[2]<='x' ) { + /* output move, normal -> Shogi */ + move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1'; + move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a'; + move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1'; + move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a'; + if(move[4] == PieceToChar(BlackQueen)) move[4] = '+'; + } + if (appData.debugMode) { + fprintf(debugFP, " out = '%s'\n", move); } } @@ -3568,16 +3930,13 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) if (appData.debugMode) { fprintf(debugFP, "move to parse: %s\n", move); } - AlphaRank(move, 10); *moveType = yylexstr(moveNum, move); switch (*moveType) { -#ifdef FAIRY case WhitePromotionChancellor: case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: -#endif case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -3613,6 +3972,9 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) *promoChar = currentMoveString[4]; 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) { + fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType); + } *fromX = *fromY = *toX = *toY = 0; return FALSE; } @@ -3626,7 +3988,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackDrop: *fromX = *moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : - (int) CharToPiece(ToLower(currentMoveString[0])); + (int) CharToPiece(ToLower(currentMoveString[0])); *fromY = DROP_RANK; *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; @@ -3644,6 +4006,9 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackWins: case GameIsDrawn: default: + if (appData.debugMode) { + fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType); + } /* bug? */ *fromX = *fromY = *toX = *toY = 0; *promoChar = NULLCHAR; @@ -3667,6 +4032,7 @@ static int FindEmptySquare( Board board, int n ) return i; } +#if 0 static void ShuffleFRC( Board board ) { int i; @@ -3682,9 +4048,15 @@ static void ShuffleFRC( Board board ) board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen; board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight; board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight; - board[0][FindEmptySquare(board, 0)] = WhiteRook; - board[0][FindEmptySquare(board, 0)] = WhiteKing; - board[0][FindEmptySquare(board, 0)] = WhiteRook; + board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook; + initialRights[1] = initialRights[4] = + castlingRights[0][1] = castlingRights[0][4] = i; + board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing; + initialRights[2] = initialRights[5] = + castlingRights[0][2] = castlingRights[0][5] = i; + board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook; + initialRights[0] = initialRights[3] = + castlingRights[0][0] = castlingRights[0][3] = i; for( i=BOARD_LEFT; i= j) i -= j--; + j = n - 1 - j; i += j; + put(board, pieceType, rank, j, ANY); + put(board, pieceType, rank, i, ANY); +} + +void SetUpShuffle(Board board, int number) +{ + int i, p, first=1; + + seed = number, nrOfShuffles = 1; + if(number < 0) { + srand(time(0)); + for(i=0; i<50; i++) seed += rand(); + seed = rand() ^ rand()<<16; + } + + squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2; + squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT; + squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK]; + + for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0; + + for(i=BOARD_LEFT; i (int) WhitePawn; p--) { + if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue; + while(piecesLeft[p] >= 2) { + AddOnePiece(board, p, 0, LITE); + AddOnePiece(board, p, 0, DARK); + } + // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares) + } + + for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) { + // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere + // but we leave King and Rooks for last, to possibly obey FRC restriction + if(p == (int)WhiteRook) continue; + while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations + if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece + } + + // now everything is placed, except perhaps King (Unicorn) and Rooks + + if(PosFlags(0) & F_FRC_TYPE_CASTLING) { + // Last King gets castling rights + while(piecesLeft[(int)WhiteUnicorn]) { + i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY); + initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + } + + while(piecesLeft[(int)WhiteKing]) { + i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY); + initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + } + + + } else { + while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY); + while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY); + } + + // Only Rooks can be left; simply place them all + while(piecesLeft[(int)WhiteRook]) { + i = put(board, WhiteRook, 0, 0, ANY); + if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights + if(first) { + first=0; + initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i; + } + initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i; + } + } + for(i=BOARD_LEFT; i= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize +} + +#endif + +BOOL SetCharTable( char *table, const char * map ) +/* [HGM] moved here from winboard.c because of its general usefulness */ +/* Basically a safe strcpy that uses the last character as King */ +{ + BOOL result = FALSE; int NrPieces; + + if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare + && NrPieces >= 12 && !(NrPieces&1)) { + int i; /* [HGM] Accept even length from 12 to 34 */ + + for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.'; + for( i=0; i>1; - castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; - castlingRights[0][4] = initialRights[4] = BOARD_LEFT; - castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1; + initialRulePlies = 0; /* 50-move counter start */ castlingRank[0] = castlingRank[1] = castlingRank[2] = 0; castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1; - - initialRulePlies = 0; /* 50-move counter start */ } @@ -3785,15 +4329,20 @@ InitPosition(redraw) gameInfo.boardWidth = 8; gameInfo.boardHeight = 8; gameInfo.holdingsSize = 0; + nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */ + for(i=0; i>1; + castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; + castlingRights[0][4] = initialRights[4] = BOARD_LEFT; + castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1; + break; + case VariantFalcon: + pieces = FalconArray; + gameInfo.boardWidth = 10; + SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); break; case VariantXiangqi: pieces = XiangqiArray; gameInfo.boardWidth = 9; gameInfo.boardHeight = 10; nrCastlingRights = 0; - for(i=0; i= 0) { - if(gameInfo.boardHeight != appData.NrRanks) overrule++; gameInfo.boardHeight = appData.NrRanks; } if(appData.holdingsSize >= 0) { @@ -3889,11 +4448,12 @@ InitPosition(redraw) if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE) DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2); - pawnRow = gameInfo.boardHeight - 7; /* seems to work in all variants */ + pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */ + if(pawnRow < 1) pawnRow = 1; /* User pieceToChar list overrules defaults */ if(appData.pieceToCharTable != NULL) - strcpy(pieceToChar, appData.pieceToCharTable); + SetCharTable(pieceToChar, appData.pieceToCharTable); for( j=0; j>1; + castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; + castlingRights[0][4] = initialRights[4] = BOARD_LEFT; + castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1; + } + +#if 0 if(gameInfo.variant == VariantFischeRandom) { if( appData.defaultFrcPosition < 0 ) { ShuffleFRC( initialPosition ); @@ -3936,14 +4510,40 @@ InitPosition(redraw) else { SetupFRC( initialPosition, appData.defaultFrcPosition ); } + startedFromSetupPosition = TRUE; + } else +#else + if (appData.debugMode) { + fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings); + } + if(shuffleOpenings) { + SetUpShuffle(initialPosition, appData.defaultFrcPosition); + startedFromSetupPosition = TRUE; + } +#endif + if(startedFromPositionFile) { + /* [HGM] loadPos: use PositionFile for every new game */ + CopyBoard(initialPosition, filePosition); + for(i=0; i= 0; i--) { - bp = &boards[moveNum][i][0]; + bp = &boards[moveNum][i][BOARD_LEFT]; for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { if ((int) *bp < (int) BlackPawn) { sprintf(message, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i); + if(message[0] == '+' || message[0] == '~') { + sprintf(message, "%c%c%c+\n", + PieceToChar((ChessSquare)(DEMOTED *bp)), + AAA + j, ONE + i); + } + if(appData.alphaRank) { + message[1] = BOARD_RGHT - 1 - j + '1'; + message[2] = BOARD_HEIGHT - 1 - i + 'a'; + } SendToProgram(message, cps); } } @@ -3985,12 +4594,21 @@ SendBoard(cps, moveNum) SendToProgram("c\n", cps); for (i = BOARD_HEIGHT - 1; i >= 0; i--) { - bp = &boards[moveNum][i][0]; + bp = &boards[moveNum][i][BOARD_LEFT]; for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { if (((int) *bp != (int) EmptySquare) && ((int) *bp >= (int) BlackPawn)) { sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)), AAA + j, ONE + i); + if(message[0] == '+' || message[0] == '~') { + sprintf(message, "%c%c%c+\n", + PieceToChar((ChessSquare)(DEMOTED *bp)), + AAA + j, ONE + i); + } + if(appData.alphaRank) { + message[1] = BOARD_RGHT - 1 - j + '1'; + message[2] = BOARD_HEIGHT - 1 - i + 'a'; + } SendToProgram(message, cps); } } @@ -3998,6 +4616,7 @@ SendBoard(cps, moveNum) SendToProgram(".\n", cps); } + setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } int @@ -4014,7 +4633,10 @@ IsPromotion(fromX, fromY, toX, toY) piece = boards[currentMove][fromY][fromX]; if(gameInfo.variant == VariantShogi) { promotionZoneSize = 3; - highestPromotingPiece = (int)WhiteFerz; /* Silver */ + highestPromotingPiece = (int)WhiteKing; + /* [HGM] Should be Silver = Ferz, really, but legality testing is off, + and if in normal chess we then allow promotion to King, why not + allow promotion of other piece in Shogi? */ } if((int)piece >= BlackPawn) { if(toY >= promotionZoneSize && fromY >= promotionZoneSize) @@ -4042,7 +4664,7 @@ PieceForSquare (x, y) int x; int y; { - if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT) + if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1; else return boards[currentMove][y][x]; @@ -4165,14 +4787,32 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) int promoChar; { ChessMove moveType; + ChessSquare pdown, pup; if (fromX < 0 || fromY < 0) return ImpossibleMove; if ((fromX == toX) && (fromY == toY)) { return ImpossibleMove; } + /* [HGM] suppress all moves into holdings area and guard band */ - if( toX < BOARD_LEFT || toX >= BOARD_RGHT ) return ImpossibleMove; - + if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 ) + return ImpossibleMove; + + /* [HGM] moved to here from winboard.c */ + /* note: this code seems to exist for filtering out some obviously illegal premoves */ + pdown = boards[currentMove][fromY][fromX]; + pup = boards[currentMove][toY][toX]; + if ( gameMode != EditPosition && + (WhitePawn <= pdown && pdown < BlackPawn && + WhitePawn <= pup && pup < BlackPawn || + BlackPawn <= pdown && pdown < EmptySquare && + BlackPawn <= pup && pup < EmptySquare + ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && + (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0|| + pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) + ) ) + return ImpossibleMove; + /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he tried to pick up may have been captured by the time he puts it down! @@ -4278,6 +4918,8 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) break; case EditPosition: + /* EditPosition, empty square, or different color piece; + click-click move is possible */ if (toX == -2 || toY == -2) { boards[0][fromY][fromX] = EmptySquare; DrawPosition(FALSE, boards[currentMove]); @@ -4289,26 +4931,34 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) return ImpossibleMove; } - if (toX < 0 || toY < 0) return ImpossibleMove; - /* [HGM] If move started in holdings, it means a drop */ - if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { - if( boards[currentMove][toY][toX] != EmptySquare ) return ImpossibleMove; + if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + if( pup != EmptySquare ) return ImpossibleMove; + if(appData.testLegality) { + /* it would be more logical if LegalityTest() also figured out + * which drops are legal. For now we forbid pawns on back rank. + * Shogi is on its own here... + */ + if( (pdown == WhitePawn || pdown == BlackPawn) && + (toY == 0 || toY == BOARD_HEIGHT -1 ) ) + return(ImpossibleMove); /* no pawn drops on 1st/8th */ + } return WhiteDrop; /* Not needed to specify white or black yet */ } userOfferedDraw = FALSE; - if (appData.testLegality) { - moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, castlingRights[currentMove], + /* [HGM] always test for legality, to get promotion info */ + moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), + epStatus[currentMove], castlingRights[currentMove], fromY, fromX, toY, toX, promoChar); + + /* [HGM] but possibly ignore an IllegalMove result */ + if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { DisplayMoveError("Illegal move"); return ImpossibleMove; } - } else { - moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); } return moveType; @@ -4341,7 +4991,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } /* [HGM] The following if has been moved here from - UserMoveEnevt(). Because it seemed to belon here (why not allow + UserMoveEvent(). Because it seemed to belon here (why not allow piece drops in training games?), and because it can only be performed after it is known to what we promote. */ if (gameMode == Training) { @@ -4410,6 +5060,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) sprintf(buf, "name %s\n", gameInfo.white); SendToProgram(buf, &first); } + StartClocks(); } ModeHighlight(); } @@ -4532,6 +5183,11 @@ HandleMachineMove(message, cps) while (*message == '\007') message++; /* + * [HGM] engine debug message: ignore lines starting with '#' character + */ + if(cps->debug && *message == '#') return; + + /* * Look for book output */ if (cps == &first && bookRequested) { @@ -4623,6 +5279,11 @@ HandleMachineMove(message, cps) return; } + if (appData.debugMode) { int f = forwardMostMove; + fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, + castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); + } + AlphaRank(machineMove, 4); if (!ParseOneMove(machineMove, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { /* Machine move could not be parsed; ignore it. */ @@ -4643,23 +5304,40 @@ HandleMachineMove(message, cps) /* to make sure an illegal e.p. capture does not slip through, */ /* to cause a forfeit on a justified illegal-move complaint */ /* of the opponent. */ - if(gameMode==TwoMachinesPlay && appData.testLegality && - fromY != DROP_RANK && /* [HGM] temporary; should still add legality test for drops */ - LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), + if( gameMode==TwoMachinesPlay && appData.testLegality + && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */ + ) { + ChessMove moveType; + moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), epStatus[forwardMostMove], castlingRights[forwardMostMove], - fromY, fromX, toY, toX, promoChar) == IllegalMove) - { + fromY, fromX, toY, toX, promoChar); if (appData.debugMode) { int i; for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", castlingRights[forwardMostMove][i], castlingRank[i]); fprintf(debugFP, "castling rights\n"); } - sprintf(buf1, "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, - buf1, GE_XBOARD); + if(moveType == IllegalMove) { + sprintf(buf1, "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, + buf1, GE_XBOARD); + } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) + /* [HGM] Kludge to handle engines that send FRC-style castling + when they shouldn't (like TSCP-Gothic) */ + switch(moveType) { + case WhiteASideCastleFR: + case BlackASideCastleFR: + toX+=2; + currentMoveString[2]++; + break; + case WhiteHSideCastleFR: + case BlackHSideCastleFR: + toX--; + currentMoveString[2]--; + break; } + } hintRequested = FALSE; lastHint[0] = NULLCHAR; bookRequested = FALSE; @@ -4673,6 +5351,18 @@ HandleMachineMove(message, cps) first.initDone) { SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; + if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */ + char buf[3*MSG_SIZ]; + + sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n", + programStats.depth, + programStats.score / 100., + programStats.time / 100., + (double) programStats.nodes, + programStats.nodes / (10*abs(programStats.time) + 1.), + programStats.movelist); + SendToICS(buf); + } } #endif /* currentMoveString is set as a side-effect of ParseOneMove */ @@ -4683,7 +5373,7 @@ HandleMachineMove(message, cps) /* [AS] Save move info and clear stats for next move */ pvInfoList[ forwardMostMove ].score = programStats.score; pvInfoList[ forwardMostMove ].depth = programStats.depth; - pvInfoList[ forwardMostMove ].time = -1; + pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats ClearProgramStats(); thinkOutput[0] = NULLCHAR; hiddenThinkOutputState = 0; @@ -4749,7 +5439,7 @@ HandleMachineMove(message, cps) if( appData.testLegality ) { /* [HGM] Some more adjudications for obstinate engines */ int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, - NrWQ=0, NrBQ=0, + NrWQ=0, NrBQ=0, bishopsColor = 0, NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k; static int moveCount; @@ -4763,19 +5453,21 @@ HandleMachineMove(message, cps) case WhiteKnight: NrWN++; break; case WhiteBishop: + bishopsColor |= 1 << ((i^j)&1); NrWB++; break; case BlackKnight: - NrWN++; break; + NrBN++; break; case BlackBishop: + bishopsColor |= 1 << ((i^j)&1); NrBB++; break; case WhiteRook: NrWR++; break; case BlackRook: NrBR++; break; case WhiteQueen: - NrWR++; break; + NrWQ++; break; case BlackQueen: - NrBR++; break; + NrBQ++; break; case EmptySquare: break; case BlackPawn: @@ -4786,8 +5478,9 @@ HandleMachineMove(message, cps) NrPieces += (p != EmptySquare); } - if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 ) - { /* KBK, KNK or KK */ + if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 + || NrPieces == 4 && NrBB+NrWB==2 && bishopsColor != 3) + { /* KBK, KNK, KK of KBKB with like Bishops */ /* always flag draws, for judging claims */ epStatus[forwardMostMove] = EP_INSUF_DRAW; @@ -4800,6 +5493,19 @@ HandleMachineMove(message, cps) } } + /* Shatranj baring rule */ + if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) ) + { /* bare King */ + + if(--bare < 0 && appData.checkMates) { + /* but only adjudicate them if adjudication enabled */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( NrW > 1 ? WhiteWins : NrPiece - NrW > 1 ? BlackWins : GameIsDrawn, + "Xboard adjudication: Bare king", GE_XBOARD ); + return; + } + } else bare = 1; + /* Then some trivial draws (only adjudicate, cannot be claimed) */ if(NrPieces == 4 && ( NrWR == 1 && NrBR == 1 /* KRKR */ @@ -4814,7 +5520,7 @@ HandleMachineMove(message, cps) return; } } else moveCount = 6; - +#if 0 if (appData.debugMode) { int i; fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", forwardMostMove, backwardMostMove, epStatus[backwardMostMove], @@ -4823,20 +5529,26 @@ HandleMachineMove(message, cps) fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]); } +#endif /* Check for rep-draws */ count = 0; for(k = forwardMostMove-2; k>=backwardMostMove && k>=forwardMostMove-100 && - epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE; + epStatus[k] < EP_UNKNOWN && + epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE; k-=2) { int rights=0; +#if 0 if (appData.debugMode) { fprintf(debugFP, " loop\n"); } +#endif if(CompareBoards(boards[k], boards[forwardMostMove])) { +#if 0 if (appData.debugMode) { fprintf(debugFP, "match\n"); } +#endif /* compare castling rights */ if( castlingRights[forwardMostMove][2] != castlingRights[k][2] && (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) ) @@ -4854,6 +5566,7 @@ HandleMachineMove(message, cps) castlingRights[forwardMostMove][4] != castlingRights[k][4] ) rights++; } +#if 0 if (appData.debugMode) { for(i=0; i appData.drawRepeats-2 && appData.drawRepeats > 1) { /* adjudicate after user-specified nr of repeats */ @@ -4890,7 +5604,28 @@ HandleMachineMove(message, cps) ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); return; - } + } + + /* if draw offer is pending, treat it as a draw claim + * when draw condition present, to allow engines a way to + * claim draws before making their move to avoid a race + * condition occurring after their move + */ + if( cps->other->offeredDraw || cps->offeredDraw ) { + char *p = NULL; + if(epStatus[forwardMostMove] == EP_RULE_DRAW) + p = "Draw claim: 50-move rule"; + if(epStatus[forwardMostMove] == EP_REP_DRAW) + p = "Draw claim: 3-fold repetition"; + if(epStatus[forwardMostMove] == EP_INSUF_DRAW) + p = "Draw claim: insufficient mating material"; + if( p != NULL ) { + GameEnds( GameIsDrawn, p, GE_XBOARD ); + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + return; + } + } + } @@ -4905,6 +5640,11 @@ HandleMachineMove(message, cps) } if (gameMode == TwoMachinesPlay) { + /* [HGM] relaying draw offers moved to after reception of move */ + /* and interpreting offer as claim if it brings draw condition */ + if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) { + SendToProgram("draw\n", cps->other); + } if (cps->other->sendTime) { SendTimeRemaining(cps->other, cps->other->twoMachinesColor[0] == 'w'); @@ -4946,6 +5686,31 @@ HandleMachineMove(message, cps) cps->useSigterm = FALSE; } + /* [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. + */ + if (!strncmp(message, "setboard ", 9)) { + Board initial_position; int i; + + GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD); + + if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) { + DisplayError("Bad FEN received from engine", 0); + return ; + } else { + Reset(FALSE, FALSE); + CopyBoard(boards[0], initial_position); + initialRulePlies = FENrulePlies; + epStatus[0] = FENepStatus; + for( i=0; ilastPong) == 1) { - return; + return; } /* * If the move is illegal, cancel it and redraw the board. @@ -5241,6 +6006,10 @@ HandleMachineMove(message, cps) } else if (strncmp(message, "Black resign", 12) == 0) { GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first)); return; + } else if (strncmp(message, "White matches", 13) == 0 || + strncmp(message, "Black matches", 13) == 0 ) { + /* [HGM] ignore GNUShogi noises */ + return; } else if (strncmp(message, "White", 5) == 0 && message[5] != '(' && StrStr(message, "Black") == NULL) { @@ -5338,11 +6107,16 @@ HandleMachineMove(message, cps) if (gameMode == TwoMachinesPlay) { if (cps->other->offeredDraw) { GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); - } else { + /* [HGM] in two-machine mode we delay relaying draw offer */ + /* until after we also have move, to see if it is really claim */ + } +#if 0 + else { if (cps->other->sendDrawOffers) { SendToProgram("draw\n", cps->other); } } +#endif } else if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) { if (userOfferedDraw) { @@ -5412,6 +6186,16 @@ HandleMachineMove(message, cps) programStats.score = curscore; programStats.got_only_move = 0; + if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */ + int ticklen; + + if(cps->nps == 0) ticklen = 10*time; // use engine reported time + else ticklen = (1000. * nodes) / cps->nps; // convert node count to time + if(WhiteOnMove(forwardMostMove)) + whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen; + else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen; + } + /* Buffer overflow protection */ if (buf1[0] != NULLCHAR) { if (strlen(buf1) >= sizeof(programStats.movelist) @@ -5641,12 +6425,16 @@ ParseGameHistory(game) yyboardindex = boardIndex; moveType = (ChessMove) yylex(); switch (moveType) { -#ifdef FAIRY + case IllegalMove: /* maybe suicide chess, etc. */ + if (appData.debugMode) { + fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text); + fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); + setbuf(debugFP, NULL); + } case WhitePromotionChancellor: case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: -#endif case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -5674,7 +6462,6 @@ ParseGameHistory(game) case BlackHSideCastleFR: case BlackASideCastleFR: /* POP Fabien */ - case IllegalMove: /* maybe suicide chess, etc. */ fromX = currentMoveString[0] - AAA; fromY = currentMoveString[1] - ONE; toX = currentMoveString[2] - AAA; @@ -5694,11 +6481,21 @@ ParseGameHistory(game) case AmbiguousMove: /* bug? */ sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text); + if (appData.debugMode) { + fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text); + fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); + setbuf(debugFP, NULL); + } DisplayError(buf, 0); return; case ImpossibleMove: /* bug? */ sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text); + if (appData.debugMode) { + fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text); + fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); + setbuf(debugFP, NULL); + } DisplayError(buf, 0); return; case (ChessMove) 0: /* end of file */ @@ -5771,7 +6568,8 @@ ParseGameHistory(game) default: break; case MT_CHECK: - strcat(parseList[boardIndex - 1], "+"); + if(gameInfo.variant != VariantShogi) + strcat(parseList[boardIndex - 1], "+"); break; case MT_CHECKMATE: strcat(parseList[boardIndex - 1], "#"); @@ -5788,24 +6586,68 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) int promoChar; Board board; { - ChessSquare captured = board[toY][toX], piece; int p; + ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0; + + /* [HGM] compute & store e.p. status and castling rights for new position */ + /* if we are updating a board for which those exist (i.e. in boards[]) */ + if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0) + { int i, j; - /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier) - && promoChar != 0) promoChar = 'F'; + if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A; + oldEP = epStatus[p-1]; + epStatus[p] = EP_NONE; + + if( board[toY][toX] != EmptySquare ) + epStatus[p] = EP_CAPTURE; + + if( board[fromY][fromX] == WhitePawn ) { + epStatus[p] = EP_PAWN_MOVE; + if( toY-fromY==2) + if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn && + gameInfo.variant != VariantBerolina || toX < fromX) + epStatus[p] = toX | berolina; + if(toX fromX) + epStatus[p] = toX; + } else + if( board[fromY][fromX] == BlackPawn ) { + epStatus[p] = EP_PAWN_MOVE; + if( toY-fromY== -2) + if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && + gameInfo.variant != VariantBerolina || toX < fromX) + epStatus[p] = toX | berolina; + if(toX fromX) + epStatus[p] = toX; + } + + for(i=0; i fromX+1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; board[fromY][BOARD_RGHT-1] = EmptySquare; - board[toY][toX-1] = WhiteRook; - } else if (initialPosition[fromY][fromX] == WhiteKing - && board[fromY][fromX] == WhiteKing + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; - board[toY][toX+1] = WhiteRook; - } else if (fromY == 0 && fromX == 3 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 5) { - board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][7] = EmptySquare; - board[toY][4] = WhiteRook; - } else if (fromY == 0 && fromX == 3 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 1) { - board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][0] = EmptySquare; - board[toY][2] = WhiteRook; } else if (board[fromY][fromX] == WhitePawn && toY == BOARD_HEIGHT-1 -#ifdef FAIRY && gameInfo.variant != VariantXiangqi -#endif ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); if (board[toY][toX] == EmptySquare) { board[toY][toX] = WhiteQueen; } - if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ - board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn; + if(gameInfo.variant==VariantBughouse || + gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY == BOARD_HEIGHT-4) && (toX != fromX) + && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantBerolina && (board[fromY][fromX] == WhitePawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhitePawn; captured = board[toY - 1][toX]; board[toY - 1][toX] = EmptySquare; - } else if (initialPosition[fromY][fromX] == BlackKing - && board[fromY][fromX] == BlackKing + } else if ((fromY == BOARD_HEIGHT-4) + && (toX == fromX) + && gameInfo.variant == VariantBerolina + && (board[fromY][fromX] == WhitePawn) + && (board[toY][toX] == EmptySquare)) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = WhitePawn; + if(oldEP & EP_BEROLIN_A) { + captured = board[fromY][fromX-1]; + board[fromY][fromX-1] = EmptySquare; + }else{ captured = board[fromY][fromX+1]; + board[fromY][fromX+1] = EmptySquare; + } + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX > fromX+1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackKing; + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; board[fromY][BOARD_RGHT-1] = EmptySquare; - board[toY][toX-1] = BlackRook; - } else if (initialPosition[fromY][fromX] == BlackKing - && board[fromY][fromX] == BlackKing + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackKing; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; - board[toY][toX+1] = BlackRook; } else if (fromY == 7 && fromX == 3 && board[fromY][fromX] == BlackKing && toY == 7 && toX == 5) { @@ -5905,30 +6747,50 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][2] = BlackRook; } else if (board[fromY][fromX] == BlackPawn && toY == 0 -#ifdef FAIRY && gameInfo.variant != VariantXiangqi -#endif ) { /* black pawn promotion */ board[0][toX] = CharToPiece(ToLower(promoChar)); if (board[0][toX] == EmptySquare) { board[0][toX] = BlackQueen; } - if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ - board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn; + if(gameInfo.variant==VariantBughouse || + gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY == 3) && (toX != fromX) + && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantBerolina && (board[fromY][fromX] == BlackPawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackPawn; captured = board[toY + 1][toX]; board[toY + 1][toX] = EmptySquare; + } else if ((fromY == 3) + && (toX == fromX) + && gameInfo.variant == VariantBerolina + && (board[fromY][fromX] == BlackPawn) + && (board[toY][toX] == EmptySquare)) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = BlackPawn; + if(oldEP & EP_BEROLIN_A) { + captured = board[fromY][fromX-1]; + board[fromY][fromX-1] = EmptySquare; + }else{ captured = board[fromY][fromX+1]; + board[fromY][fromX+1] = EmptySquare; + } } else { board[toY][toX] = board[fromY][fromX]; board[fromY][fromX] = EmptySquare; } + + /* [HGM] now we promote for Shogi, if needed */ + if(gameInfo.variant == VariantShogi && promoChar == 'q') + board[toY][toX] = (ChessSquare) (PROMOTED piece); + } + if (gameInfo.holdingsWidth != 0) { /* !!A lot more code needs to be written to support holdings */ @@ -5962,6 +6824,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) captured = (ChessSquare) (DEMOTED captured); p = DEMOTED p; } + p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; } board[p][BOARD_WIDTH-2]++; board[p][BOARD_WIDTH-1] = @@ -5972,6 +6835,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) captured = (ChessSquare) (DEMOTED captured); p = DEMOTED p; } + p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; } board[BOARD_HEIGHT-1-p][1]++; board[BOARD_HEIGHT-1-p][0] = @@ -5993,7 +6857,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = EmptySquare; } } - if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR) { + if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') { /* [HGM] Shogi promotions */ board[toY][toX] = (ChessSquare) (PROMOTED piece); } @@ -6008,6 +6872,48 @@ MakeMove(fromX, fromY, toX, toY, promoChar) { forwardMostMove++; + if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */ + int timeLeft; static int lastLoadFlag=0; int king, piece; + piece = boards[forwardMostMove-1][fromY][fromX]; + king = piece < (int) BlackPawn ? WhiteKing : BlackKing; + if(gameInfo.variant == VariantKnightmate) + king += (int) WhiteUnicorn - (int) WhiteKing; + if(forwardMostMove == 1) { + if(blackPlaysFirst) + fprintf(serverMoves, "%s;", second.tidy); + fprintf(serverMoves, "%s;", first.tidy); + if(!blackPlaysFirst) + fprintf(serverMoves, "%s;", second.tidy); + } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";"); + lastLoadFlag = loadFlag; + // print base move + fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY); + // print castling suffix + if( toY == fromY && piece == king ) { + if(toX-fromX > 1) + fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY); + if(fromX-toX >1) + fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY); + } + // e.p. suffix + if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn || + boards[forwardMostMove-1][fromY][fromX] == BlackPawn ) && + boards[forwardMostMove-1][toY][toX] == EmptySquare + && fromX != toX ) + fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY); + // promotion suffix + if(promoChar != NULLCHAR) + fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY); + if(!loadFlag) { + fprintf(serverMoves, "/%d/%d", + pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score); + if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000; + else timeLeft = blackTimeRemaining/1000; + fprintf(serverMoves, "/%d", timeLeft); + } + fflush(serverMoves); + } + if (forwardMostMove >= MAX_MOVES) { DisplayFatalError("Game too long; increase MAX_MOVES and recompile", 0, 1); @@ -6021,38 +6927,6 @@ MakeMove(fromX, fromY, toX, toY, promoChar) commentList[forwardMostMove] = NULL; } CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]); - /* [HGM] compute & store e.p. status and castling rights for new position */ - { int i, j; - - epStatus[forwardMostMove] = EP_NONE; - - if( boards[forwardMostMove][toY][toX] != EmptySquare ) - epStatus[forwardMostMove] = EP_CAPTURE; - - if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) { - epStatus[forwardMostMove] = EP_PAWN_MOVE; - if( toY-fromY==2 && - (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn || - toXBOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn || - toXvariants, v) == NULL) { + if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) { + /* [HGM] in protocol 1 we have to assume all variants valid */ sprintf(buf, "Variant %s not supported by %s", v, cps->tidy); DisplayFatalError(buf, 0, 1); return; } - b = buf; + /* [HGM] make prefix for non-standard board size. Awkward testing... */ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if( gameInfo.variant == VariantXiangqi ) @@ -6138,28 +7019,42 @@ InitChessProgram(cps) overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7; if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; - if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic ) + if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || + gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon ) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if( gameInfo.variant == VariantCourier ) overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if(overruled) { -#if 0 - // doesn't work in protocol 1 - if (StrStr(cps->variants, "boardsize") == NULL,) { - sprintf(buf, "Board size %dx%d+%d not supported by %s", - gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy); - DisplayFatalError(buf, 0, 1); - return; + sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, + gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name + /* [HGM] varsize: try first if this defiant size variant is specifically known */ + if(StrStr(cps->variants, b) == NULL) { + // specific sized variant not known, check if general sizing allowed + if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best + if(StrStr(cps->variants, "boardsize") == NULL) { + sprintf(buf, "Board size %dx%d+%d not supported by %s", + gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy); + DisplayFatalError(buf, 0, 1); + return; + } + /* [HGM] here we really should compare with the maximum supported board size */ + } } -#endif - sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth, - gameInfo.boardHeight, gameInfo.holdingsSize ); - while(*b++ != '_'); - } - sprintf(b, "variant %s\n", VariantName(gameInfo.variant)); + } else sprintf(b, "%s", VariantName(gameInfo.variant)); + sprintf(buf, "variant %s\n", b); SendToProgram(buf, cps); } + currentlyInitializedVariant = gameInfo.variant; + + /* [HGM] send opening position in FRC to first engine */ + if(setup) { + SendToProgram("force\n", cps); + SendBoard(cps, 0); + /* engine is now in force mode! Set flag to wake it up after first move. */ + setboardSpoiledMachineBlack = 1; + } + if (cps->sendICS) { sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-"); SendToProgram(buf, cps); @@ -6293,12 +7188,15 @@ GameEnds(result, resultDetails, whosays) int isIcsGame; char buf[MSG_SIZ]; + if(endingGame) return; /* [HGM] crash: forbid recursion */ + endingGame = 1; + if (appData.debugMode) { fprintf(debugFP, "GameEnds(%d, %s, %d)\n", result, resultDetails ? resultDetails : "(null)", whosays); } - if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) { + if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the game is over, but the engine can offer to draw, claim a draw, or resign. @@ -6315,7 +7213,8 @@ GameEnds(result, resultDetails, whosays) } } #endif - return; + endingGame = 0; /* [HGM] crash */ + return; } /* If we're loading the game from a file, stop */ @@ -6325,7 +7224,7 @@ GameEnds(result, resultDetails, whosays) } /* Cancel draw offers */ - first.offeredDraw = second.offeredDraw = 0; + first.offeredDraw = second.offeredDraw = 0; /* If this is an ICS game, only ICS can really say it's done; if not, anyone can. */ @@ -6356,14 +7255,19 @@ GameEnds(result, resultDetails, whosays) claimer = whosays == GE_ENGINE1 ? /* color of claimer */ first.twoMachinesColor[0] : second.twoMachinesColor[0] ; - if( result == WhiteWins && claimer == 'w' || - result == BlackWins && claimer == 'b' ) { + if( gameInfo.holdingsWidth == 0 && + (result == WhiteWins && claimer == 'w' || + result == BlackWins && claimer == 'b' ) ) { /* Xboard immediately adjudicates all mates, so win claims must be false */ sprintf(buf, "False win claim: '%s'", resultDetails); result = claimer == 'w' ? BlackWins : WhiteWins; resultDetails = buf; } else - if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) { + if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS + && (forwardMostMove <= backwardMostMove || + epStatus[forwardMostMove-1] > EP_DRAWS || + (claimer=='b')==(forwardMostMove&1)) + ) { /* Draw that was not flagged by Xboard is false */ sprintf(buf, "False draw claim: '%s'", resultDetails); result = claimer == 'w' ? BlackWins : WhiteWins; @@ -6372,6 +7276,12 @@ GameEnds(result, resultDetails, whosays) /* (Claiming a loss is accepted no questions asked!) */ } + if(serverMoves != NULL && !loadFlag) { char c = '='; + if(result==WhiteWins) c = '+'; + if(result==BlackWins) c = '-'; + if(resultDetails != NULL) + fprintf(serverMoves, ";%c;%s\n", c, resultDetails); + } if (appData.debugMode) { fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n", result, resultDetails ? resultDetails : "(null)", whosays); @@ -6380,7 +7290,30 @@ GameEnds(result, resultDetails, whosays) gameInfo.result = result; gameInfo.resultDetails = StrSave(resultDetails); + /* display last move only if game was not loaded from file */ + if ((whosays != GE_FILE) && (currentMove == forwardMostMove)) + DisplayMove(currentMove - 1); + + if (forwardMostMove != 0) { + if (gameMode != PlayFromGameFile && gameMode != EditGame) { + if (*appData.saveGameFile != NULLCHAR) { + SaveGameToFile(appData.saveGameFile, TRUE); + } else if (appData.autoSaveGames) { + AutoSaveGame(); + } + if (*appData.savePositionFile != NULLCHAR) { + SavePositionToFile(appData.savePositionFile); + } + } + } + /* Tell program how game ended in case it is learning */ + /* [HGM] Moved this to after saving the PGN, just in case */ + /* engine died and we got here through time loss. In that */ + /* case we will get a fatal error writing the pipe, which */ + /* would otherwise lose us the PGN. */ + /* [HGM] crash: not needed anymore, but doesn't hurt; */ + /* output during GameEnds should never be fatal anymore */ if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay || @@ -6398,23 +7331,6 @@ GameEnds(result, resultDetails, whosays) SendToProgram(buf, &second); } } - - /* display last move only if game was not loaded from file */ - if ((whosays != GE_FILE) && (currentMove == forwardMostMove)) - DisplayMove(currentMove - 1); - - if (forwardMostMove != 0) { - if (gameMode != PlayFromGameFile && gameMode != EditGame) { - if (*appData.saveGameFile != NULLCHAR) { - SaveGameToFile(appData.saveGameFile, TRUE); - } else if (appData.autoSaveGames) { - AutoSaveGame(); - } - if (*appData.savePositionFile != NULLCHAR) { - SavePositionToFile(appData.savePositionFile); - } - } - } } if (appData.icsActive) { @@ -6472,7 +7388,8 @@ GameEnds(result, resultDetails, whosays) if (appData.noChessProgram) { gameMode = nextGameMode; ModeHighlight(); - return; + endingGame = 0; /* [HGM] crash */ + return; } if (first.reuse) { @@ -6561,6 +7478,7 @@ GameEnds(result, resultDetails, whosays) if(appData.matchPause>10000 || appData.matchPause<10) appData.matchPause = 10000; /* [HGM] make pause adjustable */ ScheduleDelayedEvent(NextMatchGame, appData.matchPause); + endingGame = 0; /* [HGM] crash */ return; } else { char buf[MSG_SIZ]; @@ -6577,6 +7495,7 @@ GameEnds(result, resultDetails, whosays) ExitAnalyzeMode(); gameMode = nextGameMode; ModeHighlight(); + endingGame = 0; /* [HGM] crash */ } /* Assumes program was just initialized (initString sent). @@ -6592,9 +7511,20 @@ FeedMovesToProgram(cps, upto) fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n", startedFromSetupPosition ? "position and " : "", backwardMostMove, upto, cps->which); + if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ]; + // [HGM] variantswitch: make engine aware of new variant + if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL) + return; // [HGM] refrain from feeding moves altogether if variant is unsupported! + sprintf(buf, "variant %s\n", VariantName(gameInfo.variant)); + SendToProgram(buf, cps); + currentlyInitializedVariant = gameInfo.variant; + } SendToProgram("force\n", cps); if (startedFromSetupPosition) { SendBoard(cps, backwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "feedMoves\n"); + } } for (i = backwardMostMove; i < upto; i++) { SendMoveToProgram(i, cps); @@ -6611,7 +7541,7 @@ ResurrectChessProgram() if (appData.noChessProgram || first.pr != NoProc) return; StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); if (!first.sendTime) { @@ -6642,7 +7572,6 @@ Reset(redraw, init) fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n", redraw, init, gameMode); } - pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -6669,9 +7598,23 @@ Reset(redraw, init) alarmSounded = FALSE; GameEnds((ChessMove) 0, NULL, GE_PLAYER); + if(appData.serverMovesName != NULL) { + /* [HGM] prepare to make moves file for broadcasting */ + clock_t t = clock(); + if(serverMoves != NULL) fclose(serverMoves); + serverMoves = fopen(appData.serverMovesName, "r"); + if(serverMoves != NULL) { + fclose(serverMoves); + /* delay 15 sec before overwriting, so all clients can see end */ + while(clock()-t < appData.serverPause*CLOCKS_PER_SEC); + } + serverMoves = fopen(appData.serverMovesName, "w"); + } + ExitAnalyzeMode(); gameMode = BeginningOfGame; ModeHighlight(); + if(appData.icsActive) gameInfo.variant = VariantNormal; InitPosition(redraw); for (i = 0; i < MAX_MOVES; i++) { if (commentList[i] != NULL) { @@ -6685,7 +7628,9 @@ Reset(redraw, init) if (first.pr == NULL) { StartChessProgram(&first); } - if (init) InitChessProgram(&first); + if (init) { + InitChessProgram(&first, startedFromSetupPosition); + } DisplayTitle(""); DisplayMessage("", ""); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); @@ -6752,9 +7697,8 @@ AutoPlayOneMove() SendMoveToProgram(currentMove++, &first); DisplayBothClocks(); DrawPosition(FALSE, boards[currentMove]); - if (commentList[currentMove] != NULL) { - DisplayComment(currentMove - 1, commentList[currentMove]); - } + // [HGM] PV info: always display, routine tests if empty + DisplayComment(currentMove - 1, commentList[currentMove]); return TRUE; } @@ -6802,12 +7746,10 @@ LoadGameOneMove(readAhead) case WhiteCapturesEnPassant: case BlackCapturesEnPassant: -#ifdef FAIRY case WhitePromotionChancellor: case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: -#endif case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -6993,7 +7935,7 @@ LoadGameOneMove(readAhead) if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) { DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); - if (!appData.matchMode && commentList[currentMove] != NULL) + if (!appData.matchMode) // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } (void) StopLoadGameTimer(); @@ -7204,6 +8146,7 @@ LoadGame(f, gameNumber, title, useList) int numPGNTags = 0; int err; GameMode oldGameMode; + VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ if (appData.debugMode) fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode); @@ -7253,7 +8196,6 @@ LoadGame(f, gameNumber, title, useList) yynewfile(f); - if (lg && lg->gameInfo.white && lg->gameInfo.black) { sprintf(buf, "%s vs. %s", lg->gameInfo.white, lg->gameInfo.black); @@ -7412,6 +8354,16 @@ LoadGame(f, gameNumber, title, useList) err = ParsePGNTag(yy_text, &gameInfo); if (!err) numPGNTags++; + /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */ + if(gameInfo.variant != oldVariant) { + startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */ + InitPosition(TRUE); + oldVariant = gameInfo.variant; + if (appData.debugMode) + fprintf(debugFP, "New variant %d\n", (int) oldVariant); + } + + if (gameInfo.fen != NULL) { Board initial_position; startedFromSetupPosition = TRUE; @@ -7426,7 +8378,7 @@ LoadGame(f, gameNumber, title, useList) initialRulePlies = FENrulePlies; epStatus[0] = FENepStatus; for( i=0; i< nrCastlingRights; i++ ) - castlingRights[0][i] = FENcastlingRights[i]; + initialRights[i] = castlingRights[0][i] = FENcastlingRights[i]; } if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; @@ -7544,13 +8496,19 @@ LoadGame(f, gameNumber, title, useList) if (first.pr == NoProc) { StartChessProgram(&first); } - InitChessProgram(&first); + InitChessProgram(&first, FALSE); SendToProgram("force\n", &first); if (startedFromSetupPosition) { SendBoard(&first, forwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "Load Game\n"); + } DisplayBothClocks(); } + /* [HGM] server: flag to write setup moves in broadcast file as one */ + loadFlag = appData.suppressLoadMoves; + while (cm == Comment) { char *p; if (appData.debugMode) @@ -7585,10 +8543,9 @@ LoadGame(f, gameNumber, title, useList) return TRUE; } - if (commentList[currentMove] != NULL) { - if (!matchMode && (pausing || appData.timeDelay != 0)) { + // [HGM] PV info: routine tests if comment empty + if (!matchMode && (pausing || appData.timeDelay != 0)) { DisplayComment(currentMove - 1, commentList[currentMove]); - } } if (!matchMode && appData.timeDelay != 0) DrawPosition(FALSE, boards[currentMove]); @@ -7629,6 +8586,8 @@ LoadGame(f, gameNumber, title, useList) if (appData.debugMode) fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode); + + loadFlag = 0; /* [HGM] true game starts */ return TRUE; } @@ -7700,7 +8659,7 @@ LoadPosition(f, positionNumber, title) strcpy(lastLoadPositionTitle, title); if (first.pr == NoProc) { StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); } pn = positionNumber; if (positionNumber < 0) { @@ -7727,6 +8686,7 @@ LoadPosition(f, positionNumber, title) DisplayError("Position not found in file", 0); return FALSE; } +#if 0 switch (line[0]) { case '#': case 'x': default: @@ -7736,14 +8696,16 @@ LoadPosition(f, positionNumber, title) case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': -#ifdef FAIRY case 'H': case 'A': case 'M': case 'h': case 'a': case 'm': case 'E': case 'F': case 'G': case 'e': case 'f': case 'g': case 'C': case 'W': case 'c': case 'w': -#endif fenMode = TRUE; break; } +#else + // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces + fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare; +#endif if (pn >= 2) { if (fenMode || line[0] == '#') pn--; @@ -7805,6 +8767,9 @@ LoadPosition(f, positionNumber, title) DisplayMessage("", "White to play"); } SendBoard(&first, forwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "Load Position\n"); + } if (positionNumber > 1) { sprintf(line, "%s %d", title, positionNumber); @@ -8061,13 +9026,16 @@ SaveGamePGN(f) /* [HGM] add time */ char buf[MSG_SIZ]; int seconds = 0; +#if 0 if(i >= backwardMostMove) { /* take the time that changed */ seconds = timeRemaining[0][i] - timeRemaining[0][i+1]; - if(seconds <= 0) + if(seconds <= 0) seconds = timeRemaining[1][i] - timeRemaining[1][i+1]; } seconds /= 1000; +#endif + seconds = pvInfoList[i].time/100; // [HGM] PVtime: use engine time if (appData.debugMode) { fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n", timeRemaining[0][i+1], timeRemaining[0][i], @@ -8595,8 +9563,6 @@ ResetGameEvent() } } -static int exiting = 0; - void ExitEvent(status) int status; @@ -8619,8 +9585,10 @@ ExitEvent(status) if (icsPR != NoProc) { DestroyChildProcess(icsPR, TRUE); } +#if 0 /* Save game if resource set and not already saved by GameEnds() */ - if (gameInfo.resultDetails == NULL && forwardMostMove > 0) { + if ((gameInfo.resultDetails == NULL || errorExitFlag ) + && forwardMostMove > 0) { if (*appData.saveGameFile != NULLCHAR) { SaveGameToFile(appData.saveGameFile, TRUE); } else if (appData.autoSaveGames) { @@ -8631,6 +9599,17 @@ ExitEvent(status) } } GameEnds((ChessMove) 0, NULL, GE_PLAYER); +#else + /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */ + GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER); +#endif + /* [HGM] crash: the above GameEnds() is a dud if another one was running */ + /* make sure this other one finishes before killing it! */ + if(endingGame) { int count = 0; + if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n"); + while(endingGame && count++ < 10) DoSleep(1); + if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n"); + } /* Kill off chess programs */ if (first.pr != NoProc) { @@ -9004,10 +9983,13 @@ TwoMachinesEvent P((void)) return; } DisplayMessage("", ""); - InitChessProgram(&second); + InitChessProgram(&second, FALSE); SendToProgram("force\n", &second); if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "Two Machines\n"); + } } for (i = backwardMostMove; i < forwardMostMove; i++) { SendMoveToProgram(i, &second); @@ -9243,7 +10225,7 @@ void EditPositionDone() { startedFromSetupPosition = TRUE; - InitChessProgram(&first); + InitChessProgram(&first, FALSE); SendToProgram("force\n", &first); if (blackPlaysFirst) { strcpy(moveList[0], ""); @@ -9254,6 +10236,9 @@ EditPositionDone() currentMove = forwardMostMove = backwardMostMove = 0; } SendBoard(&first, forwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "EditPosDone\n"); + } DisplayTitle(""); timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; @@ -9336,6 +10321,7 @@ EditPositionMenuEvent(selection, x, y) int x, y; { char buf[MSG_SIZ]; + ChessSquare piece = boards[0][y][x]; if (gameMode != EditPosition && gameMode != IcsExamining) return; @@ -9385,7 +10371,38 @@ EditPositionMenuEvent(selection, x, y) } break; + case PromotePiece: + if(piece >= (int)WhitePawn && piece < (int)WhiteMan || + piece >= (int)BlackPawn && piece < (int)BlackMan ) { + selection = (ChessSquare) (PROMOTED piece); + } else if(piece == EmptySquare) selection = WhiteSilver; + else selection = (ChessSquare)((int)piece - 1); + goto defaultlabel; + + case DemotePiece: + if(piece > (int)WhiteMan && piece <= (int)WhiteKing || + piece > (int)BlackMan && piece <= (int)BlackKing ) { + selection = (ChessSquare) (DEMOTED piece); + } else if(piece == EmptySquare) selection = BlackSilver; + else selection = (ChessSquare)((int)piece + 1); + goto defaultlabel; + + case WhiteQueen: + case BlackQueen: + if(gameInfo.variant == VariantShatranj || + gameInfo.variant == VariantXiangqi || + gameInfo.variant == VariantCourier ) + selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); + goto defaultlabel; + + case WhiteKing: + case BlackKing: + if(gameInfo.variant == VariantXiangqi) + selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir); + if(gameInfo.variant == VariantKnightmate) + selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn); default: + defaultlabel: if (gameMode == IcsExamining) { sprintf(buf, "%s%c@%c%c\n", ics_prefix, PieceToChar(selection), AAA + x, ONE + y); @@ -9717,7 +10734,7 @@ ForwardInner(target) DisplayMove(currentMove - 1); DrawPosition(FALSE, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); - if (commentList[currentMove] && !matchMode && gameMode != Training) { + if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } } @@ -9820,9 +10837,8 @@ BackwardInner(target) DisplayMove(currentMove - 1); DrawPosition(full_redraw, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); - if (commentList[currentMove] != NULL) { - DisplayComment(currentMove - 1, commentList[currentMove]); - } + // [HGM] PV info: routine tests if comment empty + DisplayComment(currentMove - 1, commentList[currentMove]); } void @@ -10109,6 +11125,7 @@ TidyProgramName(prog, host, buf) p = q; while (p >= prog && *p != '/' && *p != '\\') p--; p++; + if(p == prog && *p == '"') p++; if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4; memcpy(buf, p, q - p); buf[q - p] = NULLCHAR; @@ -10269,7 +11286,7 @@ AppendComment(index, text) int oldlen, len; char *old; - GetInfoFromComment( index, text ); + text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */ CrushCRs(text); while (*text == '\n') text++; @@ -10307,12 +11324,15 @@ static char * FindStr( char * text, char * sub_text ) } /* [AS] Try to extract PV info from PGN comment */ -void GetInfoFromComment( int index, char * text ) +/* [HGM] PV time: and then remove it, to prevent it appearing twice */ +char *GetInfoFromComment( int index, char * text ) { + char * sep = text; + if( text != NULL && index > 0 ) { int score = 0; int depth = 0; - int time = -1; + int time = -1, sec = 0; char * s_eval = FindStr( text, "[%eval " ); char * s_emt = FindStr( text, "[%emt " ); @@ -10322,11 +11342,11 @@ void GetInfoFromComment( int index, char * text ) if( s_eval != NULL ) { if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) { - return; + return text; } if( delim != ']' ) { - return; + return text; } } @@ -10335,26 +11355,38 @@ void GetInfoFromComment( int index, char * text ) } else { /* We expect something like: [+|-]nnn.nn/dd */ - char * sep = strchr( text, '/' ); int score_lo = 0; + sep = strchr( text, '/' ); if( sep == NULL || sep < (text+4) ) { - return; + return text; } - if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { - return; + time = -1; sec = -1; + if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && + sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && + sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { + return text; } if( score_lo < 0 || score_lo >= 100 ) { - return; + return text; } + if(sec >= 0) time = 60*time + sec; score = score >= 0 ? score*100 + score_lo : score*100 - score_lo; + + /* [HGM] PV time: now locate end of PV info */ + while( *++sep >= '0' && *sep <= '9'); // strip depth + if(time >= 0) + while( *++sep >= '0' && *sep <= '9'); // strip time + if(sec >= 0) + while( *++sep >= '0' && *sep <= '9'); // strip seconds + while(*sep == ' ') sep++; } if( depth <= 0 ) { - return; + return text; } if( time < 0 ) { @@ -10363,8 +11395,9 @@ void GetInfoFromComment( int index, char * text ) pvInfoList[index-1].depth = depth; pvInfoList[index-1].score = score; - pvInfoList[index-1].time = time; + pvInfoList[index-1].time = time; } + return sep; } void @@ -10388,9 +11421,19 @@ SendToProgram(message, cps) count = strlen(message); outCount = OutputToProcess(cps->pr, message, count, &error); - if (outCount < count && !exiting) { + if (outCount < count && !exiting + && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */ sprintf(buf, "Error writing to %s chess program", cps->which); - DisplayFatalError(buf, error, 1); + if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ + if(epStatus[forwardMostMove] <= EP_DRAWS) { + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ + sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program); + } else { + gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + } + gameInfo.resultDetails = buf; + } + DisplayFatalError(buf, error, 1); } } @@ -10412,6 +11455,15 @@ ReceiveFromProgram(isr, closure, message, count, error) sprintf(buf, "Error: %s chess program (%s) exited unexpectedly", cps->which, cps->program); + if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ + if(epStatus[forwardMostMove] <= EP_DRAWS) { + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ + sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program); + } else { + gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + } + gameInfo.resultDetails = buf; + } RemoveInputSource(cps->isr); DisplayFatalError(buf, 0, 1); } else { @@ -10428,7 +11480,6 @@ ReceiveFromProgram(isr, closure, message, count, error) DisplayFatalError(buf, error, 1); } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); return; } @@ -10438,11 +11489,29 @@ ReceiveFromProgram(isr, closure, message, count, error) *end_str = NULLCHAR; if (appData.debugMode) { - TimeMark now; - GetTimeMark(&now); - fprintf(debugFP, "%ld <%-6s: %s\n", - SubtractTimeMarks(&now, &programStartTime), - cps->which, message); + TimeMark now; int print = 1; + char *quote = ""; char c; int i; + + if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */ + char start = message[0]; + if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing + if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && + sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 && + sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 && + sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 && + sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && + sscanf(message, "askuser%c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && + sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#') + { quote = "# "; print = (appData.engineComments == 2); } + message[0] = start; // restore original message + } + if(print) { + GetTimeMark(&now); + fprintf(debugFP, "%ld <%-6s: %s%s\n", + SubtractTimeMarks(&now, &programStartTime), cps->which, + quote, + message); + } } HandleMachineMove(message, cps); } @@ -10455,13 +11524,18 @@ SendTimeControl(cps, mps, tc, inc, sd, st) long tc; { char buf[MSG_SIZ]; - int seconds = (tc / 1000) % 60; + int seconds; if( timeControl_2 > 0 ) { if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) { tc = timeControl_2; } } + tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */ + inc /= cps->timeOdds; + st /= cps->timeOdds; + + seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */ if (st > 0) { /* Set exact time per move, normally using st command */ @@ -10500,6 +11574,22 @@ SendTimeControl(cps, mps, tc, inc, sd, st) } SendToProgram(buf, cps); } + + if(cps->nps > 0) { /* [HGM] nps */ + if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported! + else { + sprintf(buf, "nps %d\n", cps->nps); + SendToProgram(buf, cps); + } + } +} + +ChessProgramState *WhitePlayer() +/* [HGM] return pointer to 'first' or 'second', depending on who plays white */ +{ + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') + return &second; + return &first; } void @@ -10521,6 +11611,12 @@ SendTimeRemaining(cps, machineWhite) time = blackTimeRemaining / 10; otime = whiteTimeRemaining / 10; } + /* [HGM] translate opponent's time by time-odds factor */ + otime = (otime * cps->other->timeOdds) / cps->timeOdds; + if (appData.debugMode) { + fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds); + } + if (time <= 0) time = 1; if (otime <= 0) otime = 1; @@ -10652,6 +11748,9 @@ ParseFeatures(args, cps) if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */ + if (BoolFeature(&p, "debug", &cps->debug, cps)) continue; + if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue; + if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); continue; @@ -10898,6 +11997,8 @@ DisplayComment(moveNumber, text) char *text; { char title[MSG_SIZ]; + char buf[8000]; // comment can be long! + int score, depth; if( appData.autoDisplayComment ) { if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { @@ -10907,9 +12008,18 @@ DisplayComment(moveNumber, text) WhiteOnMove(moveNumber) ? " " : ".. ", parseList[moveNumber]); } - + } else title[0] = 0; + + // [HGM] PV info: display PV info together with (or as) comment + if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { + if(text == NULL) text = ""; + score = pvInfoList[moveNumber].score; + sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., + depth, pvInfoList[moveNumber].time, text); + CommentPopUp(title, buf); + } else + if (text != NULL) CommentPopUp(title, text); - } } /* This routine sends a ^C interrupt to gnuchess, to awaken it if it @@ -11002,30 +12112,18 @@ CheckTimeControl() if (!appData.clockMode || appData.icsActive || gameMode == PlayFromGameFile || forwardMostMove == 0) return; - if (timeIncrement >= 0) { - if (WhiteOnMove(forwardMostMove)) { - blackTimeRemaining += timeIncrement; - } else { - whiteTimeRemaining += timeIncrement; - } - } /* - * add time to clocks when time control is achieved + * add time to clocks when time control is achieved ([HGM] now also used fot increment) */ - if (movesPerSession) { - switch ((forwardMostMove + 1) % (movesPerSession * 2)) { - case 0: + if ( !WhiteOnMove(forwardMostMove) ) /* White made time control */ - whiteTimeRemaining += GetTimeControlForWhite(); - break; - case 1: + whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2) + /* [HGM] time odds: correct new time quota for time odds! */ + / WhitePlayer()->timeOdds; + else /* Black made time control */ - blackTimeRemaining += GetTimeControlForBlack(); - break; - default: - break; - } - } + blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2) + / WhitePlayer()->other->timeOdds; } void @@ -11116,6 +12214,15 @@ NextTickLength(timeRemaining) return nextTickLength; } +/* Adjust clock one minute up or down */ +void +AdjustClock(Boolean which, int dir) +{ + if(which) blackTimeRemaining += 60000*dir; + else whiteTimeRemaining += 60000*dir; + DisplayBothClocks(); +} + /* Stop clocks and reset to a fresh time control */ void ResetClocks() @@ -11123,9 +12230,9 @@ ResetClocks() (void) StopClockTimer(); if (appData.icsActive) { whiteTimeRemaining = blackTimeRemaining = 0; - } else { - whiteTimeRemaining = GetTimeControlForWhite(); - blackTimeRemaining = GetTimeControlForBlack(); + } else { /* [HGM] correct new time quote for time odds */ + whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds; + blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds; } if (whiteFlag || blackFlag) { DisplayTitle(""); @@ -11156,10 +12263,12 @@ DecrementClocks() if (fudge < 0 || fudge > FUDGE) fudge = 0; if (WhiteOnMove(forwardMostMove)) { + if(whiteNPS >= 0) lastTickLength = 0; timeRemaining = whiteTimeRemaining -= lastTickLength; DisplayWhiteClock(whiteTimeRemaining - fudge, WhiteOnMove(currentMove)); } else { + if(blackNPS >= 0) lastTickLength = 0; timeRemaining = blackTimeRemaining -= lastTickLength; DisplayBlackClock(blackTimeRemaining - fudge, !WhiteOnMove(currentMove)); @@ -11214,13 +12323,20 @@ SwitchClocks() if (StopClockTimer() && appData.clockMode) { lastTickLength = SubtractTimeMarks(&now, &tickStartTM); if (WhiteOnMove(forwardMostMove)) { + if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; + /* [HGM] PGNtime: save time for PGN file if engine did not give it */ + if(pvInfoList[forwardMostMove-1].time == -1) + pvInfoList[forwardMostMove-1].time = + (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10; } else { - whiteTimeRemaining -= lastTickLength; + if(whiteNPS >= 0) lastTickLength = 0; + whiteTimeRemaining -= lastTickLength; + /* [HGM] PGNtime: save time for PGN file if engine did not give it */ + if(pvInfoList[forwardMostMove-1].time == -1) + pvInfoList[forwardMostMove-1].time = + (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10; } - /* [HGM] save time for PGN file if engine did not give it */ - if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = lastTickLength/100; flagged = CheckFlags(); } CheckTimeControl(); @@ -11264,9 +12380,11 @@ StopClocks() lastTickLength = SubtractTimeMarks(&now, &tickStartTM); if (WhiteOnMove(forwardMostMove)) { + if(whiteNPS >= 0) lastTickLength = 0; whiteTimeRemaining -= lastTickLength; DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove)); } else { + if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove)); } @@ -11288,6 +12406,21 @@ StartClocks() GetTimeMark(&tickStartTM); intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ? whiteTimeRemaining : blackTimeRemaining); + + /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */ + whiteNPS = blackNPS = -1; + if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w' + || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white + whiteNPS = first.nps; + if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' + || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black + blackNPS = first.nps; + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode + whiteNPS = second.nps; + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w') + blackNPS = second.nps; + if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS); + StartClockTimer(intendedTickLength); } @@ -11468,21 +12601,23 @@ PositionToFEN(move, useFEN960) for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { if (boards[move][i][j] == EmptySquare) { emptycount++; - } else { + } else { ChessSquare piece = boards[move][i][j]; if (emptycount > 0) { if(emptycount<10) /* [HGM] can be >= 10 */ *p++ = '0' + emptycount; else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; } emptycount = 0; } - *p++ = PieceToChar(boards[move][i][j]); - if(gameInfo.variant == VariantCrazyhouse) { - /* [HGM] flag Crazyhouse promoted pieces */ - if( (int)boards[move][i][j] > (int) WhiteQueen && (int)boards[move][i][j] < (int) WhiteKing || - (int)boards[move][i][j] > (int) BlackQueen && (int)boards[move][i][j] < (int) BlackKing ) { - p[-1] = PieceToChar((ChessSquare)((int)boards[move][i][j]-(int)WhiteAlfil+(int)WhitePawn)); - *p++ = '~'; - } + if(PieceToChar(piece) == '+') { + /* [HGM] write promoted pieces as '+' (Shogi) */ + *p++ = '+'; + piece = (ChessSquare)(DEMOTED piece); + } + *p++ = PieceToChar(piece); + if(p[-1] == '~') { + /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ + p[-1] = PieceToChar((ChessSquare)(DEMOTED piece)); + *p++ = '~'; } } } @@ -11496,83 +12631,50 @@ PositionToFEN(move, useFEN960) } *(p - 1) = ' '; + /* [HGM] print Crazyhouse or Shogi holdings */ + if( gameInfo.holdingsWidth ) { + *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */ + q = p; + for(i=0; i= 0 && + castlingRights[move][0] >= 0 ) + *p++ = castlingRights[move][0] + AAA + 'A' - 'a'; + if(castlingRights[move][2] >= 0 && + castlingRights[move][1] >= 0 ) + *p++ = castlingRights[move][1] + AAA + 'A' - 'a'; + if(castlingRights[move][5] >= 0 && + castlingRights[move][3] >= 0 ) + *p++ = castlingRights[move][3] + AAA; + if(castlingRights[move][5] >= 0 && + castlingRights[move][4] >= 0 ) + *p++ = castlingRights[move][4] + AAA; + } else { - /* White castling rights */ - - for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) { - - if (boards[move][0][fk] == WhiteKing) { - - for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'K'; - break; - } - } - - for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'Q'; - break; - } - } - } - } - - /* Black castling rights */ - - for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) { - - if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) { - - for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */ - if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) { - *p++ = useFEN960 ? 'a' + fr : 'k'; - break; - } - } - - for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */ - if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) { - *p++ = useFEN960 ? 'a' + fr : 'q'; - break; - } - } - } - } - - if (q == p) *p++ = '-'; /* No castling rights */ - *p++ = ' '; - } - else { - q = p; - -#ifdef OLDCASTLINGCODE - if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) { - if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K'; - if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q'; - } - if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) { - if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k'; - if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q'; - } -#else /* [HGM] write true castling rights */ if( nrCastlingRights == 6 ) { if(castlingRights[move][0] == BOARD_RGHT-1 && @@ -11584,13 +12686,13 @@ PositionToFEN(move, useFEN960) if(castlingRights[move][4] == BOARD_LEFT && castlingRights[move][5] >= 0 ) *p++ = 'q'; } -#endif - if (q == p) *p++ = '-'; - *p++ = ' '; - } - - /* POP Fabien & Tord */ + } + if (q == p) *p++ = '-'; /* No castling rights */ + *p++ = ' '; + } + if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { /* En passant target square */ if (move > backwardMostMove) { fromX = moveList[move - 1][0] - AAA; @@ -11610,44 +12712,26 @@ PositionToFEN(move, useFEN960) } else { *p++ = '-'; } - - /* [HGM] print Crazyhouse holdings */ - if( gameInfo.variant == VariantCrazyhouse ) { - *p++ = ' '; q = p; - for(i=0; i backwardMostMove && epStatus[j] <= EP_NONE) j--,i++; if( j == backwardMostMove ) i += initialRulePlies; - sprintf(p, " %d", i); - p += i>=100 ? 4 : i >= 10 ? 3 : 2; + sprintf(p, "%d ", i); + p += i>=100 ? 4 : i >= 10 ? 3 : 2; } /* Fullmove number */ - sprintf(p, " %d", (move / 2) + 1); + sprintf(p, "%d", (move / 2) + 1); return StrSave(buf); } @@ -11666,21 +12750,21 @@ ParseFEN(board, blackPlaysFirst, fen) p = fen; /* [HGM] by default clear Crazyhouse holdings, if present */ - if(gameInfo.holdingsWidth) { + if(gameInfo.holdingsWidth) { for(i=0; i= 0; i--) { j = 0; for (;;) { - if (*p == '/' || *p == ' ') { - if (*p == '/') p++; + if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) { + if (*p == '/') p++; emptycount = gameInfo.boardWidth - j; while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; @@ -11698,11 +12782,19 @@ ParseFEN(board, blackPlaysFirst, fen) if (j + emptycount > gameInfo.boardWidth) return FALSE; while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; - } else if (isalpha(*p)) { + } else if (*p == '+' || isalpha(*p)) { if (j >= gameInfo.boardWidth) return FALSE; - piece = CharToPiece(*p++); + if(*p=='+') { + piece = CharToPiece(*++p); + if(piece == EmptySquare) return FALSE; /* unknown piece */ + piece = (ChessSquare) (PROMOTED piece ); p++; + if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */ + } else piece = CharToPiece(*p++); + + if(piece==EmptySquare) return FALSE; /* unknown piece */ if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */ - piece = (ChessSquare) ((int)piece + (int)WhiteAlfil - (int)WhitePawn); + piece = (ChessSquare) (PROMOTED piece); + if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */ p++; } board[i][(j++)+gameInfo.holdingsWidth] = piece; @@ -11713,6 +12805,34 @@ ParseFEN(board, blackPlaysFirst, fen) } while (*p == '/' || *p == ' ') p++; + /* [HGM] look for Crazyhouse holdings here */ + while(*p==' ') p++; + if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') { + if(*p == '[') 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 */ + /* have to add holdings and shift the board read so far here */ + while( (piece = CharToPiece(*p) ) != EmptySquare ) { + *p++; + if((int) piece >= (int) BlackPawn ) { + i = (int)piece - (int)BlackPawn; + if( i >= BOARD_HEIGHT ) return FALSE; + board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */ + board[BOARD_HEIGHT-1-i][1]++; /* black counts */ + } else { + i = (int)piece - (int)WhitePawn; + if( i >= BOARD_HEIGHT ) return FALSE; + board[i][BOARD_WIDTH-1] = piece; /* white holdings */ + board[i][BOARD_WIDTH-2]++; /* black holdings */ + } + } + } + if(*p == ']') *p++; + } + + while(*p == ' ') p++; + /* Active color */ switch (*p++) { case 'w': @@ -11727,11 +12847,12 @@ ParseFEN(board, blackPlaysFirst, fen) /* [HGM] We NO LONGER ignore the rest of the FEN notation */ /* return the extra info in global variiables */ - { + /* set defaults in case FEN is incomplete */ FENepStatus = EP_UNKNOWN; for(i=0; i=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1; if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1; @@ -11742,79 +12863,102 @@ ParseFEN(board, blackPlaysFirst, fen) FENrulePlies = 0; while(*p==' ') p++; - - if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') { - /* castling indicator present, so default is no castlings */ - for(i=0; i= 'a' && *p < 'a' + gameInfo.boardWidth) || + ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { + char c = *p++; int whiteKingFile=-1, blackKingFile=-1; + + for(i=BOARD_LEFT; i>1; + for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--); + FENcastlingRights[0] = i != whiteKingFile ? i : -1; + FENcastlingRights[2] = whiteKingFile; break; case'Q': - FENcastlingRights[1] = BOARD_LEFT; - FENcastlingRights[2] = BOARD_WIDTH>>1; + for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i>1; + for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--); + FENcastlingRights[3] = i != blackKingFile ? i : -1; + FENcastlingRights[5] = blackKingFile; break; case'q': - FENcastlingRights[4] = BOARD_LEFT; - FENcastlingRights[5] = BOARD_WIDTH>>1; + for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i= 'a') { /* black rights */ + for(i=BOARD_LEFT; i= BlackKing ) break; + if(c > i) + FENcastlingRights[3] = c; + else + FENcastlingRights[4] = c; + } else { /* white rights */ + for(i=BOARD_LEFT; i= WhiteKing) break; + if(c > i) + FENcastlingRights[0] = c; + else + FENcastlingRights[1] = c; + } } + } + if (appData.debugMode) { + fprintf(debugFP, "FEN castling rights:"); + for(i=0; i= BOARD_RGHT) return TRUE; - if(*p >= '0' && *p <='9') *p++; - FENepStatus = c; - } + } else { + char c = *p++ - AAA; - /* [HGM] look for Crazyhouse holdings here */ - while(*p==' ') p++; - if( !isdigit(*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 */ - /* have to add holdings and shift the board read so far here */ - while( (piece = CharToPiece(*p) ) != EmptySquare ) { - *p++; - if((int) piece >= (int) BlackPawn ) { - i = (int)piece - (int)BlackPawn; - if( i >= BOARD_HEIGHT ) return FALSE; - board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */ - board[BOARD_HEIGHT-1-i][1]++; /* black counts */ - } else { - i = (int)piece - (int)WhitePawn; - if( i >= BOARD_HEIGHT ) return FALSE; - board[i][BOARD_WIDTH-1] = piece; /* white holdings */ - board[i][BOARD_WIDTH-2]++; /* black holdings */ - } - } - } + if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE; + if(*p >= '0' && *p <='9') *p++; + FENepStatus = c; + } } - if(sscanf(p, "%d", &i) == 1) { FENrulePlies = i; /* 50-move ply counter */ /* (The move number is still ignored) */ } - } + return TRUE; }