added some comments and formated code
[xboard.git] / xboard.c
index 1675b79..79b61a2 100644 (file)
--- a/xboard.c
+++ b/xboard.c
@@ -2,9 +2,10 @@
  * xboard.c -- X front end for XBoard
  *
  * Copyright 1991 by Digital Equipment Corporation, Maynard,
- * Massachusetts.  Enhancements Copyright
- * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
- * Foundation, Inc.
+ * Massachusetts. 
+ *
+ * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
+ * 2007, 2008, 2009 Free Software Foundation, Inc.
  *
  * The following terms apply to Digital Equipment Corporation's copyright
  * interest in XBoard:
@@ -1021,7 +1022,7 @@ XtResource clientResources[] = {
     { "localLineEditing", "localLineEditing", XtRBoolean,
        sizeof(Boolean), XtOffset(AppDataPtr, localLineEditing),
        XtRImmediate, (XtPointer) True }, /* not implemented, must be True */
-#ifdef ZIPPY
+#if ZIPPY
     { "zippyTalk", "zippyTalk", XtRBoolean,
        sizeof(Boolean), XtOffset(AppDataPtr, zippyTalk),
        XtRImmediate, (XtPointer) ZIPPY_TALK },
@@ -1228,6 +1229,9 @@ XtResource clientResources[] = {
     { "adjudicateLossThreshold", "adjudicateLossThreshold", XtRInt,
        sizeof(int), XtOffset(AppDataPtr, adjudicateLossThreshold),
        XtRImmediate, (XtPointer) 0},
+    { "adjudicateDrawMoves", "adjudicateDrawMoves", XtRInt,
+       sizeof(int), XtOffset(AppDataPtr, adjudicateDrawMoves),
+       XtRImmediate, (XtPointer) 0},
     { "pgnEventHeader", "pgnEventHeader", XtRString,
         sizeof(String), XtOffset(AppDataPtr, pgnEventHeader),
        XtRImmediate, (XtPointer) "Computer Chess Game" },
@@ -1343,7 +1347,7 @@ XtResource clientResources[] = {
        XtRImmediate, (XtPointer) "xboard.debug"},
     { "engineDebugOutput", "engineDebugOutput", XtRInt,
        sizeof(int), XtOffset(AppDataPtr, engineComments),
-       XtRImmediate, (XtPointer) 0},
+       XtRImmediate, (XtPointer) 1},
     { "noGUI", "noGUI", XtRBoolean,
        sizeof(Boolean), XtOffset(AppDataPtr, noGUI),
        XtRImmediate, (XtPointer) 0},
@@ -1397,6 +1401,12 @@ XtResource clientResources[] = {
     { "delayAfterQuit", "delayAfterQuit", XtRInt,
        sizeof(int), XtOffset(AppDataPtr, delayAfterQuit),
        XtRImmediate, (XtPointer) 0},
+    { "keepAlive", "keepAlive", XtRInt,
+       sizeof(int), XtOffset(AppDataPtr, keepAlive),
+       XtRImmediate, (XtPointer) 0},
+    { "forceIllegalMoves", "forceIllegalMoves", XtRBoolean,
+       sizeof(Boolean), XtOffset(AppDataPtr, forceIllegal),
+       XtRImmediate, (XtPointer) False},
 };
 
 XrmOptionDescRec shellOptions[] = {
@@ -1696,6 +1706,7 @@ XrmOptionDescRec shellOptions[] = {
     { "-pgnExtendedInfo", "pgnExtendedInfo", XrmoptionSepArg, NULL },
     { "-hideThinkingFromHuman", "hideThinkingFromHuman", XrmoptionSepArg, NULL },
     { "-adjudicateLossThreshold", "adjudicateLossThreshold", XrmoptionSepArg, NULL },
+    { "-adjudicateDrawMoves", "adjudicateDrawMoves", XrmoptionSepArg, NULL },
     { "-pgnEventHeader", "pgnEventHeader", XrmoptionSepArg, NULL },
     { "-firstIsUCI", "firstIsUCI", XrmoptionSepArg, NULL },
     { "-secondIsUCI", "secondIsUCI", XrmoptionSepArg, NULL },
@@ -1763,6 +1774,8 @@ XrmOptionDescRec shellOptions[] = {
     { "-secondOptions", "secondOptions", XrmoptionSepArg, NULL },
     { "-firstNeedsNoncompliantFEN", "firstNeedsNoncompliantFEN", XrmoptionSepArg, NULL },
     { "-secondNeedsNoncompliantFEN", "secondNeedsNoncompliantFEN", XrmoptionSepArg, NULL },
+    { "-keepAlive", "keepAlive", XrmoptionSepArg, NULL },
+    { "-forceIllegalMoves", "forceIllegalMoves", XrmoptionNoArg, "True" },
 };
 
 
@@ -2285,7 +2298,7 @@ void InitDrawingSizes(BoardSize boardSize, int flags)
        }
 #ifdef GOTHIC
        if(gameInfo.variant == VariantGothic) {
-           ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[i][(int)WhiteSilver];
+           ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteSilver];
        }
 #endif
 #endif
@@ -2315,6 +2328,19 @@ void InitDrawingSizes(BoardSize boardSize, int flags)
 }
 #endif
 
