X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=859a47f22594cc42cc1e1b41c612a696d5086392;hb=refs%2Ftags%2Fv4.3.14;hp=29ebca685c9172a5af0db0a045a20675489a6f4a;hpb=056614196635a4730170261fe0e638191f14c620;p=xboard.git diff --git a/backend.c b/backend.c index 29ebca6..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, @@ -421,70 +443,98 @@ ChessSquare twoKingsArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteKing, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, - BlackKing, BlackKing, BlackKnight, BlackRook } + BlackKing, BlackKing, BlackKnight, BlackRook } +}; + +ChessSquare KnightmateArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen, + WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook }, + { BlackRook, BlackMan, BlackBishop, BlackQueen, + BlackUnicorn, BlackBishop, BlackMan, BlackRook } }; -#ifdef FAIRY ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */ - { WhiteFairyRook, WhiteFairyKnight, WhiteFairyBishop, WhiteQueen, + { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackFairyRook, BlackFairyKnight, BlackFairyBishop, BlackQueen, + { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen, BlackKing, BlackBishop, BlackKnight, BlackRook } }; ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */ - { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn, - WhiteKing, WhiteFairyBishop, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn, - BlackKing, BlackFairyBishop, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing, + WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackKing, + BlackFerz, BlackAlfil, BlackKnight, BlackRook } +}; + + +#if (BOARD_SIZE>=10) +ChessSquare ShogiArray[2][BOARD_SIZE] = { + { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir, + WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen }, + { BlackQueen, BlackKnight, BlackFerz, BlackWazir, + BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen } }; -#if (BOARD_SIZE>=9) ChessSquare XiangqiArray[2][BOARD_SIZE] = { - { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn, - WhiteKing, WhiteFairyPawn, WhiteFairyBishop, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn, - BlackKing, BlackFairyPawn, BlackFairyBishop, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz, + WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackFerz, + BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook } }; -#else -#define XiangqiPosition FIDEPosition -#endif -#if (BOARD_SIZE>=10) 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 #define GothicArray FIDEArray #endif // !(BOARD_SIZE>=10) #if (BOARD_SIZE>=12) ChessSquare CourierArray[2][BOARD_SIZE] = { - { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteBishop, WhiteFairyKing, WhiteKing, - WhiteFairyPawn, WhiteFairyRook, WhiteBishop, WhiteFairyBishop, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackFairyBishop, BlackBishop, BlackFairyKing, BlackKing, - BlackFairyPawn, BlackFairyRook, BlackBishop, BlackFairyBishop, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing, + WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing, + BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook } }; #else // !(BOARD_SIZE>=12) #define CourierArray CapablancaArray #endif // !(BOARD_SIZE>=12) -#endif // FAIRY Board initialPosition; @@ -513,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; @@ -628,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; @@ -654,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] */ @@ -733,25 +806,32 @@ InitBackEnd1() case VariantLoadable: case Variant29: case Variant30: -#ifndef FAIRY case Variant31: case Variant32: case Variant33: case Variant34: case Variant35: case Variant36: -#endif default: sprintf(buf, "Unknown variant name %s", appData.variant); DisplayFatalError(buf, 0, 2); return; + case VariantXiangqi: /* [HGM] repetition rules not implemented */ + case VariantFairy: /* [HGM] TestLegality definitely off! */ + case VariantGothic: /* [HGM] should work */ + case VariantCapablanca: /* [HGM] should work */ + case VariantCourier: /* [HGM] initial forced moves not implemented */ + case VariantShogi: /* [HGM] drops not tested for legality */ + 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 VariantCrazyhouse: /* holdings not shown, - offboard interposition not understood */ + 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, @@ -761,15 +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 */ -#ifdef FAIRY - case VariantShogi: - case VariantXiangqi: - case VariantFairy: /* [HGM] TestLegality definitely off! */ - case VariantGothic: - case VariantCapablanca: - case VariantCourier: -#endif + case VariantShatranj: /* should work except for all win conditions */ + case VariantBerolina: /* might work if TestLegality is off */ + case VariantCapaRandom: /* should work */ break; } } @@ -817,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 @@ -855,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; @@ -925,7 +1041,7 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err; - InitChessProgram(&first); + InitChessProgram(&first, startedFromSetupPosition); if (appData.icsActive) { err = establish(); @@ -1028,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; + } else { + holdingsColumn = BOARD_WIDTH-1; + countsColumn = BOARD_WIDTH-2; + holdingsStartRow = 0; + direction = 1; + } + + for(i=0; i= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */ + if(j < 0) continue; /* should not happen */ + 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. */ @@ -2275,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; } @@ -2652,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*/ @@ -2725,9 +3004,9 @@ read_from_ics(isr, closure, data, count, error) ClearPremoveHighlights(); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); - UserMoveEvent(premoveFromX, premoveFromY, + UserMoveEvent(premoveFromX, premoveFromY, premoveToX, premoveToY, - premovePromoChar); + premovePromoChar); } } @@ -2744,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) { @@ -2763,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, @@ -2780,7 +3068,8 @@ read_from_ics(isr, closure, data, count, error) gameInfo.white, white_holding, gameInfo.black, black_holding); } - DrawPosition(FALSE, NULL); + + DrawPosition(FALSE, boards[currentMove]); DisplayTitle(str); } /* Suppress following prompt */ @@ -2996,7 +3285,13 @@ ParseBoard12(string) timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); - gameInfo.variant = StringToVariant(gameInfo.event); + 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? */ @@ -3058,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. */ @@ -3148,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], "#"); @@ -3169,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); @@ -3181,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). */ @@ -3215,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); } } @@ -3254,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); + } } @@ -3319,6 +3702,7 @@ SendMoveToProgram(moveNum, cps) ChessProgramState *cps; { char buf[MSG_SIZ]; + if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -3332,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) { - int fromX = moveList[moveNum][0] - 'a'; + 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] - 'a'; + 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); } @@ -3359,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 @@ -3404,28 +3802,31 @@ 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", - 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY, + 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; case WhiteDrop: case BlackDrop: sprintf(user_move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) fromX)), - 'a' + toX, ONE + toY); + AAA + toX, ONE + toY); break; case NormalMove: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case IllegalMove: /* could be a variant we don't quite understand */ sprintf(user_move, "%c%c%c%c\n", - 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY); + AAA + fromX, ONE + fromY, AAA + toX, ONE + toY); break; } SendToICS(user_move); @@ -3439,16 +3840,17 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) { if (rf == DROP_RANK) { sprintf(move, "%c@%c%c\n", - ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, ONE + rt); + ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt); } else { if (promoChar == 'x' || promoChar == NULLCHAR) { sprintf(move, "%c%c%c%c\n", - 'a' + ff, ONE + rf, 'a' + ft, ONE + rt); + AAA + ff, ONE + rf, AAA + ft, ONE + rt); } else { sprintf(move, "%c%c%c%c%c\n", - 'a' + ff, ONE + rf, 'a' + ft, ONE + rt, promoChar); + AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); } } + AlphaRank(move, 4); } void @@ -3465,6 +3867,57 @@ ProcessICSInitScript(f) } +/* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */ +void +AlphaRank(char *move, int n) +{ + char *p = move, c; int x, y; + + if( !appData.alphaRank ) return; + + 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); + } +} + /* Parser for moves from gnuchess, ICS, or user typein box */ Boolean ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) @@ -3474,15 +3927,16 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) int *fromX, *fromY, *toX, *toY; char *promoChar; { + if (appData.debugMode) { + fprintf(debugFP, "move to parse: %s\n", move); + } *moveType = yylexstr(moveNum, move); switch (*moveType) { -#ifdef FAIRY case WhitePromotionChancellor: case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: -#endif case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -3511,13 +3965,16 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackASideCastleFR: /* End of code added by Tord */ case IllegalMove: /* bug or odd chess variant */ - *fromX = currentMoveString[0] - 'a'; + *fromX = currentMoveString[0] - AAA; *fromY = currentMoveString[1] - ONE; - *toX = currentMoveString[2] - 'a'; + *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; *promoChar = currentMoveString[4]; - if (*fromX < 0 || *fromX >= BOARD_WIDTH || *fromY < 0 || *fromY >= BOARD_HEIGHT || - *toX < 0 || *toX >= BOARD_WIDTH || *toY < 0 || *toY >= BOARD_HEIGHT) { + 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; } @@ -3531,9 +3988,9 @@ 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] - 'a'; + *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; *promoChar = NULLCHAR; return TRUE; @@ -3549,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; @@ -3572,6 +4032,7 @@ static int FindEmptySquare( Board board, int n ) return i; } +#if 0 static void ShuffleFRC( Board board ) { int i; @@ -3587,11 +4048,17 @@ 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; - - for( i=0; 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_WIDTH-1; - castlingRights[0][4] = initialRights[4] = 0; - 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 */ } @@ -3683,17 +4325,24 @@ InitPosition(redraw) /* empty squares. This initial position is then copied to boards[0], */ /* possibly after shuffling, so that it remains available. */ + gameInfo.holdingsWidth = 0; /* default board sizes */ + 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; + SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); + break; + case VariantShogi: + pieces = ShogiArray; + gameInfo.boardWidth = 9; + gameInfo.boardHeight = 9; + gameInfo.holdingsSize = 7; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); break; case VariantCourier: pieces = CourierArray; + gameInfo.boardWidth = 12; nrCastlingRights = 0; + SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); for(i=0; i= 0) { + if(gameInfo.boardWidth != appData.NrFiles) overrule++; + gameInfo.boardWidth = appData.NrFiles; + } + if(appData.NrRanks >= 0) { + gameInfo.boardHeight = appData.NrRanks; + } + if(appData.holdingsSize >= 0) { + i = appData.holdingsSize; + if(i > gameInfo.boardHeight) i = gameInfo.boardHeight; + gameInfo.holdingsSize = i; + } + if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2; + 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 common variants */ + if(pawnRow < 1) pawnRow = 1; + + /* User pieceToChar list overrules defaults */ + if(appData.pieceToCharTable != NULL) + SetCharTable(pieceToChar, appData.pieceToCharTable); + + for( j=0; j= BOARD_RGHT || overrule) continue; + initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[pawnRow][j] = WhitePawn; + initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn; if(gameInfo.variant == VariantXiangqi) { if(j&1) { - initialPosition[3][j] = - initialPosition[BOARD_HEIGHT-4][j] = EmptySquare; - if(j==1 || j>=BOARD_WIDTH-2) { - initialPosition[2][j] = WhiteFairyMarshall; - initialPosition[BOARD_HEIGHT-3][j] = BlackFairyMarshall; + initialPosition[pawnRow][j] = + initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare; + if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) { + initialPosition[2][j] = WhiteCannon; + initialPosition[BOARD_HEIGHT-3][j] = BlackCannon; } - } else { - initialPosition[3][j] = WhitePawn; - initialPosition[BOARD_HEIGHT-4][j] = BlackPawn; } - } else { - initialPosition[1][j] = WhitePawn; - initialPosition[BOARD_HEIGHT-2][j] = BlackPawn; } - initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j]; + initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth]; } + if( (gameInfo.variant == VariantShogi) && !overrule ) { + + j=BOARD_LEFT+1; + initialPosition[1][j] = WhiteBishop; + initialPosition[BOARD_HEIGHT-2][j] = BlackRook; + j=BOARD_RGHT-2; + initialPosition[1][j] = WhiteRook; + initialPosition[BOARD_HEIGHT-2][j] = BlackBishop; + } + + if( nrCastlingRights == -1) { + /* [HGM] Build normal castling rights (must be done after board sizing!) */ + /* This sets default castling rights from none to normal corners */ + /* Variants with other castling rights must set them themselves above */ + nrCastlingRights = 6; + + castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1; + castlingRights[0][1] = initialRights[1] = BOARD_LEFT; + castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>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 ); @@ -3766,10 +4510,41 @@ 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]; - for (j = 0; j < BOARD_WIDTH; j++, bp++) { + 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), - 'a' + j, ONE + i); + 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); } } @@ -3810,12 +4594,21 @@ SendBoard(cps, moveNum) SendToProgram("c\n", cps); for (i = BOARD_HEIGHT - 1; i >= 0; i--) { - bp = &boards[moveNum][i][0]; - for (j = 0; j < BOARD_WIDTH; j++, bp++) { + 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)), - 'a' + j, ONE + i); + 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); } } @@ -3823,18 +4616,48 @@ SendBoard(cps, moveNum) SendToProgram(".\n", cps); } + setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } int IsPromotion(fromX, fromY, toX, toY) int fromX, fromY, toX, toY; { - return gameMode != EditPosition && gameInfo.variant != VariantXiangqi && - fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 && - ((boards[currentMove][fromY][fromX] == WhitePawn && toY == BOARD_HEIGHT-1) || - (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0)); + /* [HGM] add Shogi promotions */ + int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn; + ChessSquare piece; + + if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || + !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE; + /* [HGM] Note to self: line above also weeds out drops */ + piece = boards[currentMove][fromY][fromX]; + if(gameInfo.variant == VariantShogi) { + promotionZoneSize = 3; + 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) + return FALSE; + highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece; + } else { + if( toY < BOARD_HEIGHT - promotionZoneSize && + fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE; + } + return ( (int)piece <= highestPromotingPiece ); } +int +InPalace(row, column) + int row, column; +{ /* [HGM] for Xiangqi */ + if( (row < 3 || row > BOARD_HEIGHT-4) && + column < (BOARD_WIDTH + 4)/2 && + column > (BOARD_WIDTH - 5)/2 ) return TRUE; + return FALSE; +} int PieceForSquare (x, y) @@ -3958,18 +4781,38 @@ char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = (ChessMove) 0; -void -UserMoveEvent(fromX, fromY, toX, toY, promoChar) +ChessMove +UserMoveTest(fromX, fromY, toX, toY, promoChar) int fromX, fromY, toX, toY; int promoChar; { ChessMove moveType; + ChessSquare pdown, pup; - if (fromX < 0 || fromY < 0) return; + if (fromX < 0 || fromY < 0) return ImpossibleMove; if ((fromX == toX) && (fromY == toY)) { - return; - } - + return ImpossibleMove; + } + + /* [HGM] suppress all moves into holdings area and guard band */ + 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! @@ -3990,13 +4833,13 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) case IcsIdle: /* We switched into a game mode where moves are not accepted, perhaps while the mouse button was down. */ - return; + return ImpossibleMove; case MachinePlaysWhite: /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); - return; + return ImpossibleMove; } break; @@ -4004,7 +4847,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); - return; + return ImpossibleMove; } break; @@ -4018,13 +4861,13 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); - return; + return ImpossibleMove; } } else { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); - return; + return ImpossibleMove; } } break; @@ -4046,7 +4889,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } - return; + return ImpossibleMove; } break; @@ -4067,7 +4910,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } - return; + return ImpossibleMove; } break; @@ -4075,6 +4918,8 @@ UserMoveEvent(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]); @@ -4083,24 +4928,72 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) boards[0][fromY][fromX] = EmptySquare; DrawPosition(FALSE, boards[currentMove]); } - return; + return ImpossibleMove; + } + + /* [HGM] If move started in holdings, it means a drop */ + 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 */ } - if (toX < 0 || toY < 0) return; 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; + return ImpossibleMove; } - } else { - moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); } + return moveType; + /* [HGM] in stead of calling FinishMove directly, this + function is made into one that returns an OK move type if FinishMove + should be called. This to give the calling driver routine the + opportunity to finish the userMove input with a promotion popup, + without bothering the user with this for invalid or illegal moves */ + +/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */ +} + +/* Common tail of UserMoveEvent and DropMenuEvent */ +void +FinishMove(moveType, fromX, fromY, toX, toY, promoChar) + ChessMove moveType; + int fromX, fromY, toX, toY; + /*char*/int promoChar; +{ + /* [HGM] kludge to avoid having know the exact promotion + move type in caller when we know the move is a legal promotion */ + if(moveType == NormalMove) + moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); + + /* [HGM] convert drag-and-drop piece drops to standard form */ + if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; + fromX = boards[currentMove][fromY][fromX]; + fromY = DROP_RANK; + } + + /* [HGM] The following if has been moved here from + 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) { /* compare the move played on the board to the next move in the * game. If they match, display the move and the opponent's response. @@ -4136,16 +5029,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) return; } - FinishMove(moveType, fromX, fromY, toX, toY, promoChar); -} - -/* Common tail of UserMoveEvent and DropMenuEvent */ -void -FinishMove(moveType, fromX, fromY, toX, toY, promoChar) - ChessMove moveType; - int fromX, fromY, toX, toY; - /*char*/int promoChar; -{ /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { @@ -4177,6 +5060,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) sprintf(buf, "name %s\n", gameInfo.white); SendToProgram(buf, &first); } + StartClocks(); } ModeHighlight(); } @@ -4236,6 +5120,26 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } } +void +UserMoveEvent(fromX, fromY, toX, toY, promoChar) + int fromX, fromY, toX, toY; + int promoChar; +{ + /* [HGM] This routine was added to allow calling of its two logical + parts from other modules in the old way. Before, UserMoveEvent() + automatically called FinishMove() if the move was OK, and returned + otherwise. I separated the two, in order to make it possible to + slip a promotion popup in between. But that it always needs two + calls, to the first part, (now called UserMoveTest() ), and to + FinishMove if the first part succeeded. Calls that do not need + to do anything in between, can call this routine the old way. + */ + ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar); + + if(moveType != ImpossibleMove) + FinishMove(moveType, fromX, fromY, toX, toY, promoChar); +} + void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats ) { char * hint = lastHint; @@ -4279,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) { @@ -4370,15 +5279,22 @@ HandleMachineMove(message, cps) return; } - if (!ParseOneMove(machineMove, forwardMostMove, &moveType, - &fromX, &fromY, &toX, &toY, &promoChar)) { + 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. */ sprintf(buf1, "Illegal move \"%s\" from %s machine", machineMove, cps->which); DisplayError(buf1, 0); + sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); if (gameMode == TwoMachinesPlay) { GameEnds(machineWhite ? BlackWins : WhiteWins, - "Forfeit due to illegal move", GE_XBOARD); + buf1, GE_XBOARD); } return; } @@ -4388,21 +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 && - 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) - { static char buf[MSG_SIZ]; + 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(buf, "Xboard: Forfeit due to illegal move %s (%c%c%c%c)%c", - machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0); - GameEnds(machineWhite ? BlackWins : WhiteWins, buf, 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; @@ -4416,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 */ @@ -4426,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; @@ -4464,10 +5411,11 @@ HandleMachineMove(message, cps) #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines - if( gameMode == TwoMachinesPlay ) { + if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) { int count = 0, epFile = epStatus[forwardMostMove]; - if(appData.testLegality) // don't wait for engine to announce game end if we can judge ourselves + if(appData.testLegality && appData.checkMates) + // don't wait for engine to announce game end if we can judge ourselves switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile, castlingRights[forwardMostMove]) ) { @@ -4491,12 +5439,12 @@ 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; /* First absolutely insufficient mating material. Count what is on board. */ - for(i=0; i 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 */ @@ -4549,14 +5513,14 @@ HandleMachineMove(message, cps) || NrWN==2 || NrBN==2 /* KNNK */ || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ ) ) { - if(--moveCount < 0 && adjudicateLossThreshold != 0) + if(--moveCount < 0 && appData.trivialDraws) { /* if the first 3 moves do not show a tactical win, declare draw */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); 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], @@ -4565,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) ) @@ -4596,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 - && adjudicateLossThreshold != 0) { + && appData.drawRepeats > 1) { /* adjudicate after user-specified nr of repeats */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); @@ -4628,11 +5600,32 @@ HandleMachineMove(message, cps) if( count >= 100) epStatus[forwardMostMove] = EP_RULE_DRAW; /* this is used to judge if draw claims are legal */ - if(adjudicateLossThreshold != 0 && count >= 2*appData.ruleMoves) { + if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { 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; + } + } + } @@ -4647,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'); @@ -4688,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. @@ -4868,11 +5891,8 @@ HandleMachineMove(message, cps) /* [HGM] illegal-move claim should forfeit game when Xboard */ /* only passes fully legal moves */ if( appData.testLegality && gameMode == TwoMachinesPlay ) { - static char buf[MSG_SIZ]; - sprintf(buf, "False illegal-move claim on %s (%c%c%c%c)%c", - machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0); GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, - buf, GE_XBOARD ); + "False illegal-move claim", GE_XBOARD ); } return; } @@ -4986,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) { @@ -5083,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) { @@ -5157,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) @@ -5386,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: @@ -5419,10 +6462,9 @@ ParseGameHistory(game) case BlackHSideCastleFR: case BlackASideCastleFR: /* POP Fabien */ - case IllegalMove: /* maybe suicide chess, etc. */ - fromX = currentMoveString[0] - 'a'; + fromX = currentMoveString[0] - AAA; fromY = currentMoveString[1] - ONE; - toX = currentMoveString[2] - 'a'; + toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; break; @@ -5432,18 +6474,28 @@ ParseGameHistory(game) (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); fromY = DROP_RANK; - toX = currentMoveString[2] - 'a'; + toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = NULLCHAR; break; 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 */ @@ -5516,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], "#"); @@ -5533,102 +6586,151 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) int promoChar; Board board; { - /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier) - && promoChar != 0) promoChar = 'f'; + 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; + + 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) { - board[0][BOARD_WIDTH-2] = WhiteKing; board[0][BOARD_WIDTH-3] = WhiteRook; + board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook; } else { - board[0][2] = WhiteKing; board[0][3] = WhiteRook; + board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook; } } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook) { board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if(toX > fromX) { - board[BOARD_HEIGHT-1][BOARD_WIDTH-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_WIDTH-3] = BlackRook; + board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook; } else { - board[BOARD_HEIGHT-1][2] = BlackKing; board[BOARD_HEIGHT-1][3] = BlackRook; + board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook; } /* End of code added by Tord */ - } 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[fromY][BOARD_WIDTH-1] = EmptySquare; - board[toY][toX-1] = WhiteRook; - } else if (initialPosition[fromY][fromX] == WhiteKing - && board[fromY][fromX] == WhiteKing + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; + board[fromY][BOARD_RGHT-1] = EmptySquare; + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][0] = 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; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; + board[fromY][BOARD_LEFT] = EmptySquare; } 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==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[fromY][BOARD_WIDTH-1] = EmptySquare; - board[toY][toX-1] = BlackRook; - } else if (initialPosition[fromY][fromX] == BlackKing - && board[fromY][fromX] == BlackKing + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; + board[fromY][BOARD_RGHT-1] = EmptySquare; + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ && toY == fromY && toX < fromX-1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackKing; - board[fromY][0] = EmptySquare; - board[toY][toX+1] = BlackRook; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; + board[fromY][BOARD_LEFT] = EmptySquare; } else if (fromY == 7 && fromX == 3 && board[fromY][fromX] == BlackKing && toY == 7 && toX == 5) { @@ -5645,50 +6747,108 @@ 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==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; } - if (gameInfo.variant == VariantCrazyhouse) { -#if 0 - /* !!A lot more code needs to be written to support holdings */ + + /* [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 */ + /* [HGM] OK, so I have written it. Holdings are stored in the */ + /* penultimate board files, so they are automaticlly stored */ + /* in the game history. */ if (fromY == DROP_RANK) { - /* Delete from holdings */ - if (holdings[(int) fromX] > 0) holdings[(int) fromX]--; + /* Delete from holdings, by decreasing count */ + /* and erasing image if necessary */ + p = (int) fromX; + if(p < (int) BlackPawn) { /* white drop */ + p -= (int)WhitePawn; + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[p][BOARD_WIDTH-2] == 0) + board[p][BOARD_WIDTH-1] = EmptySquare; + } else { /* black drop */ + p -= (int)BlackPawn; + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[BOARD_HEIGHT-1-p][1] == 0) + board[BOARD_HEIGHT-1-p][0] = EmptySquare; + } } - if (captured != EmptySquare) { - /* Add to holdings */ - if (captured < BlackPawn) { - holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++; + if (captured != EmptySquare && gameInfo.holdingsSize > 0 + && gameInfo.variant != VariantBughouse ) { + /* Add to holdings, if holdings exist */ + p = (int) captured; + if (p >= (int) BlackPawn) { + p -= (int)BlackPawn; + if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { + /* in Shogi restore piece to its original first */ + captured = (ChessSquare) (DEMOTED captured); + p = DEMOTED p; + } + p = PieceToNumber((ChessSquare)p); + if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; } + board[p][BOARD_WIDTH-2]++; + board[p][BOARD_WIDTH-1] = + BLACK_TO_WHITE captured; } else { - holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++; + p -= (int)WhitePawn; + if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { + 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] = + WHITE_TO_BLACK captured; } } -#endif + } else if (gameInfo.variant == VariantAtomic) { if (captured != EmptySquare) { int y, x; for (y = toY-1; y <= toY+1; y++) { for (x = toX-1; x <= toX+1; x++) { - if (y >= 0 && y < BOARD_WIDTH && x >= 0 && x < BOARD_WIDTH && + if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT && board[y][x] != WhitePawn && board[y][x] != BlackPawn) { board[y][x] = EmptySquare; } @@ -5697,6 +6857,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = EmptySquare; } } + if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') { + /* [HGM] Shogi promotions */ + board[toY][toX] = (ChessSquare) (PROMOTED piece); + } + } /* Updates forwardMostMove */ @@ -5707,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); @@ -5720,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>1 && boards[forwardMostMove][toY][toX-1] == BlackPawn || - toX1 && boards[forwardMostMove][toY][toX-1] == WhitePawn || - toXinitString, cps); if (gameInfo.variant != VariantNormal && - gameInfo.variant != VariantLoadable) { + gameInfo.variant != VariantLoadable + /* [HGM] also send variant if board size non-standard */ + || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0 + ) { char *v = VariantName(gameInfo.variant); - if (StrStr(cps->variants, 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; } - sprintf(buf, "variant %s\n", VariantName(gameInfo.variant)); + + /* [HGM] make prefix for non-standard board size. Awkward testing... */ + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantXiangqi ) + overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantShogi ) + 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 == 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) { + 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 */ + } + } + } 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); @@ -5961,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. @@ -5983,7 +7213,8 @@ GameEnds(result, resultDetails, whosays) } } #endif - return; + endingGame = 0; /* [HGM] crash */ + return; } /* If we're loading the game from a file, stop */ @@ -5993,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. */ @@ -6024,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; @@ -6040,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); @@ -6048,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 || @@ -6066,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) { @@ -6140,7 +7388,8 @@ GameEnds(result, resultDetails, whosays) if (appData.noChessProgram) { gameMode = nextGameMode; ModeHighlight(); - return; + endingGame = 0; /* [HGM] crash */ + return; } if (first.reuse) { @@ -6229,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]; @@ -6245,6 +7495,7 @@ GameEnds(result, resultDetails, whosays) ExitAnalyzeMode(); gameMode = nextGameMode; ModeHighlight(); + endingGame = 0; /* [HGM] crash */ } /* Assumes program was just initialized (initString sent). @@ -6260,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); @@ -6279,7 +7541,7 @@ ResurrectChessProgram() if (appData.noChessProgram || first.pr != NoProc) return; StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); if (!first.sendTime) { @@ -6310,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; @@ -6337,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) { @@ -6353,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); @@ -6397,7 +7674,7 @@ AutoPlayOneMove() return FALSE; } - toX = moveList[currentMove][2] - 'a'; + toX = moveList[currentMove][2] - AAA; toY = moveList[currentMove][3] - ONE; if (moveList[currentMove][1] == '@') { @@ -6405,7 +7682,7 @@ AutoPlayOneMove() SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[currentMove][0] - 'a'; + fromX = moveList[currentMove][0] - AAA; fromY = moveList[currentMove][1] - ONE; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */ @@ -6420,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; } @@ -6470,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: @@ -6503,9 +7777,9 @@ LoadGameOneMove(readAhead) /* POP Fabien */ if (appData.debugMode) fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString); - fromX = currentMoveString[0] - 'a'; + fromX = currentMoveString[0] - AAA; fromY = currentMoveString[1] - ONE; - toX = currentMoveString[2] - 'a'; + toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; break; @@ -6518,7 +7792,7 @@ LoadGameOneMove(readAhead) (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); fromY = DROP_RANK; - toX = currentMoveString[2] - 'a'; + toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; break; @@ -6627,9 +7901,9 @@ LoadGameOneMove(readAhead) if (appData.debugMode) fprintf(debugFP, "Parsed %s into IllegalMove %s\n", yy_text, currentMoveString); - fromX = currentMoveString[0] - 'a'; + fromX = currentMoveString[0] - AAA; fromY = currentMoveString[1] - ONE; - toX = currentMoveString[2] - 'a'; + toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; } @@ -6661,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(); @@ -6738,9 +8012,9 @@ MakeRegisteredMove() thinkOutput[0] = NULLCHAR; strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]); - fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a'; + fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA; fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE; - toX = cmailMove[lastLoadGameNumber - 1][2] - 'a'; + toX = cmailMove[lastLoadGameNumber - 1][2] - AAA; toY = cmailMove[lastLoadGameNumber - 1][3] - ONE; promoChar = cmailMove[lastLoadGameNumber - 1][4]; MakeMove(fromX, fromY, toX, toY, promoChar); @@ -6872,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); @@ -6921,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); @@ -7080,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; @@ -7094,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; @@ -7167,7 +8451,7 @@ LoadGame(f, gameNumber, title, useList) if (!startedFromSetupPosition) { p = yy_text; for (i = BOARD_HEIGHT - 1; i >= 0; i--) - for (j = 0; j < BOARD_WIDTH; p++) + for (j = BOARD_LEFT; j < BOARD_RGHT; p++) switch (*p) { case '[': case '-': @@ -7212,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) @@ -7253,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]); @@ -7297,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; } @@ -7368,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) { @@ -7395,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: @@ -7404,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--; @@ -7437,7 +8731,7 @@ LoadPosition(f, positionNumber, title) for (i = BOARD_HEIGHT - 1; i >= 0; i--) { (void) fgets(line, MSG_SIZ, f); - for (p = line, j = 0; j < BOARD_WIDTH; p++) { + for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) { if (*p == ' ') continue; initial_position[i][j++] = CharToPiece(*p); @@ -7473,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); @@ -7729,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], @@ -8263,8 +9563,6 @@ ResetGameEvent() } } -static int exiting = 0; - void ExitEvent(status) int status; @@ -8287,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) { @@ -8299,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) { @@ -8672,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); @@ -8911,7 +10225,7 @@ void EditPositionDone() { startedFromSetupPosition = TRUE; - InitChessProgram(&first); + InitChessProgram(&first, FALSE); SendToProgram("force\n", &first); if (blackPlaysFirst) { strcpy(moveList[0], ""); @@ -8922,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; @@ -9004,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; @@ -9021,7 +10339,7 @@ EditPositionMenuEvent(selection, x, y) if (gameMode == IcsExamining) { if (boards[currentMove][y][x] != EmptySquare) { sprintf(buf, "%sx@%c%c\n", ics_prefix, - 'a' + x, ONE + y); + AAA + x, ONE + y); SendToICS(buf); } } else { @@ -9045,7 +10363,7 @@ EditPositionMenuEvent(selection, x, y) case EmptySquare: if (gameMode == IcsExamining) { - sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, ONE + y); + sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y); SendToICS(buf); } else { boards[0][y][x] = EmptySquare; @@ -9053,10 +10371,41 @@ 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), 'a' + x, ONE + y); + PieceToChar(selection), AAA + x, ONE + y); SendToICS(buf); } else { boards[0][y][x] = selection; @@ -9350,14 +10699,14 @@ ForwardInner(target) if (target > 0 && moveList[target - 1][0]) { int fromX, fromY, toX, toY; - toX = moveList[target - 1][2] - 'a'; + toX = moveList[target - 1][2] - AAA; toY = moveList[target - 1][3] - ONE; if (moveList[target - 1][1] == '@') { if (appData.highlightLastMove) { SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[target - 1][0] - 'a'; + fromX = moveList[target - 1][0] - AAA; fromY = moveList[target - 1][1] - ONE; if (target == currentMove + 1) { AnimateMove(boards[currentMove], fromX, fromY, toX, toY); @@ -9385,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]); } } @@ -9453,14 +10802,14 @@ BackwardInner(target) if (moveList[target][0]) { int fromX, fromY, toX, toY; - toX = moveList[target][2] - 'a'; + toX = moveList[target][2] - AAA; toY = moveList[target][3] - ONE; if (moveList[target][1] == '@') { if (appData.highlightLastMove) { SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[target][0] - 'a'; + fromX = moveList[target][0] - AAA; fromY = moveList[target][1] - ONE; if (target == currentMove - 1) { AnimateMove(boards[currentMove], toX, toY, fromX, fromY); @@ -9488,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 @@ -9733,10 +11081,10 @@ PrintPosition(fp, move) int i, j; for (i = BOARD_HEIGHT - 1; i >= 0; i--) { - for (j = 0; j < BOARD_WIDTH; j++) { + for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { char c = PieceToChar(boards[move][i][j]); fputc(c == 'x' ? '.' : c, fp); - fputc(j == BOARD_WIDTH - 1 ? '\n' : ' ', fp); + fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp); } } if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0)) @@ -9777,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; @@ -9937,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++; @@ -9975,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 " ); @@ -9990,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; } } @@ -10003,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 ) { @@ -10031,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 @@ -10056,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); } } @@ -10080,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 { @@ -10096,7 +11480,6 @@ ReceiveFromProgram(isr, closure, message, count, error) DisplayFatalError(buf, error, 1); } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); return; } @@ -10106,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); } @@ -10123,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 */ @@ -10168,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 @@ -10189,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; @@ -10320,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; @@ -10566,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) { @@ -10575,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 @@ -10670,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 @@ -10784,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() @@ -10791,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(""); @@ -10824,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)); @@ -10882,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(); @@ -10932,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)); } @@ -10956,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); } @@ -11124,6 +12589,7 @@ PositionToFEN(move, useFEN960) char buf[128]; char *p, *q; int emptycount; + ChessSquare piece; whiteToPlay = (gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0); @@ -11132,17 +12598,27 @@ PositionToFEN(move, useFEN960) /* Piece placement data */ for (i = BOARD_HEIGHT - 1; i >= 0; i--) { emptycount = 0; - for (j = 0; j < BOARD_WIDTH; j++) { + 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(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++ = '~'; + } } } if (emptycount > 0) { @@ -11155,113 +12631,80 @@ 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 fk; fr--) { /* H side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'K'; - break; - } - } - - for (fr = 0; fr < fk; fr++) { /* A side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'Q'; - break; - } - } - } - } - - /* Black castling rights */ - - for (fk = 1; fk < BOARD_WIDTH-1; fk++) { - - if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) { - - for (fr = BOARD_WIDTH-1; fr > fk; fr--) { /* H side */ - if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) { - *p++ = useFEN960 ? 'a' + fr : 'k'; - break; - } - } - - for (fr = 0; 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; + if(nrCastlingRights) { + q = p; + if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) { + /* [HGM] write directly from rights */ + if(castlingRights[move][2] >= 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 { -#ifdef OLDCASTLINGCODE - if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) { - if (boards[move][0][BOARD_WIDTH-1] == WhiteRook) *p++ = 'K'; - if (boards[move][0][0] == 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][0] == BlackRook) *p++ = 'q'; - } -#else /* [HGM] write true castling rights */ if( nrCastlingRights == 6 ) { - if(castlingRights[move][0] == BOARD_WIDTH-1 && + if(castlingRights[move][0] == BOARD_RGHT-1 && castlingRights[move][2] >= 0 ) *p++ = 'K'; - if(castlingRights[move][1] == 0 && + if(castlingRights[move][1] == BOARD_LEFT && castlingRights[move][2] >= 0 ) *p++ = 'Q'; - if(castlingRights[move][3] == BOARD_WIDTH-1 && + if(castlingRights[move][3] == BOARD_RGHT-1 && castlingRights[move][5] >= 0 ) *p++ = 'k'; - if(castlingRights[move][4] == 0 && + 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] - 'a'; + fromX = moveList[move - 1][0] - AAA; fromY = moveList[move - 1][1] - ONE; - toX = moveList[move - 1][2] - 'a'; + toX = moveList[move - 1][2] - AAA; toY = moveList[move - 1][3] - ONE; if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) && toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) && boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) && fromX == toX) { /* 2-square pawn move just happened */ - *p++ = toX + 'a'; + *p++ = toX + AAA; *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3'; } else { *p++ = '-'; @@ -11269,24 +12712,26 @@ PositionToFEN(move, useFEN960) } else { *p++ = '-'; } + *p++ = ' '; + } /* [HGM] find reversible plies */ { int i = 0, j=move; - if (appData.debugMode) { int k; - fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove); - for(k=backwardMostMove; k<=forwardMostMove; k++) - fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]); + if (appData.debugMode) { int k; + fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove); + for(k=backwardMostMove; k<=forwardMostMove; k++) + fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]); - } + } while(j > 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); } @@ -11300,32 +12745,59 @@ ParseFEN(board, blackPlaysFirst, fen) int i, j; char *p; int emptycount; + ChessSquare piece; p = fen; + /* [HGM] by default clear Crazyhouse holdings, if present */ + if(gameInfo.holdingsWidth) { + for(i=0; i= 0; i--) { j = 0; for (;;) { - if (*p == '/' || *p == ' ') { - if (*p == '/') p++; - emptycount = BOARD_WIDTH - j; - while (emptycount--) board[i][j++] = EmptySquare; + if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) { + if (*p == '/') p++; + emptycount = gameInfo.boardWidth - j; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; break; #if(BOARD_SIZE >= 10) } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */ p++; emptycount=10; - if (j + emptycount > BOARD_WIDTH) return FALSE; - while (emptycount--) board[i][j++] = EmptySquare; + if (j + emptycount > gameInfo.boardWidth) return FALSE; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; #endif } else if (isdigit(*p)) { emptycount = *p++ - '0'; while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */ - if (j + emptycount > BOARD_WIDTH) return FALSE; - while (emptycount--) board[i][j++] = EmptySquare; - } else if (isalpha(*p)) { - if (j >= BOARD_WIDTH) return FALSE; - board[i][j++] = CharToPiece(*p++); + if (j + emptycount > gameInfo.boardWidth) return FALSE; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; + } else if (*p == '+' || isalpha(*p)) { + if (j >= gameInfo.boardWidth) return FALSE; + 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) (PROMOTED piece); + if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */ + p++; + } + board[i][(j++)+gameInfo.holdingsWidth] = piece; } else { return FALSE; } @@ -11333,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': @@ -11347,68 +12847,118 @@ 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; + if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1; + if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1; + if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1; + if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1; FENrulePlies = 0; while(*p==' ') p++; - - if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') { - /* castling indicator present, so default is impossible */ - 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] = 0; - 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] = 0; - 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_WIDTH) return TRUE; - if(*p >= '0' && *p <='9') *p++; - FENepStatus = c; + 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; }