From 681dfc03596526017b3b8f39caef7f8f7c032987 Mon Sep 17 00:00:00 2001 From: H.G. Muller Date: Wed, 19 Jan 2011 11:46:41 +0100 Subject: [PATCH] Allow null move in analysis and edit-game mode In some games it is allowed to pass your turn. The internal representation picked for such a null move is the drop of an empty square, (EmptySquare,DROP_RANK,0,0). Computer-algebraic form is "@@@@", SAN form is "--", (for compatibility with ChessBase / SCID), and the non-compliant forms "null", "pass"and "Z0" are recognized in the parser as well. ApplyMove() has to test for this special case (to not actually clear a1), and a way to enter the pass for the user had to be found (clicking the opponent clock, like in EditPosition mode). The null move is made move irreversible to prevent repetition draws spanning null moves being adjudicated. Such entry of null move is now allowed in AnalyzeMode and EditGame mode. Because Chess engines will not accept null moves, SendMoveToProgram had to be adapted to send not the move, but the position after it in case of a null move. This erases the move history in the engine, so it will not react to 'undo' for that null move, so BackwardInner had to be adapted to test for intervening null moves, and if one is found, approach the target from the other side after loading the earliest position before it not crossing another null move, and then loading the moves upto the target. XBoard clock clicks had to be fixed, to pay attention to the shift key. --- backend.c | 41 ++++++++++++++++++++++++++++++++++++----- moves.c | 1 + parser.c | 4 ++++ xboard.c | 10 ++++++++-- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/backend.c b/backend.c index f0289bc..a304ca5 100644 --- a/backend.c +++ b/backend.c @@ -4861,6 +4861,11 @@ SendMoveToProgram(moveNum, cps) { char buf[MSG_SIZ]; + if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') { + // null move in variant where engine does not understand it (for analysis purposes) + SendBoard(cps, moveNum + 1); // send position after move in stead. + return; + } if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -4901,6 +4906,7 @@ SendMoveToProgram(moveNum, cps) } else if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move + if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0], moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); } else @@ -5076,6 +5082,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) char move[7]; { if (rf == DROP_RANK) { + if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass sprintf(move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt); } else { @@ -6527,6 +6534,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* [HGM] always test for legality, to get promotion info */ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar); + + if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove; + /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { @@ -6668,6 +6678,9 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) // [HGM] book: if program might be playing, let it use book bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); first.maybeThinking = TRUE; + } else if(fromY == DROP_RANK && fromX == EmptySquare) { + if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard + SendBoard(&first, currentMove+1); } else SendMoveToProgram(forwardMostMove-1, &first); if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; @@ -9006,15 +9019,19 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) oldEP = (signed char)board[EP_STATUS]; board[EP_STATUS] = EP_NONE; - if( board[toY][toX] != EmptySquare ) - board[EP_STATUS] = EP_CAPTURE; - if (fromY == DROP_RANK) { /* must be first */ + if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change. + board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible + return; + } piece = board[toY][toX] = (ChessSquare) fromX; } else { int i; + if( board[toY][toX] != EmptySquare ) + board[EP_STATUS] = EP_CAPTURE; + if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants @@ -13559,7 +13576,9 @@ ClockClick(int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetBlackToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack) { @@ -13569,7 +13588,9 @@ ClockClick(int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetWhiteToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingBlack || gameMode == MachinePlaysWhite) { @@ -13846,6 +13867,16 @@ BackwardInner(target) if (gameMode == EditGame || gameMode==AnalyzeMode || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { while (currentMove > target) { + if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') { + // null move cannot be undone. Reload program with move history before it. + int i; + for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move + if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; + } + SendBoard(&first, i); + for(currentMove=i; currentMove: XawPositionSimpleMenu(menuB) XawPositionSimpleMenu(menuD) \ PieceMenuPopup(menuB) \n"; -char whiteTranslations[] = ": WhiteClock()\n"; -char blackTranslations[] = ": BlackClock()\n"; +char whiteTranslations[] = + "Shift: WhiteClock(1)\n \ + : WhiteClock(0)\n"; +char blackTranslations[] = + "Shift: BlackClock(1)\n \ + : BlackClock(0)\n"; char ICSInputTranslations[] = "Up: UpKeyProc() \n " @@ -4141,6 +4145,7 @@ void WhiteClock(w, event, prms, nprms) String *prms; Cardinal *nprms; { + shiftKey = prms[0][0] & 1; ClockClick(0); } @@ -4150,6 +4155,7 @@ void BlackClock(w, event, prms, nprms) String *prms; Cardinal *nprms; { + shiftKey = prms[0][0] & 1; ClockClick(1); } -- 1.7.0.4