+void EscapeExpand(char *p, char *q)
+{      // [HGM] initstring: routine to shape up string arguments
+       while(*p++ = *q++) if(p[-1] == '\\')
+           switch(*q++) {
+               case 'n': p[-1] = '\n'; break;
+               case 'r': p[-1] = '\r'; break;
+               case 't': p[-1] = '\t'; break;
+               case '\\': p[-1] = '\\'; break;
+               case 0: *p = 0; return;
+               default: p[-1] = q[-1]; break;
+           }
+}
+
 int
 main(argc, argv)
      int argc;
@@ -2373,7 +2399,6 @@ main(argc, argv)
 #endif
 #endif
 
-
     setbuf(stdout, NULL);
     setbuf(stderr, NULL);
     debugFP = stderr;
@@ -2386,43 +2411,43 @@ main(argc, argv)
 
 #ifdef ENABLE_NLS
     XtSetLanguageProc(NULL, NULL, NULL);
-    bindtextdomain(PRODUCT, LOCALEDIR);
-    textdomain(PRODUCT);
+    bindtextdomain(PACKAGE, LOCALEDIR);
+    textdomain(PACKAGE);
 #endif
 
     shellWidget =
       XtAppInitialize(&appContext, "XBoard", shellOptions,
                      XtNumber(shellOptions),
                      &argc, argv, xboardResources, NULL, 0);
-    if (argc > 1) {
+    if (argc > 1) 
+      { /* left over command line arguments, print out help and exit.
+        * Use two columns to print help
+        */
        fprintf(stderr, _("%s: unrecognized argument %s\n"),
                programName, argv[1]);
+
        fprintf(stderr, "Recognized options:\n");
-       for(i = 0; i < XtNumber(shellOptions); i++) {
+       for(i = 0; i < XtNumber(shellOptions); i++) 
+         {
+           /* print first column */
            j = fprintf(stderr, "  %s%s", shellOptions[i].option,
                        (shellOptions[i].argKind == XrmoptionSepArg
                         ? " ARG" : ""));
-           if (i++ < XtNumber(shellOptions)) {
+           /* print second column and end line */
+           if (++i < XtNumber(shellOptions)) 
+             {         
                fprintf(stderr, "%*c%s%s\n", 40 - j, ' ',
                        shellOptions[i].option,
                        (shellOptions[i].argKind == XrmoptionSepArg
                         ? " ARG" : ""));
-           } else {
+             } 
+           else 
+             {
                fprintf(stderr, "\n");
-           }
-       }
+             };
+         };
        exit(2);
-    }
-
-    if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
-       chessDir = ".";
-    } else {
-       if (chdir(chessDir) != 0) {
-           fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
-           perror(chessDir);
-           exit(1);
-       }
-    }
+      };
 
     p = getenv("HOME");
     if (p == NULL) p = "/tmp";
@@ -2436,6 +2461,28 @@ main(argc, argv)
                              clientResources, XtNumber(clientResources),
                              NULL, 0);
 
+    { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
+       static char buf[MSG_SIZ];
+       EscapeExpand(buf, appData.initString);
+       appData.initString = strdup(buf);
+       EscapeExpand(buf, appData.secondInitString);
+       appData.secondInitString = strdup(buf);
+       EscapeExpand(buf, appData.firstComputerString);
+       appData.firstComputerString = strdup(buf);
+       EscapeExpand(buf, appData.secondComputerString);
+       appData.secondComputerString = strdup(buf);
+    }
+
+    if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
+       chessDir = ".";
+    } else {
+       if (chdir(chessDir) != 0) {
+           fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
+           perror(chessDir);
+           exit(1);
+       }
+    }
+
     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
        /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
@@ -3948,9 +3995,23 @@ void CreateXPMPieces()
                if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
                                           &(xpmPieceBitmap2[kind][piece]),
                                           NULL, &attr)) != 0) {
-                   fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
-                           r, buf);
-                   exit(1);
+                   if(piece != (int)WhiteKing && piece > (int)WhiteQueen) {
+                     // [HGM] missing: read of unorthodox piece failed; substitute King.
+                     snprintf(buf, sizeof(buf), "%s/k%s%u.xpm",
+                               ExpandPathName(appData.pixmapDirectory),
+                               xpmkind[kind], ss);
+                       if (appData.debugMode) {
+                           fprintf(stderr, _("(Replace by File:%s:) "), buf);
+                       }
+                       r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
+                                               &(xpmPieceBitmap2[kind][piece]),
+                                               NULL, &attr);
+                   }
+                   if (r != 0) {
+                       fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
+                               r, buf);
+                       exit(1);
+                   }
                }
                if(piece <= (int) WhiteKing) 
                    xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
@@ -5084,10 +5145,40 @@ void HandleUserMove(w, event, prms, nprms)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0;
+    static int second = 0, promotionChoice = 0;
+    ChessMove moveType;
 
     if (w != boardWidget || errorExitStatus != -1) return;
 
+    x = EventToSquare(event->xbutton.x, BOARD_WIDTH);
+    y = EventToSquare(event->xbutton.y, BOARD_HEIGHT);
+    if (!flipView && y >= 0) {
+       y = BOARD_HEIGHT - 1 - y;
+    }
+    if (flipView && x >= 0) {
+       x = BOARD_WIDTH - 1 - x;
+    }
+
+    if(promotionChoice) { // we are waiting for a click to indicate promotion piece
+       if(event->type == ButtonRelease) return; // ignore upclick of click-click destination
+       promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
+       if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
+       if(gameInfo.holdingsWidth && 
+               (WhiteOnMove(currentMove) 
+                       ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
+                       : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
+           // click in right holdings, for determining promotion piece
+           ChessSquare p = boards[currentMove][y][x];
+           if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
+           if(p != EmptySquare) {
+               FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
+               fromX = fromY = -1;
+               return;
+           }
+       }
+       DrawPosition(FALSE, boards[currentMove]);
+       return;
+    }
     if (event->type == ButtonPress) ErrorPopDown();
 
     if (promotionUp) {
@@ -5102,15 +5193,6 @@ void HandleUserMove(w, event, prms, nprms)
        }
     }
 
-    x = EventToSquare(event->xbutton.x, BOARD_WIDTH);
-    y = EventToSquare(event->xbutton.y, BOARD_HEIGHT);
-    if (!flipView && y >= 0) {
-       y = BOARD_HEIGHT - 1 - y;
-    }
-    if (flipView && x >= 0) {
-       x = BOARD_WIDTH - 1 - x;
-    }
-
     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
     if(event->type == ButtonPress
             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
@@ -5120,7 +5202,7 @@ void HandleUserMove(w, event, prms, nprms)
 
     if (fromX == -1) {
        if (event->type == ButtonPress) {
-           /* First square */
+           /* First square, prepare to drag */
            if (OKToStartUserMove(x, y)) {
                fromX = x;
                fromY = y;
@@ -5135,35 +5217,8 @@ void HandleUserMove(w, event, prms, nprms)
     }
 
     /* fromX != -1 */
-    if (event->type == ButtonPress && gameMode != EditPosition &&
-       x >= 0 && y >= 0) {
-       ChessSquare fromP;
-       ChessSquare toP;
-
-       /* Check if clicking again on the same color piece */
-       fromP = boards[currentMove][fromY][fromX];
-       toP = boards[currentMove][y][x];
-       if ((WhitePawn <= fromP && fromP < WhiteKing && // [HGM] this test should go, as UserMoveTest now does it.
-            WhitePawn <= toP && toP <= WhiteKing) ||   //       For now I made it less critical by exempting King
-           (BlackPawn <= fromP && fromP < BlackKing && //       moves, to not interfere with FRC castlings.
-            BlackPawn <= toP && toP <= BlackKing)) {
-           /* Clicked again on same color piece -- changed his mind */
-           second = (x == fromX && y == fromY);
-           if (appData.highlightDragging) {
-               SetHighlights(x, y, -1, -1);
-           } else {
-               ClearHighlights();
-           }
-           if (OKToStartUserMove(x, y)) {
-               fromX = x;
-               fromY = y;
-               DragPieceBegin(event->xbutton.x, event->xbutton.y);
-           }
-           return;
-       }
-    }
-
     if (event->type == ButtonRelease &&        x == fromX && y == fromY) {
+    /* Click on single square in stead of drag-drop */
        DragPieceEnd(event->xbutton.x, event->xbutton.y);
        if (appData.animateDragging) {
            /* Undo animation damage if any */
@@ -5183,7 +5238,34 @@ void HandleUserMove(w, event, prms, nprms)
        return;
     }
 
-    /* Completed move */
+    moveType = UserMoveTest(fromX, fromY, x, y, NULLCHAR, event->type == ButtonRelease);
+
+    if (moveType == Comment) { // kludge for indicating capture-own on Press
+      /* Clicked again on same color piece -- changed his mind */
+      /* note that re-clicking same square always hits same color piece */
+      second = (x == fromX && y == fromY);
+      if (appData.highlightDragging) {
+       SetHighlights(x, y, -1, -1);
+      } else {
+       ClearHighlights();
+      }
+      if (OKToStartUserMove(x, y)) {
+       fromX = x;
+       fromY = y;
+       DragPieceBegin(event->xbutton.x, event->xbutton.y);
+      }
+      return;
+    }
+
+    if(moveType == AmbiguousMove) { // kludge to indicate edit-position move
+      fromX = fromY = -1; 
+      ClearHighlights();
+      DragPieceEnd(event->xbutton.x, event->xbutton.y);
+      DrawPosition(FALSE, boards[currentMove]);
+      return;
+    }
+
+    /* Complete move; (x,y) is now different from (fromX, fromY) on both Press and Release */
     toX = x;
     toY = y;
     saveAnimate = appData.animate;
@@ -5205,22 +5287,38 @@ void HandleUserMove(w, event, prms, nprms)
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
-    if (IsPromotion(fromX, fromY, toX, toY)) {
-       if (appData.alwaysPromoteToQueen) {
-           UserMoveEvent(fromX, fromY, toX, toY, 'q');
+    if (moveType == WhitePromotionKnight || moveType == BlackPromotionKnight ||
+        (moveType == WhitePromotionQueen || moveType == BlackPromotionQueen) &&
+            appData.alwaysPromoteToQueen) { // promotion, but no choice
+      FinishMove(moveType, fromX, fromY, toX, toY, 'q');
+    } else
+    if (moveType == WhitePromotionQueen || moveType == BlackPromotionQueen ) {
+       SetHighlights(fromX, fromY, toX, toY);
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+           // [HGM] super: promotion to captured piece selected from holdings
+           ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
+           promotionChoice = TRUE;
+           // kludge follows to temporarily execute move on display, without promoting yet
+           boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
+           boards[currentMove][toY][toX] = p;
+           DrawPosition(FALSE, boards[currentMove]);
+           boards[currentMove][fromY][fromX] = p; // take back, but display stays
+           boards[currentMove][toY][toX] = q;
+           DisplayMessage("Click in holdings to choose piece", "");
+           return;
+       }
+       PromotionPopUp();
+       goto skipClearingFrom; // the skipped stuff is done asynchronously by PromotionCallback
+    } else
+    if(moveType != ImpossibleMove) { // valid move, but no promotion
+      FinishMove(moveType, fromX, fromY, toX, toY, NULLCHAR);
+    } else { // invalid move; could have set premove
+      ClearHighlights();
+    }
            if (!appData.highlightLastMove || gotPremove) ClearHighlights();
            if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
            fromX = fromY = -1;
-       } else {
-           SetHighlights(fromX, fromY, toX, toY);
-           PromotionPopUp();
-       }
-    } else {
-       UserMoveEvent(fromX, fromY, toX, toY, NULLCHAR);
-       if (!appData.highlightLastMove || gotPremove) ClearHighlights();
-       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
-       fromX = fromY = -1;
-    }
+skipClearingFrom:
     appData.animate = saveAnimate;
     if (appData.animate || appData.animateDragging) {
        /* Undo animation damage if needed */
@@ -6005,7 +6103,7 @@ void PromotionCallback(w, client_data, call_data)
        promoChar = ToLower(name[0]);
     }
 
-    UserMoveEvent(fromX, fromY, toX, toY, promoChar);
+    FinishMove(NormalMove, fromX, fromY, toX, toY, promoChar);
 
     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
@@ -7542,7 +7640,7 @@ void AboutProc(w, event, prms, nprms)
            "Copyright 1991 Digital Equipment Corporation",
            "Enhancements Copyright 1992-2009 Free Software Foundation",
            "Enhancements Copyright 2005 Alessandro Scotti",
-           PRODUCT, " is free software and carries NO WARRANTY;",
+           PACKAGE, " is free software and carries NO WARRANTY;",
            "see the file COPYING for more information.");
     ErrorPopUp(_("About XBoard"), buf, FALSE);
 }
@@ -7590,19 +7688,34 @@ void Iconify(w, event, prms, nprms)
 void DisplayMessage(message, extMessage)
      char *message, *extMessage;
 {
-    char buf[MSG_SIZ];
-    Arg arg;
-
-    if (extMessage) {
-       if (*message) {
-           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
-           message = buf;
-       } else {
-           message = extMessage;
-       }
-    }
-    XtSetArg(arg, XtNlabel, message);
-    XtSetValues(messageWidget, &arg, 1);
+  /* display a message in the message widget */
+  
+  char buf[MSG_SIZ];
+  Arg arg;
+  
+  if (extMessage) 
+    {
+      if (*message) 
+       {
+         snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
+         message = buf;
+       } 
+      else 
+       {
+         message = extMessage;
+       };
+    };
+  
+  /* need to test if messageWidget already exists, since this function
+     can also be called during the startup, if for example a Xresource
+     is not set up correctly */
+  if(messageWidget)
+    {
+      XtSetArg(arg, XtNlabel, message);
+      XtSetValues(messageWidget, &arg, 1);
+    };
+  
+  return;
 }
 
 void DisplayTitle(text)
@@ -8093,6 +8206,9 @@ void
 ScheduleDelayedEvent(cb, millisec)
      DelayedEventCallback cb; long millisec;
 {
+    if(delayedEventTimerXID && delayedEventCallback == cb)
+       // [HGM] alive: replace, rather than add or flush identical event
+       XtRemoveTimeOut(delayedEventTimerXID);
     delayedEventCallback = cb;
     delayedEventTimerXID =
       XtAppAddTimeOut(appContext, millisec,