home *** CD-ROM | disk | FTP | other *** search
/ Amiga MA Magazine 1998 #3 / amigamamagazinepolishissue1998.iso / szachy / gnu / amyboard-3.2.pl2 / backend.c < prev    next >
C/C++ Source or Header  |  1995-03-08  |  168KB  |  6,253 lines

  1. /*
  2.  * backend.c -- Common back end for X and Windows NT versions of
  3.  * XBoard $Id: backend.c,v 1.50 1995/03/09 00:43:28 mann Exp $
  4.  *
  5.  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
  6.  * Enhancements Copyright 1992-95 Free Software Foundation, Inc.
  7.  *
  8.  * The following terms apply to Digital Equipment Corporation's copyright
  9.  * interest in XBoard:
  10.  * ------------------------------------------------------------------------
  11.  * All Rights Reserved
  12.  *
  13.  * Permission to use, copy, modify, and distribute this software and its
  14.  * documentation for any purpose and without fee is hereby granted,
  15.  * provided that the above copyright notice appear in all copies and that
  16.  * both that copyright notice and this permission notice appear in
  17.  * supporting documentation, and that the name of Digital not be
  18.  * used in advertising or publicity pertaining to distribution of the
  19.  * software without specific, written prior permission.
  20.  *
  21.  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  22.  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
  23.  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
  24.  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  25.  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  26.  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  27.  * SOFTWARE.
  28.  * ------------------------------------------------------------------------
  29.  *
  30.  * The following terms apply to the enhanced version of XBoard distributed
  31.  * by the Free Software Foundation:
  32.  * ------------------------------------------------------------------------
  33.  * This program is free software; you can redistribute it and/or modify
  34.  * it under the terms of the GNU General Public License as published by
  35.  * the Free Software Foundation; either version 2 of the License, or
  36.  * (at your option) any later version.
  37.  *
  38.  * This program is distributed in the hope that it will be useful,
  39.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  40.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  41.  * GNU General Public License for more details.
  42.  *
  43.  * You should have received a copy of the GNU General Public License
  44.  * along with this program; if not, write to the Free Software
  45.  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  46.  * ------------------------------------------------------------------------
  47.  *
  48.  * See the file ChangeLog for a revision history.  */
  49.  
  50. #include <stdio.h>
  51. #include <ctype.h>
  52. #include <errno.h>
  53. #include <sys/types.h>
  54. #include <sys/stat.h>
  55. #include <math.h>
  56.  
  57. #if STDC_HEADERS
  58. # include <stdlib.h>
  59. # include <string.h>
  60. #else /* not STDC_HEADERS */
  61. # if HAVE_STRING_H
  62. #  include <string.h>
  63. # else /* not HAVE_STRING_H */
  64. #  include <strings.h>
  65. # endif /* not HAVE_STRING_H */
  66. #endif /* not STDC_HEADERS */
  67.  
  68. #if HAVE_SYS_FCNTL_H
  69. # include <sys/fcntl.h>
  70. #else /* not HAVE_SYS_FCNTL_H */
  71. # if HAVE_FCNTL_H
  72. #  include <fcntl.h>
  73. # endif /* HAVE_FCNTL_H */
  74. #endif /* not HAVE_SYS_FCNTL_H */
  75.  
  76. #if TIME_WITH_SYS_TIME
  77. # include <sys/time.h>
  78. # include <time.h>
  79. #else
  80. # if HAVE_SYS_TIME_H
  81. #  include <sys/time.h>
  82. # else
  83. #  include <time.h>
  84. # endif
  85. #endif
  86.  
  87. #if defined(_amigados) && !defined(__GNUC__)
  88. struct timezone {
  89.     int tz_minuteswest;
  90.     int tz_dsttime;
  91. };
  92. extern int gettimeofday(struct timeval *, struct timezone *);
  93. #endif
  94.  
  95. #if HAVE_UNISTD_H
  96. # include <unistd.h>
  97. #endif
  98.  
  99. #include "common.h"
  100. #include "frontend.h"
  101. #include "backend.h"
  102. #include "parser.h"
  103. #include "moves.h"
  104. #ifdef ZIPPY
  105. # include "zippy.h"
  106. #endif
  107.  
  108.  
  109. /* A point in time */
  110. typedef struct {
  111.     long sec;  /* Assuming this is >= 32 bits */
  112.     int ms;    /* Assuming this is >= 16 bits */
  113. } TimeMark;
  114.  
  115.  
  116. int establish P((void));
  117. void read_from_player P((InputSourceRef isr, char *buf, int count, int error));
  118. void read_from_ics P((InputSourceRef isr, char *buf, int count, int error));
  119. void SendToICS P((char *s));
  120. void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
  121.               int toX, int toY));
  122. Boolean ParseMachineMove P((char *machineMove, int moveNum,
  123.                 ChessMove *moveType, int *fromX, int *fromY,
  124.                 int *toX, int *toY, char *promoChar));
  125. void InitPosition P((int redraw));
  126. void SendCurrentBoard P((ProcRef pr));
  127. void SendBoard P((ProcRef pr, Board board));
  128. void FinishUserMove P((ChessMove moveType, int toX, int toY));
  129. void HandleMachineMove P((char *message, InputSourceRef isr));
  130. void LoadGameLoop P((void));
  131. int LoadGameOneMove P((void));
  132. int LoadGameFromFile P((char *filename, int n, char *title));
  133. int LoadPositionFromFile P((char *filename, int n, char *title));
  134. int SaveGameToFile P((char *filename));
  135. int SavePositionToFile P((char *filename));
  136. void ApplyMove P((ChessMove *moveType, int fromX, int fromY,
  137.           int toX, int toY, Board board));
  138. void MakeMove P((ChessMove *moveType, int fromX, int fromY,
  139.          int toX, int toY));
  140. void BackwardInner P((int target));
  141. void ForwardInner P((int target));
  142. void GameEnds P((ChessMove result, char *resultDetails, int whosays));
  143. void Reset P((int redraw));
  144. void EditPositionDone P((void));
  145. void PrintOpponents P((FILE *fp));
  146. void PrintPosition P((FILE *fp, int move));
  147. void InitChessProgram P((char *hostName, char *programName,
  148.              ProcRef *pr, InputSourceRef *isr, int *sendTime));
  149. void SendToProgram P((char *message, ProcRef pr));
  150. void ReceiveFromProgram P((InputSourceRef isr, char *buf, int count, int error));
  151. void SendSearchDepth P((ProcRef pr));
  152. void SendTimeRemaining P((ProcRef pr));
  153. void SendMoveToProgram P((ChessMove moveType, int fromX, int fromY, int toX,
  154.               int toY, ProcRef firstProgramPR, int sendTime));
  155. void Attention P((ProcRef pr));
  156. void ResurrectChessProgram P((void));
  157. void DisplayComment P((int moveNumber, char *text));
  158. void DisplayMove P((int moveNumber));
  159.  
  160. void ParseGameHistory P((char *game));
  161. void ParseBoard12 P((char *string));
  162. void StartClocks P((void));
  163. void DisplayBothClocks P((void));
  164. void SwitchClocks P((void));
  165. void StopClocks P((void));
  166. void ResetClocks P((void));
  167. char *PGNDate P((void));
  168. char *PGNResult P((ChessMove result));
  169. char *PGNTags P((void));
  170. void PrintPGNTags P((FILE *f));
  171. void ParsePGNTag P((char *tag));
  172. void ClearGameInfo P((void));
  173. void SetGameInfo P((void));
  174. char *PositionToFEN P((int move));
  175. Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
  176. int RegisterMove P((void));
  177. void MakeRegisteredMove P((void));
  178. char *AppendCmailMsg P((char *tags));
  179. void TruncateGame P((void));
  180. int looking_at P((char *, int *, char *));
  181. void CopyPlayerNameIntoFileName P((char **, char *));
  182. char *SavePart P((char *));
  183. int SaveGameOldStyle P((FILE *));
  184. int SaveGamePGN P((FILE *));
  185. char *PGNTagsStatic P((void));
  186. char *StrSave P((char *));
  187. void GetTimeMark P((TimeMark *));
  188. long SubtractTimeMarks P((TimeMark *, TimeMark *));
  189. void CheckFlags(void);
  190. long NextTickLength P((long));
  191. void CheckTimeControl P((void));
  192.  
  193.  
  194. /* States for ics_getting_history */
  195. #define H_FALSE 0
  196. #define H_REQUESTED 1
  197. #define H_GOT_REQ_HEADER 2
  198. #define H_GOT_UNREQ_HEADER 3
  199. #define H_GETTING_MOVES 4
  200.  
  201. /* whosays values for GameEnds */
  202. #define GE_ICS 0
  203. #define GE_GNU 1
  204. #define GE_PLAYER 2
  205. #define GE_FILE 3
  206.  
  207. /* Maximum number of games in a cmail message */
  208. #define CMAIL_MAX_GAMES 20
  209.  
  210. /* Different types of move when calling RegisterMove */
  211. #define CMAIL_MOVE   0
  212. #define CMAIL_RESIGN 1
  213. #define CMAIL_DRAW   2
  214. #define CMAIL_ACCEPT 3
  215.  
  216. /* Different types of result to remember for each game */
  217. #define CMAIL_NOT_RESULT 0
  218. #define CMAIL_OLD_RESULT 1
  219. #define CMAIL_NEW_RESULT 2
  220.  
  221. /* Fake up flags for now, as we aren't keeping track of castling
  222.    availability yet */
  223. #define FakeFlags(index) \
  224.     (((((index) % 2) == 0) ? F_WHITE_ON_MOVE : 0) | F_ALL_CASTLE_OK)
  225.  
  226. FILE *gameFileFP, *fromUserFP, *toUserFP, *debugFP;
  227.  
  228. char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
  229. char bookOutput[MSG_SIZ], thinkOutput[MSG_SIZ];
  230.  
  231. int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0,
  232.   pauseExamForwardMostMove = 0,
  233.   nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0,
  234.   cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES],
  235.   cmailMsgLoaded = FALSE, cmailMailedMove = FALSE,
  236.   cmailOldMove = -1, firstMove = TRUE, flipView = FALSE,
  237.   blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE,
  238.   searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE,
  239.   whiteFlag = FALSE, blackFlag = FALSE, maybePondering = FALSE, 
  240.   ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE,
  241.   matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE,
  242.   cmailMoveType[CMAIL_MAX_GAMES];
  243. long ics_basetime, ics_increment = 0;
  244. ProcRef firstProgramPR = NoProc, secondProgramPR = NoProc, icsPR = NoProc,
  245.   lastMsgPR = NoProc, cmailPR = NoProc;
  246. InputSourceRef firstProgramISR = NULL, secondProgramISR = NULL,
  247.   telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
  248. int firstSendTime = 2, secondSendTime = 2;  /* 0=don't, 1=do, 2=test */
  249. char timeTestStr[MSG_SIZ];
  250. GameMode gameMode = BeginningOfGame, lastGameMode = BeginningOfGame;
  251. char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2],
  252.   ptyname[24];
  253. char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
  254.  
  255. long whiteTimeRemaining, blackTimeRemaining, timeControl;
  256. long timeRemaining[2][MAX_MOVES];
  257.      
  258. GameInfo gameInfo;
  259.  
  260. AppData appData;
  261.  
  262. Board boards[MAX_MOVES], initialPosition = {
  263.     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
  264.     WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
  265.     { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
  266.     WhitePawn, WhitePawn, WhitePawn, WhitePawn },
  267.     { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
  268.     EmptySquare, EmptySquare, EmptySquare, EmptySquare },
  269.     { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
  270.     EmptySquare, EmptySquare, EmptySquare, EmptySquare },
  271.     { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
  272.     EmptySquare, EmptySquare, EmptySquare, EmptySquare },
  273.     { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
  274.     EmptySquare, EmptySquare, EmptySquare, EmptySquare },
  275.     { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
  276.     BlackPawn, BlackPawn, BlackPawn, BlackPawn },
  277.     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
  278.     BlackKing, BlackBishop, BlackKnight, BlackRook }
  279. };
  280.  
  281.  
  282. void InitBackEnd1()
  283. {
  284.     int matched, min, sec;
  285.  
  286.     /*
  287.      * Internet chess server status
  288.      */
  289.     if (appData.icsActive) {
  290.     appData.matchMode = FALSE;
  291. #ifdef ZIPPY    
  292.     appData.noChessProgram = !appData.zippyPlay;
  293. #else
  294.     appData.zippyPlay = FALSE;
  295.     appData.zippyTalk = FALSE;
  296.     appData.noChessProgram = TRUE;
  297. #endif
  298.     } else {
  299.     appData.zippyTalk = appData.zippyPlay = FALSE;
  300.     }
  301.  
  302.     /*
  303.      * Parse timeControl resource
  304.      */
  305.     if (!ParseTimeControl(appData.timeControl)) {
  306.     /* Can't use DisplayFatalError yet; need more initialization */
  307.     fprintf(stderr, "Bad timeControl option %s", appData.timeControl);
  308.     exit(2);
  309.     }
  310.     if (appData.icsActive) timeControl = 0;
  311.     
  312.     /*
  313.      * Parse searchTime resource
  314.      */
  315.     if (*appData.searchTime != NULLCHAR) {
  316.     matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
  317.     if (matched == 1) {
  318.         searchTime = min * 60;
  319.     } else if (matched == 2) {
  320.         searchTime = min * 60 + sec;
  321.     } else {
  322.         /* Can't use DisplayFatalError yet; need more initialization */
  323.         fprintf(stderr, "Bad searchTime option %s", appData.searchTime);
  324.         exit(2);
  325.     }
  326.     }
  327.     
  328.     if (appData.icsActive) {
  329.     appData.clockMode = TRUE;
  330.     } else if ((*appData.searchTime != NULLCHAR) ||
  331.            (appData.searchDepth > 0) ||
  332.            appData.noChessProgram) {
  333.     appData.clockMode = FALSE;
  334.     firstSendTime = secondSendTime = 0;
  335.     }
  336.  
  337. #ifdef ZIPPY
  338.     ZippyInit();
  339. #endif
  340. }
  341.  
  342. int ParseTimeControl(tc)
  343.      char *tc;
  344. {
  345.     int matched, min, sec;
  346.  
  347.     matched = sscanf(tc, "%d:%d", &min, &sec);
  348.     if (matched == 1) {
  349.     timeControl = min * 60 * 1000;
  350.     } else if (matched == 2) {
  351.     timeControl = (min * 60 + sec) * 1000;
  352.     } else {
  353.     return FALSE;
  354.     }
  355.     return TRUE;
  356. }
  357.  
  358. void InitBackEnd2()
  359. {
  360.     char buf[MSG_SIZ];
  361.     int err;
  362.  
  363.     if (appData.icsActive) {
  364.     err = establish();
  365.     if (err != 0) {
  366.         if (*appData.icsCommPort != NULLCHAR) {
  367.         sprintf(buf, "Could not open comm port %s",  
  368.             appData.icsCommPort);
  369.         } else {
  370.         sprintf(buf, "Could not connect to host %s, port %d",  
  371.             appData.icsHost, appData.icsPort);
  372.         }
  373.         DisplayFatalError(buf, err, 1);
  374.         return;
  375.     }
  376.     SetICSMode();
  377.     telnetISR = AddInputSource(icsPR, FALSE, read_from_ics);
  378.     fromUserISR = AddInputSource(NoProc, FALSE, read_from_player);
  379.     } else if (appData.noChessProgram) {
  380.     SetNCPMode();
  381.     } else {
  382.     SetGNUMode();
  383.     }
  384.  
  385.     if (*appData.cmailGameName != NULLCHAR) {
  386.     SetCmailMode();
  387.     OpenLoopback(&cmailPR);
  388.     cmailISR = AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack);
  389.     }
  390.     
  391.     if (appData.matchMode) {
  392.     /* Set up machine vs. machine match */
  393.     if (appData.noChessProgram) {
  394.         DisplayFatalError("Can't have a match with no chess programs",
  395.                   0, 2);
  396.         return;
  397.     }
  398.     Reset(TRUE);
  399.     matchMode = TRUE;
  400.     if (*appData.loadGameFile != NULLCHAR) {
  401.         if (!LoadGameFromFile(appData.loadGameFile,
  402.                   appData.loadGameIndex,
  403.                   appData.loadGameFile)) {
  404.         DisplayFatalError("Bad game file", 0, 1);
  405.         return;
  406.         }
  407.     } else if (*appData.loadPositionFile != NULLCHAR) {
  408.         if (!LoadPositionFromFile(appData.loadPositionFile,
  409.                       appData.loadPositionIndex,
  410.                       appData.loadPositionFile)) {
  411.         DisplayFatalError("Bad position file", 0, 1);
  412.         return;
  413.         }
  414.     }
  415.     TwoMachinesEvent();
  416.     } else if (*appData.cmailGameName != NULLCHAR) {
  417.     /* Set up cmail mode */
  418.     ReloadCmailGameEvent(TRUE);
  419.     } else {
  420.     /* Set up other modes */
  421.     Reset(TRUE);
  422.     if (*appData.loadGameFile != NULLCHAR) {
  423.         (void) LoadGameFromFile(appData.loadGameFile,
  424.                     appData.loadGameIndex,
  425.                     appData.loadGameFile);
  426.     } else if (*appData.loadPositionFile != NULLCHAR) {
  427.         (void) LoadPositionFromFile(appData.loadPositionFile,
  428.                     appData.loadPositionIndex,
  429.                     appData.loadPositionFile);
  430.     }
  431.     }
  432. }
  433.  
  434. /*
  435.  * Establish will establish a contact to a remote host.port.
  436.  * Sets icsPR to a ProcRef for a process (or pseudo-process)
  437.  *  used to talk to the host.
  438.  * Returns 0 if okay, error code if not.
  439.  */
  440. int establish()
  441. {
  442.     char buf[MSG_SIZ];
  443.  
  444.     if (*appData.icsCommPort != NULLCHAR) {
  445.     /* Talk to the host through a serial comm port */
  446.     return OpenCommPort(appData.icsCommPort, &icsPR);
  447.  
  448.     } else if (*appData.gateway != NULLCHAR) {
  449.     if (*appData.remoteShell == NULLCHAR) {
  450.         /* Use the rcmd protocol to run telnet program on a gateway host */
  451.         sprintf(buf, "%s %s %d",
  452.             appData.telnetProgram, appData.icsHost, appData.icsPort);
  453.         return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
  454.  
  455.     } else {
  456.         /* Use the rsh program to run telnet program on a gateway host */
  457.         if (*appData.remoteUser == NULLCHAR) {
  458.         sprintf(buf, "%s %s %s %s %d", appData.remoteShell,
  459.             appData.gateway, appData.telnetProgram,
  460.             appData.icsHost, appData.icsPort);
  461.         } else {
  462.         sprintf(buf, "%s -l %s %s %s %s %d",
  463.             appData.remoteShell, appData.remoteUser,
  464.             appData.gateway, appData.telnetProgram,
  465.             appData.icsHost, appData.icsPort);
  466.         }
  467.         return StartChildProcess(buf, &icsPR);
  468.  
  469.     }
  470.     } else if (appData.useTelnet) {
  471.     return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
  472.  
  473.     } else {
  474.     /* TCP socket interface differs somewhat between
  475.        Unix and NT; handle details in the front end.
  476.     */
  477.     return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
  478.     }
  479. }
  480.  
  481. void show_bytes(fp, buf, count)
  482.      FILE *fp;
  483.      char *buf;
  484.      int count;
  485. {
  486.     while (count--) {
  487.     if (*buf < 040 || *buf > 0177) {
  488.         fprintf(fp, "\\%03o", *buf & 0xff);
  489.     } else {
  490.         putc(*buf, fp);
  491.     }
  492.     buf++;
  493.     }
  494.     fflush(fp);
  495. }
  496.  
  497. void read_from_player(isr, message, count, error)
  498.      InputSourceRef isr;
  499.      char *message;
  500.      int count;
  501.      int error;
  502. {
  503.     int outError, outCount;
  504.     static int eofCount = 0;
  505.  
  506.     if (count > 0) {
  507.     if (appData.debugMode) {
  508.         fprintf(debugFP, "Sending to ICS: ");
  509.         show_bytes(debugFP, message, count);
  510.     }
  511.     outCount = OutputToProcess(icsPR, message, count, &outError);
  512.     eofCount = 0;
  513.     if (outCount < count) {
  514.         RemoveInputSource(isr);
  515.         DisplayFatalError("Error writing to ICS", outError, 1);
  516.     }
  517.     } else if (count < 0) {
  518.     RemoveInputSource(isr);
  519.     DisplayFatalError("Error reading from keyboard", error, 1);
  520.     } else if (eofCount++ > 0) {
  521.     RemoveInputSource(isr);
  522.     DisplayFatalError("Got end of file from keyboard", 0, 0);
  523.     }
  524. }
  525.  
  526. void SendToICS(s)
  527.      char *s;
  528. {
  529.     int count, outCount, outError;
  530.  
  531.     if (icsPR == NULL) return;
  532.  
  533.     count = strlen(s);
  534.     if (appData.debugMode) {
  535.     fprintf(debugFP, "Sending to ICS: ");
  536.     show_bytes(debugFP, s, count);
  537.     }
  538.     outCount = OutputToProcess(icsPR, s, count, &outError);
  539.     if (outCount < count) {
  540.     DisplayFatalError("Error writing to ICS", outError, 1);
  541.     }
  542. }
  543.  
  544.  
  545. static int leftover_start = 0, leftover_len = 0;
  546. char star_match[STAR_MATCH_N][MSG_SIZ];
  547.  
  548. /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
  549.    advance *index beyond it, and set leftover_start to the new value of
  550.    *index; else return FALSE.  If pattern contains the character '*', it
  551.    matches any sequence of characters not containing '\r', '\n', or the
  552.    character following the '*' (if any), and the matched sequence(s) are
  553.    copied into star_match.
  554. */
  555. int looking_at(buf, index, pattern)
  556.      char *buf;
  557.      int *index;
  558.      char *pattern;
  559. {
  560.     char *bufp = &buf[*index], *patternp = pattern;
  561.     int star_count = 0;
  562.     char *matchp = star_match[0];
  563.     
  564.     for (;;) {
  565.     if (*patternp == NULLCHAR) {
  566.         *index = leftover_start = bufp - buf;
  567.         *matchp = NULLCHAR;
  568.         return TRUE;
  569.     }
  570.     if (*bufp == NULLCHAR) return FALSE;
  571.     if (*patternp == '*') {
  572.         if (*bufp == *(patternp + 1)) {
  573.         *matchp = NULLCHAR;
  574.         matchp = star_match[++star_count];
  575.         patternp += 2;
  576.         bufp++;
  577.         continue;
  578.         } else if (*bufp == '\n' || *bufp == '\r') {
  579.         patternp++;
  580.         if (*patternp == NULLCHAR)
  581.           continue;
  582.         else
  583.           return FALSE;
  584.         } else {
  585.         *matchp++ = *bufp++;
  586.         continue;
  587.         }
  588.     }
  589.     if (*patternp != *bufp) return FALSE;
  590.     patternp++;
  591.     bufp++;
  592.     }
  593. }
  594.  
  595. /*-- Game start info cache: --*/
  596. int gs_gamenum;
  597. char gs_kind[MSG_SIZ];
  598. /*----------------------------*/
  599.  
  600. void read_from_ics(isr, data, count, error)
  601.      InputSourceRef isr;
  602.      char *data;
  603.      int count;
  604.      int error;
  605. {
  606. #define BUF_SIZE 8192
  607. #define STARTED_NONE 0
  608. #define STARTED_MOVES 1
  609. #define STARTED_BOARD 2
  610. #define STARTED_OBSERVE 3
  611.     
  612.     static int started = STARTED_NONE;
  613.     static char parse[20000];
  614.     static int parse_pos;
  615.     static char buf[BUF_SIZE + 1];
  616.     static int firstTime = TRUE;
  617.     
  618.     char str[500];
  619.     int i, oldi;
  620.     int buf_len;
  621.     int next_out;
  622.     
  623.     if (count > 0) {
  624.     /* If last read ended with a partial line that we couldn't parse,
  625.        prepend it to the new read and try again. */
  626.     if (leftover_len > 0) {
  627.         for (i=0; i<leftover_len; i++)
  628.           buf[i] = buf[leftover_start + i];
  629.     }
  630.  
  631.     /* Copy in new characters, removing nulls and \r's */
  632.     buf_len = leftover_len;
  633.     for (i = 0; i < count; i++) {
  634.         if (data[i] != NULLCHAR && data[i] != '\r')
  635.           buf[buf_len++] = data[i];
  636.     }
  637.  
  638.     buf[buf_len] = NULLCHAR;
  639.     next_out = leftover_len;
  640.     leftover_start = 0;
  641.     
  642.     i = 0;
  643.     while (i < buf_len) {
  644.         
  645. #define TN_WILL '\373'
  646. #define TN_WONT '\374'
  647. #define TN_DO   '\375'
  648. #define TN_DONT '\376'
  649. #define TN_IAC  '\377'
  650. #define TN_ECHO '\001'
  651. #define TN_SGA  '\003'
  652. #define TN_PORT 23
  653.  
  654.         /* Deal with part of the TELNET option negotiation
  655.            protocol.  We refuse to do anything beyond the
  656.            defaults, except that we allow the WILL ECHO option,
  657.            which ICS uses to turn off password echoing when we are
  658.            directly connected to it.  We must reject this option
  659.            when we are talking to a real telnet server (port 23),
  660.            because most telnet servers want to go into
  661.            character-by-character mode and handle all the
  662.            echoing. */
  663.         if (buf_len - i >= 3 && buf[i] == TN_IAC) {
  664.         static int remoteEchoOption = FALSE;  /* telnet ECHO option */
  665.         char reply[4];
  666.         oldi = i;
  667.         reply[0] = TN_IAC;
  668.         reply[3] = NULLCHAR;
  669.         switch (buf[++i]) {
  670.           case TN_WILL:
  671.             if (appData.debugMode)
  672.               fprintf(debugFP, "\n<WILL ");
  673.             switch (reply[2] = buf[++i]) {
  674.               case TN_ECHO:
  675.             if (appData.debugMode)
  676.               fprintf(debugFP, "ECHO ");
  677.             /* Reply only if this is a change, according
  678.                to the protocol rules. */
  679.             if (remoteEchoOption) break;
  680.             if (appData.icsPort == TN_PORT) {
  681.                 reply[1] = TN_DONT;
  682.                 if (appData.debugMode)
  683.                   fprintf(debugFP, ">DONT ECHO ");
  684.             } else {
  685.                 EchoOff();
  686.                 reply[1] = TN_DO;
  687.                 if (appData.debugMode)
  688.                   fprintf(debugFP, ">DO ECHO ");
  689.                 remoteEchoOption = TRUE;
  690.             }
  691.             SendToICS(reply);
  692.             break;
  693.               default:
  694.             if (appData.debugMode)
  695.               fprintf(debugFP, "%d ", reply[2]);
  696.             /* Whatever this is, we don't want it. */
  697.             reply[1] = TN_DONT;
  698.             if (appData.debugMode)
  699.               fprintf(debugFP, ">DONT %d ", reply[2]);
  700.             SendToICS(reply);
  701.             break;
  702.             }
  703.             break;
  704.           case TN_WONT:
  705.             if (appData.debugMode)
  706.               fprintf(debugFP, "\n<WONT ");
  707.             switch (reply[2] = buf[++i]) {
  708.               case TN_ECHO:
  709.             if (appData.debugMode)
  710.               fprintf(debugFP, "ECHO ");
  711.             /* Reply only if this is a change, according
  712.                to the protocol rules. */
  713.             if (!remoteEchoOption) break;
  714.             EchoOn();
  715.             reply[1] = TN_DONT;
  716.             if (appData.debugMode)
  717.               fprintf(debugFP, ">DONT ECHO ");
  718.             remoteEchoOption = FALSE;
  719.             SendToICS(reply);
  720.             break;
  721.               default:
  722.             if (appData.debugMode)
  723.               fprintf(debugFP, "%d ", buf[i]);
  724.             /* Whatever this is, it must already be turned
  725.                off, because we never agree to turn on
  726.                anything non-default, so according to the
  727.                protocol rules, we don't reply. */
  728.             break;
  729.             }
  730.             break;
  731.           case TN_DO:
  732.             if (appData.debugMode)
  733.               fprintf(debugFP, "\n<DO ");
  734.             switch (reply[2] = buf[++i]) {
  735.               default:
  736.             /* Whatever this is, we refuse to do it. */
  737.             reply[1] = TN_WONT;
  738.             if (appData.debugMode)
  739.               fprintf(debugFP, "%d >WONT %d ", reply[2], reply[2]);
  740.             SendToICS(reply);
  741.             break;
  742.             }
  743.             break;
  744.           case TN_DONT:
  745.             if (appData.debugMode)
  746.               fprintf(debugFP, "\n<DONT ");
  747.             switch (reply[2] = buf[++i]) {
  748.               default:
  749.             if (appData.debugMode)
  750.               fprintf(debugFP, "%d ", reply[2]);
  751.             /* Whatever this is, we are already not doing
  752.                it, because we never agree to do anything
  753.                non-default, so according to the protocol
  754.                rules, we don't reply. */
  755.             break;
  756.             }
  757.             break;
  758.           case TN_IAC:
  759.             if (appData.debugMode)
  760.               fprintf(debugFP, "\n<IAC ");
  761.             /* Doubled IAC; pass it through */
  762.             i--;
  763.             break;
  764.           default:
  765.             if (appData.debugMode)
  766.               fprintf(debugFP, "\n<%d ", buf[i]);
  767.             /* Drop all other telnet commands on the floor */
  768.             break;
  769.         }
  770.         if (oldi > next_out)
  771.           fwrite(&buf[next_out], oldi - next_out, 1, toUserFP);
  772.         if (++i > next_out)
  773.           next_out = i;
  774.         continue;
  775.         }
  776.         
  777.             /* Kludge to deal with rcmd protocol */
  778.         if (firstTime && looking_at(buf, &i, "\001*")) {
  779.         DisplayFatalError(&buf[1], 0, 1);
  780.         continue;
  781.         } else {
  782.             firstTime = FALSE;
  783.         }
  784.  
  785.         if (appData.zippyTalk || appData.zippyPlay) {
  786. #ifdef ZIPPY
  787.         if (ZippyControl(buf, &i)) continue;
  788.         if (appData.zippyTalk && ZippyConverse(buf, &i)) continue;
  789.         if (appData.zippyPlay && ZippyMatch(buf, &i)) continue;
  790. #endif
  791.         } else if (looking_at(buf, &i, "shouts: *") ||
  792.              looking_at(buf, &i, "tells you: *") ||
  793.              looking_at(buf, &i, "says: *") ||
  794.              looking_at(buf, &i, "whispers: *") ||
  795.              looking_at(buf, &i, "kibitzes: *") ||
  796.              looking_at(buf, &i, "queries: *") ||
  797.              looking_at(buf, &i, "--> *") ||
  798.              looking_at(buf, &i, "\\   *")) {
  799.         /* Skip over what people say */
  800.         continue;
  801.         }
  802.  
  803.         if (looking_at(buf, &i, "Black Strength :") ||
  804.         looking_at(buf, &i, "<<< style 10 board >>>") ||
  805.         looking_at(buf, &i, "<10>") ||
  806.         looking_at(buf, &i, "#@#")) {
  807.         /* Wrong board style */
  808.         SendToICS("set style 12\n");
  809.                 SendToICS("refresh\n");
  810.         continue;
  811.         }
  812.         
  813.         oldi = i;
  814.         if (looking_at(buf, &i, "\n<12> ") ||
  815.         looking_at(buf, &i, "<12> ")) {
  816.         if (oldi > next_out) {
  817.             fwrite(&buf[next_out], oldi - next_out, 1, toUserFP);
  818.             fflush(toUserFP);
  819.         }
  820.         next_out = i;
  821.         started = STARTED_BOARD;
  822.         parse_pos = 0;
  823.         continue;
  824.         }
  825.  
  826.         if (looking_at(buf, &i, "* *vs. * *--- *")) {
  827.         /* Header for a move list -- first line */
  828.         /* We might want to save some of these fields but
  829.            for now we don't */
  830.         switch (ics_getting_history) {
  831.           case H_FALSE:
  832.             switch (gameMode) {
  833.               case IcsIdle:
  834.               case BeginningOfGame:
  835.             /* User typed "moves" or "oldmoves" while we
  836.                were idle.  Pretend we asked for these
  837.                moves and soak them up so user can step
  838.                through them and/or save them.
  839.             */
  840.             Reset(FALSE);
  841.             gameMode = IcsObserving;
  842.             ModeHighlight();
  843.             ics_gamenum = -1;
  844.             ics_getting_history = H_GOT_REQ_HEADER;
  845.             break;
  846.               case EditGame: /*?*/
  847.               case EditPosition: /*?*/
  848.             /* Should above feature work in these modes too? */
  849.             /* For now it doesn't */
  850.             ics_getting_history = H_GOT_UNREQ_HEADER;
  851.             break;
  852.               default:
  853.             ics_getting_history = H_GOT_UNREQ_HEADER;
  854.             break;
  855.             }
  856.             break;
  857.           case H_REQUESTED:
  858.             /* All is well */
  859.             ics_getting_history = H_GOT_REQ_HEADER;
  860.             break;
  861.           case H_GOT_REQ_HEADER:
  862.           case H_GOT_UNREQ_HEADER:
  863.           case H_GETTING_MOVES:
  864.             /* Should not happen */
  865.             DisplayError("Error gathering move list: two headers", 0);
  866.             ics_getting_history = H_FALSE;
  867.             break;
  868.         }
  869.         continue;
  870.         }
  871.  
  872.         if (looking_at(buf, &i,
  873.               "* * match, initial time: * minutes, increment: * seconds.")) {
  874.         /* Header for a move list -- second line */
  875.         /* Initial board will follow if this is a wild game */
  876.         /* Again, we might want to save some of these fields later */
  877.         /* For now we do nothing with them. */
  878.         continue;
  879.         }
  880.  
  881.         oldi = i;
  882.         if (looking_at(buf, &i, "Move  ")) {
  883.         /* Beginning of a move list */
  884.         switch (ics_getting_history) {
  885.           case H_FALSE:
  886.             /* Normally should not happen */
  887.             /* Maybe user hit reset while we were parsing */
  888.             break;
  889.           case H_REQUESTED:
  890.           case H_GETTING_MOVES:
  891.             /* Should not happen */
  892.             DisplayError("Error gathering move list: no header", 0);
  893.             ics_getting_history = H_FALSE;
  894.             break;
  895.           case H_GOT_REQ_HEADER:
  896.             ics_getting_history = H_GETTING_MOVES;
  897.             started = STARTED_MOVES;
  898.             parse_pos = 0;
  899.             if (oldi > next_out) {
  900.             fwrite(&buf[next_out], oldi - next_out, 1, toUserFP);
  901.             fflush(toUserFP);
  902.             }
  903.             break;
  904.           case H_GOT_UNREQ_HEADER:
  905.             ics_getting_history = H_FALSE;
  906.             break;
  907.         }
  908.         continue;
  909.         }                
  910.         
  911.         if (looking_at(buf, &i, "% ") ||
  912.         (started == STARTED_MOVES && looking_at(buf, &i, ": "))) {
  913.         switch (started) {
  914.           case STARTED_MOVES:
  915.             started = STARTED_NONE;
  916.             parse[parse_pos] = NULLCHAR;
  917.             ParseGameHistory(parse);
  918.              if (gameMode == IcsObserving && ics_gamenum == -1) {
  919.             /* Moves came from oldmoves or moves command
  920.                while we weren't doing anything else.
  921.             */
  922.             currentMove = forwardMostMove;
  923.             flipView = appData.flipView;
  924.             DrawPosition(FALSE, boards[currentMove]);
  925.             DisplayBothClocks();
  926.             sprintf(str, "%s vs. %s",
  927.                 gameInfo.white, gameInfo.black);
  928.             DisplayTitle(str);
  929.             gameMode = IcsIdle;
  930.             } else {
  931.             /* Moves were history of an active game */
  932.             if (gameInfo.resultDetails != NULL) {
  933.                 free(gameInfo.resultDetails);
  934.                 gameInfo.resultDetails = NULL;
  935.             }
  936.             }
  937.             DisplayMove(currentMove - 1);
  938. #ifdef NOTDEF
  939.             SendToICS("\n");  /*kludge: force a prompt*/
  940. #endif
  941.             next_out = i;
  942.             ics_getting_history = H_FALSE;
  943.             break;
  944.  
  945.           case STARTED_OBSERVE:
  946.             started = STARTED_NONE;
  947.             SendToICS("refresh\n");
  948.             break;
  949.  
  950.           default:
  951.             break;
  952.         }
  953.         continue;
  954.         }
  955.         
  956.         if ((started == STARTED_MOVES || started == STARTED_BOARD) &&
  957.         i >= leftover_len) {
  958.         /* Accumulate characters in move list or board */
  959.         parse[parse_pos++] = buf[i];
  960.         }
  961.         
  962.         /* Start of game messages.  Mostly we detect start of game
  963.            when the first board image arrives.  On some versions
  964.            of the ICS, though, we need to do a "refresh" after starting
  965.            to observe in order to get the current board right away. */
  966.         if (looking_at(buf, &i, "Adding game * to observation list")) {
  967.         started = STARTED_OBSERVE;
  968.         continue;
  969.         }
  970.  
  971.         /* Handle auto-observe */
  972.         if (appData.autoObserve &&
  973.         (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
  974.         looking_at(buf, &i, "Game notification: * ")) {
  975.         char *player = star_match[0];
  976.         char *pend;
  977.         if (*player == '\033') {
  978.             /* Strip off highlighting escape sequences */
  979.             while (!isalpha(*player)) player++;
  980.             player++;
  981.             pend = strchr(player, '\033');
  982.             if (pend != NULL) *pend = NULLCHAR;
  983.         }
  984.         sprintf(str, "observe %s\n", player);
  985.         SendToICS(str);
  986.         continue;
  987.         }
  988.  
  989.         /* Deal with automatic examine mode after a game,
  990.            and with IcsObserving -> IcsExamining transition */
  991.         if (looking_at(buf, &i, "Entering examine mode for game *.") ||
  992.         looking_at(buf, &i, "has made you an examiner of game *.")) {
  993.  
  994.         int gamenum = atoi(star_match[0]);
  995.         if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
  996.             gamenum == ics_gamenum) {
  997.             /* We were already playing or observing this game;
  998.                no need to refetch history */
  999.             gameMode = IcsExamining;
  1000.             if (pausing) {
  1001.             pauseExamForwardMostMove = forwardMostMove;
  1002.             } else if (currentMove < forwardMostMove) {
  1003.             ForwardInner(forwardMostMove);
  1004.             }
  1005.         } else {
  1006.             /* I don't think this case really can happen */
  1007.             SendToICS("refresh\n");
  1008.         }
  1009.         continue;
  1010.         }    
  1011.         
  1012.         /* Error messages */
  1013.         if (ics_user_moved) {
  1014.         if (looking_at(buf, &i, "Illegal move") ||
  1015.             looking_at(buf, &i, "Not a legal move") ||
  1016.             looking_at(buf, &i, "Your king is in check") ||
  1017.             looking_at(buf, &i, "It isn't your turn")) {
  1018.             /* Illegal move */
  1019.             ics_user_moved = 0;
  1020.             if (forwardMostMove > backwardMostMove) {
  1021.             currentMove = --forwardMostMove;
  1022.             DisplayError("Illegal move\n(rejected by ICS)", 0);
  1023.             DrawPosition(FALSE, boards[currentMove]);
  1024.             SwitchClocks();
  1025.             }
  1026.             continue;
  1027.         }
  1028.         }
  1029.  
  1030.         if (looking_at(buf, &i, "still have time") ||
  1031.         looking_at(buf, &i, "not out of time")) {
  1032.         /* We must have called his flag a little too soon */
  1033.         whiteFlag = blackFlag = FALSE;
  1034.         continue;
  1035.         }
  1036.  
  1037.         if (looking_at(buf, &i, "added * seconds to")) {
  1038.         /* Update the clocks */
  1039.         SendToICS("refresh\n");
  1040.         continue;
  1041.         }
  1042.  
  1043.         /* Improved generic end-of-game messages */
  1044.         if (looking_at(buf, &i, "{Game * (* vs. *) *} *")) {
  1045.         /* star_match[0] is the game number */
  1046.         /*           [1] is the white player's name */
  1047.         /*           [2] is the black player's name */
  1048.         /*           [3] is the reason for the game end */
  1049.         /*           [4] is a PGN end game-token */
  1050.         int gamenum = atoi(star_match[0]);
  1051.         char *why = star_match[3];
  1052.         char *endtoken = star_match[4];
  1053.         ChessMove endtype;
  1054.  
  1055.         if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
  1056.             ics_gamenum != gamenum) {
  1057.             continue;
  1058.         }
  1059.         switch (endtoken[0]) {
  1060.           case '*':
  1061.             endtype = GameUnfinished;
  1062.             break;
  1063.           case '0':
  1064.             endtype = BlackWins;
  1065.             break;
  1066.           case '1':
  1067.             if (endtoken[1] == '/')
  1068.               endtype = GameIsDrawn;
  1069.             else
  1070.               endtype = WhiteWins;
  1071.             break;
  1072.         }
  1073.         GameEnds(endtype, why, GE_ICS);
  1074.         continue;
  1075.         }
  1076.  
  1077.         /* Start/end-of-game messages */
  1078.         if (looking_at(buf, &i, "{Game * (* vs. *)* * *}")) {
  1079.         /* Generic game start/end messages */
  1080.         /* star_match[0] is the game number */
  1081.         /*           [1] is the white player's name */
  1082.         /*           [2] is the black player's name */
  1083.         /*           [3] is either ":" or empty (don't care) */
  1084.         /*           [4] is usually the loser's name or a noise word */
  1085.         /*           [5] contains the reason for the game end */
  1086.         int gamenum = atoi(star_match[0]);
  1087.         char *white = star_match[1];
  1088.         char *loser = star_match[4];
  1089.         char *why = star_match[5];
  1090.         
  1091.         /* Game start messages */
  1092.         if (strcmp(loser, "Creating") == 0 ||
  1093.             strcmp(loser, "Continuing") == 0) {
  1094.             gs_gamenum = gamenum;
  1095.             strcpy(gs_kind, why);  /* why = type of game */
  1096. #ifdef ZIPPY
  1097.             ZippyGameStart(white, star_match[2]);
  1098. #endif /*ZIPPY*/
  1099.             continue;
  1100.         }
  1101.  
  1102.         /* Game end messages */
  1103.         if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
  1104.             ics_gamenum != gamenum) {
  1105.             continue;
  1106.         }
  1107.  
  1108.         if (StrStr(why, "checkmate")) {
  1109.             if (strcmp(loser, white) == 0)
  1110.               GameEnds(BlackWins, "Black mates", GE_ICS);
  1111.             else
  1112.               GameEnds(WhiteWins, "White mates", GE_ICS);
  1113.         } else if (StrStr(why, "resign")) {
  1114.             if (strcmp(loser, white) == 0)
  1115.               GameEnds(BlackWins, "White resigns", GE_ICS);
  1116.             else
  1117.               GameEnds(WhiteWins, "Black resigns", GE_ICS);
  1118.         } else if (StrStr(why, "forfeits on time")) {
  1119.             if (strcmp(loser, white) == 0)
  1120.               GameEnds(BlackWins, "Black wins on time", GE_ICS);
  1121.             else
  1122.               GameEnds(WhiteWins, "White wins on time", GE_ICS);
  1123.         } else if (StrStr(why, "stalemate")) {
  1124.             GameEnds(GameIsDrawn, "Stalemate", GE_ICS);
  1125.         } else if (StrStr(why, "drawn by mutual agreement")) {
  1126.             GameEnds(GameIsDrawn, "Draw agreed", GE_ICS);
  1127.         } else if (StrStr(why, "repetition")) {
  1128.             GameEnds(GameIsDrawn, "Draw by repetition", GE_ICS);
  1129.         } else if (StrStr(why, "50")) {
  1130.             GameEnds(GameIsDrawn, "50 move rule", GE_ICS);
  1131.         } else if (StrStr(why, "neither player has mating")) {
  1132.             GameEnds(GameIsDrawn, "Insufficient material", GE_ICS);
  1133.         } else if (StrStr(why, "no material")) {
  1134.             GameEnds(GameIsDrawn,
  1135.                  "Insufficient material to win on time", GE_ICS);
  1136.         } else if (StrStr(why, "time")) {
  1137.             GameEnds(GameIsDrawn, "Both players ran out of time",
  1138.                  GE_ICS);
  1139.         } else if (StrStr(why, "disconnected and forfeits")) {
  1140.             /* in this case the word "abuser" preceded the loser */
  1141.             loser = why;
  1142.             why = strchr(loser, ' ');
  1143.             *why++ = NULLCHAR;
  1144.             if (strcmp(loser, white) == 0)
  1145.               GameEnds(BlackWins, "Forfeit", GE_ICS);
  1146.             else
  1147.               GameEnds(WhiteWins, "Forfeit", GE_ICS);
  1148.         } else if (StrStr(why, "assert")) {
  1149.             /* "loser" is actually the winner in this case */
  1150.             if (strcmp(loser, white) == 0)
  1151.               GameEnds(WhiteWins, "White asserts a win", GE_ICS);
  1152.             else
  1153.               GameEnds(BlackWins, "Black asserts a win", GE_ICS);
  1154.         } else if (StrStr(why, "aborted")) {
  1155.             GameEnds(GameUnfinished, "Game aborted", GE_ICS);
  1156.         } else if (StrStr(why, "removed")) {
  1157.             GameEnds(GameUnfinished, "Game aborted", GE_ICS);
  1158.         } else if (StrStr(why, "adjourn")) {
  1159.             GameEnds(GameUnfinished, "Game adjourned", GE_ICS);
  1160.         } else {
  1161.             /* Unrecognized game end string */
  1162.             GameEnds(GameUnfinished, why, GE_ICS);
  1163.         }
  1164.         continue;
  1165.         }
  1166.  
  1167.         if (looking_at(buf, &i, "Removing game * from observation") ||
  1168.         looking_at(buf, &i, "Game * (*) has no examiners")) {
  1169.         if (gameMode == IcsObserving &&
  1170.             atoi(star_match[0]) == ics_gamenum)
  1171.           {
  1172.               StopClocks();
  1173.               gameMode = IcsIdle;
  1174.               ics_gamenum = -1;
  1175.               ics_user_moved = FALSE;
  1176.           }
  1177.         continue;
  1178.         }
  1179.  
  1180.         if (looking_at(buf, &i, "You are no longer examining game *.")) {
  1181.         if (gameMode == IcsExamining &&
  1182.             atoi(star_match[0]) == ics_gamenum)
  1183.           {
  1184.               gameMode = IcsIdle;
  1185.               ics_gamenum = -1;
  1186.               ics_user_moved = FALSE;
  1187.           }
  1188.         continue;
  1189.         }
  1190.  
  1191.         /* Advance leftover_start past any newlines we find,
  1192.            so only partial lines can get reparsed */
  1193.         if (looking_at(buf, &i, "\n")) {
  1194.         if (started == STARTED_BOARD) {
  1195.             started = STARTED_NONE;
  1196.             parse[parse_pos] = NULLCHAR;
  1197.             ParseBoard12(parse);
  1198.             ics_user_moved = 0;
  1199.             /* Usually suppress following prompt */
  1200.             if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
  1201.             (void) looking_at(buf, &i, "*% ");
  1202.             }
  1203.             next_out = i;
  1204.         }
  1205.         continue;
  1206.         }
  1207.  
  1208.         i++;    /* skip unparsed character and loop back */
  1209.     }
  1210.     
  1211.     if (started != STARTED_MOVES && started != STARTED_BOARD
  1212.         && i > next_out) {
  1213.         fwrite(&buf[next_out], i - next_out, 1, toUserFP);
  1214.         fflush(toUserFP);
  1215.     }
  1216.     
  1217.     leftover_len = buf_len - leftover_start;
  1218.     /* if buffer ends with something we couldn't parse,
  1219.        reparse it after appending the next read */
  1220.     
  1221.     } else if (count == 0) {
  1222.     extern char *programName;
  1223.     RemoveInputSource(isr);
  1224.     /*** User probably typed "quit", so don't do this:
  1225.       DisplayFatalError("Connection closed by ICS", 0, 0);
  1226.     ***/
  1227.     fprintf(stderr, "%s: Connection closed by ICS\n", programName);
  1228.     ExitEvent(0);
  1229.     } else {
  1230.     RemoveInputSource(isr);
  1231.     DisplayFatalError("Error reading from ICS", error, 1);
  1232.     }
  1233. }
  1234.  
  1235.  
  1236. /* Board style 12 looks like this:
  1237.  
  1238. <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0
  1239.  
  1240.  * The "<12> " is stripped before it gets to this routine.
  1241.  * The trailing 0 (flip state) is a recent addition, and FICS doesn't have it.
  1242.  * Additional trailing fields may be added in the future.
  1243.  */
  1244.  
  1245. #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d"
  1246.  
  1247. #define RELATION_OBSERVING_PLAYED    0
  1248. #define RELATION_OBSERVING_EXAMINED -2
  1249. #define RELATION_PLAYING_MYMOVE      1
  1250. #define RELATION_PLAYING_NOTMYMOVE  -1
  1251. #define RELATION_EXAMINING           2
  1252. #define RELATION_ISOLATED_BOARD     -3
  1253.  
  1254. void ParseBoard12(string)
  1255.      char *string;
  1256.     GameMode newGameMode;
  1257.     int gamenum, newGame, relation, basetime, increment, ics_flip = 0;
  1258.     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
  1259.     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
  1260.     char to_play, board_chars[72];
  1261.     char move_str[500], str[500], elapsed_time[500];
  1262.     char black[32], white[32];
  1263.     Board board;
  1264.     
  1265.     newGame = FALSE;
  1266.  
  1267.     if (appData.debugMode)
  1268.       fprintf(debugFP, "Parsing board: %s\n", string);
  1269.  
  1270.     move_str[0] = NULLCHAR;
  1271.     elapsed_time[0] = NULLCHAR;
  1272.     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
  1273.            &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
  1274.            &gamenum, white, black, &relation, &basetime, &increment,
  1275.            &white_stren, &black_stren, &white_time, &black_time,
  1276.            &moveNum, str, elapsed_time, move_str, &ics_flip);
  1277.  
  1278.     if (n < 22) {
  1279.     sprintf(str, "Failed to parse board string:\n\"%s\"", string);
  1280.     DisplayError(str, 0);
  1281.     return;
  1282.     }
  1283.  
  1284.     if (gamenum == -1) relation = RELATION_ISOLATED_BOARD; 
  1285.  
  1286.     switch (relation) {
  1287.       case RELATION_OBSERVING_PLAYED:
  1288.       case RELATION_OBSERVING_EXAMINED:
  1289.     newGameMode = IcsObserving;
  1290.     break;
  1291.       case RELATION_PLAYING_MYMOVE:
  1292.       case RELATION_PLAYING_NOTMYMOVE:
  1293.     newGameMode =
  1294.       ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
  1295.         IcsPlayingWhite : IcsPlayingBlack;
  1296.     break;
  1297.       case RELATION_EXAMINING:
  1298.     newGameMode = IcsExamining;
  1299.     break;
  1300.       case RELATION_ISOLATED_BOARD:
  1301.       default:
  1302.     /* Just display this board.  If user was doing something else,
  1303.        we will forget about it until the next board comes. */ 
  1304.     newGameMode = IcsIdle;
  1305.     break;
  1306.     }
  1307.     
  1308.     /* Convert the move number to internal form */
  1309.     moveNum = (moveNum - 1) * 2;
  1310.     if (to_play == 'B') moveNum++;
  1311.  
  1312.     /* Deal with initial board display on move listing
  1313.        of wild games.
  1314.     */
  1315.     switch (ics_getting_history) {
  1316.       case H_FALSE:
  1317.       case H_REQUESTED:
  1318.     break;
  1319.       case H_GOT_REQ_HEADER:
  1320.     /* This is an initial board that we want */
  1321.     gamenum = ics_gamenum;
  1322.     moveNum = 0; /* !! ICS bug workaround */
  1323.     break;
  1324.       case H_GOT_UNREQ_HEADER:
  1325.     /* This is an initial board that we don't want */
  1326.     return;
  1327.       case H_GETTING_MOVES:
  1328.     /* Should not happen */
  1329.     DisplayError("Error gathering move list: extra board", 0);
  1330.     ics_getting_history = H_FALSE;
  1331.     return;
  1332.     }
  1333.  
  1334.     /* Take action if this is the first board of a new game */
  1335.     if (gamenum != ics_gamenum || newGameMode != gameMode ||
  1336.     relation == RELATION_ISOLATED_BOARD) {
  1337.     /* Check if trying to do two things at once */
  1338.     /* xboard can't handle two games at once */
  1339.     if (gameMode == IcsObserving && newGameMode != IcsIdle) {
  1340.         /* Stop observing the old game */
  1341.         sprintf(str, "observe %d\n", ics_gamenum);
  1342.         SendToICS(str);
  1343.         sprintf(str, "Aren't you observing game %d?\nAttempting to stop observing it.", ics_gamenum);
  1344.         DisplayError(str, 0);
  1345.         /* continue as in normal case */
  1346.     } else if (gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite
  1347.            || gameMode == IcsExamining) {
  1348.         if (newGameMode == IcsObserving) {
  1349.         if (gamenum != -1) {
  1350.             /* Stop observing the new game */
  1351.             SendToICS("observe\n");
  1352.             sprintf(str, "Aren't you %s a game?\nAttempting to stop observing game %d.",
  1353.                 gameMode == IcsExamining ? "examining" : "playing",
  1354.                 gamenum);
  1355.             DisplayError(str, 0);
  1356.         }
  1357.         /* ignore this board */
  1358.         return;
  1359.         }
  1360.         /* else newGameMode ==
  1361.            Ics(PlayingWhite|PlayingBlack|Examining|Idle) 
  1362.                  Maybe we switched sides in the same game, or are
  1363.              playing in a simul.  FICS can do these things.  We
  1364.              just quietly forget about the old game and give our
  1365.              attention to the new one.  Maybe for simuls we should
  1366.              have a mode where we don't fetch the history when
  1367.              switching games in this way.  (I believe the case where
  1368.          newGameMode == IcsExamining is impossible.)
  1369.         */
  1370.     }
  1371.     /* Normal case, or error recovered */
  1372.     Reset(FALSE);
  1373.     newGame = TRUE;
  1374.     if (gamenum == -1) {
  1375.         newGameMode = IcsIdle;
  1376.     } else if (moveNum > 0 && newGameMode != IcsIdle) {
  1377.         /* Need to get game history */
  1378.         ics_getting_history = H_REQUESTED;
  1379.         sprintf(str, "moves %d\n", gamenum);
  1380.         SendToICS(str);
  1381.     }
  1382.  
  1383.     /* Initially flip the board to have black on the bottom if playing
  1384.        black or if the ICS flip flag is set, but let the user change
  1385.        it with the Flip View button. */
  1386.     flipView = (newGameMode == IcsPlayingBlack) || ics_flip;
  1387.  
  1388.     /* Done with values from previous mode; copy in new ones */
  1389.     gameMode = newGameMode;
  1390.     ModeHighlight();
  1391.     ics_gamenum = gamenum;
  1392.     if (gamenum == gs_gamenum) {
  1393.         int klen = strlen(gs_kind);
  1394.         if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
  1395.         sprintf(str, "ICS %s", gs_kind);
  1396.         gameInfo.event = StrSave(str);
  1397.     } else {
  1398.         gameInfo.event = StrSave("ICS game");
  1399.     }
  1400.     gameInfo.site = StrSave(appData.icsHost);
  1401.     gameInfo.date = PGNDate();
  1402.     gameInfo.round = StrSave("-");
  1403.     gameInfo.white = StrSave(white);
  1404.     gameInfo.black = StrSave(black);
  1405.     ics_basetime = basetime * 60 * 1000; /* convert to ms */
  1406.     ics_increment = increment * 1000;    /* convert to ms */
  1407.     if (increment == 0)
  1408.       sprintf(str, "%d", basetime * 60);
  1409.     else
  1410.       sprintf(str, "%d+%d", basetime * 60, increment);
  1411.     gameInfo.timeControl = StrSave(str);
  1412.  
  1413.     /* Silence shouts if requested */
  1414.     if (appData.quietPlay &&
  1415.         (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
  1416.         SendToICS("set shout 0\n");
  1417.     }
  1418.     }
  1419.  
  1420.     /* Throw away game result if anything actually changes in examine mode */
  1421.     if (gameMode == IcsExamining && !newGame) {
  1422.     gameInfo.result = GameUnfinished;
  1423.     if (gameInfo.resultDetails != NULL) {
  1424.         free(gameInfo.resultDetails);
  1425.         gameInfo.resultDetails = NULL;
  1426.     }
  1427.     }
  1428.  
  1429.     /* In pausing && IcsExamining mode, we ignore boards coming
  1430.        in if they are in a different variation than we are. */
  1431.     if (pauseExamInvalid) return;
  1432.     if (pausing && gameMode == IcsExamining) {
  1433.     if (moveNum <= pauseExamForwardMostMove) {
  1434.         pauseExamInvalid = TRUE;
  1435.         forwardMostMove = pauseExamForwardMostMove;
  1436.         return;
  1437.     }
  1438.     }
  1439.  
  1440.     /* Parse the board */
  1441.     for (k = 0; k < 8; k++)
  1442.       for (j = 0; j < 8; j++)
  1443.     board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
  1444.     CopyBoard(boards[moveNum], board);
  1445.     if (moveNum == 0) {
  1446.     startedFromSetupPosition =
  1447.       !CompareBoards(board, initialPosition);
  1448.     }
  1449.     
  1450.     if (ics_getting_history == H_GOT_REQ_HEADER) {
  1451.     /* This was an initial position from a move list, not
  1452.        the current position */
  1453.     return;
  1454.     }
  1455.  
  1456.     /* Update currentMove and known move number limits */
  1457.     if (newGame) {
  1458.     forwardMostMove = backwardMostMove = currentMove = moveNum;
  1459.     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
  1460.            || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
  1461.     forwardMostMove = moveNum;
  1462.     if (!pausing || currentMove > forwardMostMove)
  1463.       currentMove = forwardMostMove;
  1464.     } else {
  1465.     /* New part of history that is not contiguous with old part */ 
  1466.     if (pausing && gameMode == IcsExamining) {
  1467.         pauseExamInvalid = TRUE;
  1468.         forwardMostMove = pauseExamForwardMostMove;
  1469.         return;
  1470.     }
  1471.     forwardMostMove = backwardMostMove = currentMove = moveNum;
  1472.     if (gameMode == IcsExamining && moveNum > 0) {
  1473.         ics_getting_history = H_REQUESTED;
  1474.         sprintf(str, "moves %d\n", gamenum);
  1475.         SendToICS(str);
  1476.     }
  1477.     }
  1478.  
  1479.     /* Update the clocks */
  1480.     timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
  1481.     timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
  1482.  
  1483. #ifdef ZIPPY
  1484.     if (appData.zippyPlay && newGame &&
  1485.     gameMode != IcsObserving && gameMode != IcsIdle &&
  1486.     gameMode != IcsExamining)
  1487.       ZippyFirstBoard(moveNum, basetime, increment);
  1488. #endif
  1489.  
  1490.     /* Put the move on the move list, first converting
  1491.        to canonical algebraic form. */
  1492.     if (moveNum > 0) {
  1493.     ChessMove moveType;
  1494.     int fromX, fromY, toX, toY;
  1495.     char promoChar;
  1496.  
  1497.     fromX = fromY = toX = toY = -1;
  1498.     if (moveNum <= backwardMostMove) {
  1499.         /* We don't know what the board looked like before
  1500.            this move.  Punt. */
  1501.         strcpy(parseList[moveNum - 1], move_str);
  1502.         strcat(parseList[moveNum - 1], " ");
  1503.         strcat(parseList[moveNum - 1], elapsed_time);
  1504.     } else if (ParseMachineMove(move_str, moveNum - 1, &moveType,
  1505.                     &fromX, &fromY, &toX, &toY, &promoChar)) {
  1506.         moveType = CoordsToAlgebraic(boards[moveNum - 1],
  1507.                      FakeFlags(moveNum - 1), EP_UNKNOWN,
  1508.                      fromY, fromX, toY, toX, promoChar,
  1509.                      parseList[moveNum-1]);
  1510.         strcat(parseList[moveNum - 1], " ");
  1511.         strcat(parseList[moveNum - 1], elapsed_time);
  1512.     } else if (strcmp(move_str, "none") == 0) {
  1513.         /* Again, we don't know what the board looked like;
  1514.            this is really the start of the game. */
  1515.         /* !!What should Zippy do if this happens? */
  1516.         /* !!Actually, I don't think this can happen currently. */
  1517.         parseList[moveNum - 1][0] = NULLCHAR;
  1518.         backwardMostMove = moveNum;
  1519.         startedFromSetupPosition = TRUE;
  1520.     } else {
  1521.         /* Move from ICS was illegal!?  Punt. */
  1522.         if (appData.debugMode) {
  1523.         sprintf(str, "Illegal move \"%s\" from ICS", move_str);
  1524.         DisplayError(str, 0);
  1525.         }
  1526.         strcpy(parseList[moveNum - 1], move_str);
  1527.         strcat(parseList[moveNum - 1], " ");
  1528.         strcat(parseList[moveNum - 1], elapsed_time);
  1529.     }
  1530.  
  1531. #ifdef ZIPPY
  1532.     /* Send move to chess program */
  1533.     if (appData.zippyPlay && !newGame) {
  1534.         if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
  1535.         (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
  1536.  
  1537.         if (fromX == -1) {
  1538.             sprintf(str, "Couldn't parse move \"%s\" from ICS",
  1539.                 move_str);
  1540.             DisplayError(str, 0);
  1541.         } else {
  1542.             SendMoveToProgram(moveType, fromX, fromY, toX, toY,
  1543.                       firstProgramPR, firstSendTime);
  1544.             if (firstMove) {
  1545.             firstMove = FALSE;
  1546.             SendToProgram(appData.whiteString, firstProgramPR);
  1547.             }
  1548.         }
  1549.         }
  1550.     }
  1551. #endif
  1552.     }
  1553.     
  1554.     /* Start the clocks */
  1555.     if (gameMode == IcsIdle || relation == 2 || relation == -2)
  1556.       DisplayBothClocks();
  1557.     else
  1558.       StartClocks();
  1559.  
  1560.     /* Display opponents and material strengths */
  1561.     sprintf(str, "%s (%d) vs. %s (%d)",
  1562.         gameInfo.white, white_stren, gameInfo.black, black_stren);
  1563.     DisplayTitle(str);
  1564.     
  1565.     /* Display the board */
  1566.     if (!pausing) {
  1567.     DrawPosition(FALSE, boards[currentMove]);
  1568.     DisplayMove(moveNum - 1);
  1569.     if (appData.ringBellAfterMoves && !ics_user_moved)
  1570.       RingBell();
  1571.     }
  1572. }
  1573.  
  1574.  
  1575. void SendMoveToICS(moveType, fromX, fromY, toX, toY)
  1576.      ChessMove moveType;
  1577.      int fromX, fromY, toX, toY;
  1578. {
  1579.     char user_move[MSG_SIZ];
  1580.     char promo_char;
  1581.  
  1582.     switch (moveType) {
  1583.       default:
  1584.     DisplayError("Internal error; bad moveType", 0);
  1585.     break;
  1586.       case WhiteKingSideCastle:
  1587.       case BlackKingSideCastle:
  1588.       case WhiteQueenSideCastleWild:
  1589.       case BlackQueenSideCastleWild:
  1590.     sprintf(user_move, "o-o\n");
  1591.     break;
  1592.       case WhiteQueenSideCastle:
  1593.       case BlackQueenSideCastle:
  1594.       case WhiteKingSideCastleWild:
  1595.       case BlackKingSideCastleWild:
  1596.     sprintf(user_move, "o-o-o\n");
  1597.     break;
  1598.       case WhitePromotionQueen:
  1599.       case BlackPromotionQueen:
  1600.       case WhitePromotionRook:
  1601.       case BlackPromotionRook:
  1602.       case WhitePromotionBishop:
  1603.       case BlackPromotionBishop:
  1604.       case WhitePromotionKnight:
  1605.       case BlackPromotionKnight:
  1606. #if 0 
  1607.     sprintf(user_move, "%c%c%c%c=%c\n",
  1608.         'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
  1609.         PieceToChar(PromoPiece(moveType)));
  1610. #else /* temporary code for FICS compatibility */
  1611.     promo_char = PieceToChar(PromoPiece(moveType));
  1612.     if (promo_char == 'n') promo_char = 'k';  /* for ICC (!) */
  1613.     sprintf(user_move, "promote %c\n%c%c%c%c\n",
  1614.         PieceToChar(PromoPiece(moveType)),
  1615.         'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
  1616. #endif
  1617.     break;
  1618.       case NormalMove:
  1619.       case WhiteCapturesEnPassant:
  1620.       case BlackCapturesEnPassant:
  1621.     sprintf(user_move, "%c%c%c%c\n",
  1622.         'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
  1623.     break;
  1624.     }
  1625.     SendToICS(user_move);
  1626. }
  1627.  
  1628. void ProcessICSInitScript(f)
  1629.      FILE *f;
  1630. {
  1631.     char buf[MSG_SIZ];
  1632.  
  1633.     while (fgets(buf, MSG_SIZ, f)) {
  1634.     SendToICS(buf);
  1635.     }
  1636.  
  1637.     fclose(f);
  1638. }
  1639.  
  1640.  
  1641. /* Parser for moves from gnuchess or ICS */
  1642. Boolean ParseMachineMove(machineMove, moveNum,
  1643.              moveType, fromX, fromY, toX, toY, promoChar)
  1644.      char *machineMove;
  1645.      int moveNum;
  1646.      ChessMove *moveType;
  1647.      int *fromX, *fromY, *toX, *toY;
  1648.      char *promoChar;
  1649. {       
  1650.     *moveType = yylexstr(moveNum, machineMove);
  1651.     switch (*moveType) {
  1652.       case WhitePromotionQueen:
  1653.       case BlackPromotionQueen:
  1654.       case WhitePromotionRook:
  1655.       case BlackPromotionRook:
  1656.       case WhitePromotionBishop:
  1657.       case BlackPromotionBishop:
  1658.       case WhitePromotionKnight:
  1659.       case BlackPromotionKnight:
  1660.       case NormalMove:
  1661.       case WhiteCapturesEnPassant:
  1662.       case BlackCapturesEnPassant:
  1663.       case WhiteKingSideCastle:
  1664.       case WhiteQueenSideCastle:
  1665.       case BlackKingSideCastle:
  1666.       case BlackQueenSideCastle:
  1667.       case WhiteKingSideCastleWild:
  1668.       case WhiteQueenSideCastleWild:
  1669.       case BlackKingSideCastleWild:
  1670.       case BlackQueenSideCastleWild:
  1671.     *fromX = currentMoveString[0] - 'a';
  1672.     *fromY = currentMoveString[1] - '1';
  1673.     *toX = currentMoveString[2] - 'a';
  1674.     *toY = currentMoveString[3] - '1';
  1675.     *promoChar = currentMoveString[4];
  1676.     return TRUE;
  1677.  
  1678.       case BadMove:
  1679.     /* bug?  Try parsing as coordinate notation. */
  1680.     *fromX = machineMove[0] - 'a';
  1681.     *fromY = machineMove[1] - '1';
  1682.     *toX = machineMove[2] - 'a';
  1683.     *toY = machineMove[3] - '1';
  1684.     *promoChar = machineMove[4];
  1685.     if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
  1686.         *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7)
  1687.       *fromX = *fromY = *toX = *toY = 0;
  1688.     return FALSE;
  1689.  
  1690.       case AmbiguousMove:
  1691.       case (ChessMove) 0:    /* end of file */
  1692.       case ElapsedTime:
  1693.       case Comment:
  1694.       case PGNTag:
  1695.       case WhiteWins:
  1696.       case BlackWins:
  1697.       case GameIsDrawn:
  1698.       default:
  1699.     /* bug? */
  1700.     *fromX = *fromY = *toX = *toY = 0;
  1701.     *promoChar = NULLCHAR;
  1702.     return FALSE;
  1703.     }
  1704. }
  1705.  
  1706.  
  1707. void InitPosition(redraw)
  1708.      int redraw;
  1709. {
  1710.     currentMove = forwardMostMove = backwardMostMove = 0;
  1711.     CopyBoard(boards[0], initialPosition);
  1712.     if (redraw)
  1713.       DrawPosition(FALSE, boards[currentMove]);
  1714. }
  1715.  
  1716. void SendCurrentBoard(pr)
  1717.      ProcRef pr;
  1718. {
  1719.     SendBoard(pr, boards[currentMove]);
  1720. }
  1721.  
  1722. void SendBoard(pr, board)
  1723.      ProcRef pr;
  1724.      Board board;
  1725. {
  1726.     char message[MSG_SIZ];
  1727.     ChessSquare *bp;
  1728.     int i, j;
  1729.     
  1730.     SendToProgram("edit\n", pr);
  1731.     SendToProgram("#\n", pr);
  1732.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  1733.     bp = &board[i][0];
  1734.     for (j = 0; j < BOARD_SIZE; j++, bp++) {
  1735.         if ((int) *bp < (int) BlackPawn) {
  1736.         sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
  1737.             'a' + j, '1' + i);
  1738.         SendToProgram(message, pr);
  1739.         }
  1740.     }
  1741.     }
  1742.     
  1743.     SendToProgram("c\n", pr);
  1744.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  1745.     bp = &board[i][0];
  1746.     for (j = 0; j < BOARD_SIZE; j++, bp++) {
  1747.         if (((int) *bp != (int) EmptySquare)
  1748.         && ((int) *bp >= (int) BlackPawn)) {
  1749.         sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
  1750.             'a' + j, '1' + i);
  1751.         SendToProgram(message, pr);
  1752.         }
  1753.     }
  1754.     }
  1755.     
  1756.     SendToProgram(".\n", pr);
  1757. }
  1758.  
  1759.  
  1760. void SendMoveToProgram(moveType, fromX, fromY, toX, toY, programPR, sendTime)
  1761.      ChessMove moveType;
  1762.      int fromX, fromY, toX, toY;
  1763.      ProcRef programPR;
  1764.      int sendTime;
  1765. {
  1766.     char user_move[MSG_SIZ];
  1767.     char promoChar = ToLower(PieceToChar(PromoPiece(moveType)));
  1768.  
  1769.     if (promoChar == '.')
  1770.       sprintf(user_move, "%c%c%c%c\n",
  1771.           'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
  1772.     else
  1773.       sprintf(user_move, "%c%c%c%c%c\n",
  1774.           'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
  1775.           promoChar);
  1776.     
  1777.     if (sendTime)
  1778.       SendTimeRemaining(programPR);
  1779.     SendToProgram(user_move, programPR);
  1780.     strcpy(moveList[currentMove - 1], user_move);  /* note side-effect */
  1781. }
  1782.  
  1783.  
  1784. int IsPromotion(fromX, fromY, toX, toY)
  1785.      int fromX, fromY, toX, toY;
  1786. {
  1787.     return gameMode != EditPosition &&
  1788.       fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
  1789.     ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
  1790.      (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
  1791. }
  1792.  
  1793.  
  1794. int OKToStartUserMove(x, y)
  1795.      int x, y;
  1796. {
  1797.     ChessSquare from_piece;
  1798.     int white_piece;
  1799.  
  1800.     if (matchMode) return FALSE;
  1801.     if (gameMode == EditPosition) return TRUE;
  1802.  
  1803.     if (x >= 0 && y >= 0)
  1804.       from_piece = boards[currentMove][y][x];
  1805.     else
  1806.       from_piece = EmptySquare;
  1807.  
  1808.     if (from_piece == EmptySquare) return FALSE;
  1809.  
  1810.     white_piece = (int)from_piece >= (int)WhitePawn &&
  1811.                   (int)from_piece <= (int)WhiteKing;
  1812.  
  1813.     switch (gameMode) {
  1814.       case PlayFromGameFile:
  1815.       case TwoMachinesPlay:
  1816.       case EndOfGame:
  1817.     return FALSE;
  1818.  
  1819.       case IcsObserving:
  1820.       case IcsIdle:
  1821.     return FALSE;
  1822.  
  1823.       case MachinePlaysWhite:
  1824.       case IcsPlayingBlack:
  1825.     if (appData.zippyPlay) return FALSE;
  1826.     if (white_piece) {
  1827.         DisplayError("You are playing Black", 0);
  1828.         return FALSE;
  1829.     }
  1830.     break;
  1831.  
  1832.       case MachinePlaysBlack:
  1833.       case IcsPlayingWhite:
  1834.     if (appData.zippyPlay) return FALSE;
  1835.     if (!white_piece) {
  1836.         DisplayError("You are playing White", 0);
  1837.         return FALSE;
  1838.     }
  1839.     break;
  1840.  
  1841.       case EditGame:
  1842.     if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
  1843.         /* Editing correspondence game history */
  1844.         /* Could disallow this or prompt for confirmation */
  1845.         cmailOldMove = -1;
  1846.     }
  1847.     if (currentMove < forwardMostMove) {
  1848.         /* Discarding moves */
  1849.         /* Could prompt for confirmation here,
  1850.            but I don't think that's such a good idea */
  1851.         forwardMostMove = currentMove;
  1852.     }
  1853.     break;
  1854.  
  1855.       case BeginningOfGame:
  1856.     if (appData.icsActive) return FALSE;
  1857.     if (!appData.noChessProgram) {
  1858.         if (!white_piece) {
  1859.         DisplayError("You are playing White", 0);
  1860.         return FALSE;
  1861.         }
  1862.     }
  1863.     break;
  1864.  
  1865.       default:
  1866.       case IcsExamining:
  1867.     break;
  1868.     }
  1869.     if (currentMove != forwardMostMove) {
  1870.     DisplayError("Displayed position is not current", 0);
  1871.     return FALSE;
  1872.     }
  1873.     return TRUE;
  1874. }
  1875.  
  1876. FILE *lastLoadGameFP = NULL;
  1877. int lastLoadGameNumber = 0;
  1878. char lastLoadGameTitle[MSG_SIZ];
  1879.  
  1880. void UserMoveEvent(fromX, fromY, toX, toY, promoChar)
  1881.      int fromX, fromY, toX, toY;
  1882.      int promoChar;
  1883. {
  1884.     ChessMove moveType;
  1885.     
  1886.     if (fromX < 0 || fromY < 0) return;
  1887.     if ((fromX == toX) && (fromY == toY)) {
  1888.     return;
  1889.     }
  1890.     
  1891.     if (toX < 0 || toY < 0) {
  1892.     if (gameMode == EditPosition && (toX == -2 || toY == -2)) {
  1893.         boards[0][fromY][fromX] = EmptySquare;
  1894.         DrawPosition(FALSE, boards[currentMove]);
  1895.     }
  1896.     return;
  1897.     }
  1898.     
  1899.     if (gameMode == EditPosition) {
  1900.     boards[0][toY][toX] = boards[0][fromY][fromX];
  1901.     boards[0][fromY][fromX] = EmptySquare;
  1902.     DrawPosition(FALSE, boards[currentMove]);
  1903.     return;
  1904.     }
  1905.     
  1906.     /* Check if the user is playing in turn.  This is complicated because we
  1907.        let the user "pick up" a piece before it is his turn.  So the piece he
  1908.        tried to pick up may have been captured by the time he puts it down!
  1909.        Therefore we use the color the user is supposed to be playing in this
  1910.        test, not the color of the piece that is currently on the starting
  1911.        square---except in EditGame mode, where the user is playing both
  1912.        sides; fortunately there the capture race can't happen.  (It can
  1913.        now happen in IcsExamining mode, but that's just too bad.  The user
  1914.        will get a somewhat confusing message in that case.)
  1915.     */
  1916.     if (gameMode == MachinePlaysWhite || gameMode == IcsPlayingBlack ||
  1917.     ((gameMode == EditGame || gameMode == BeginningOfGame ||
  1918.       gameMode == IcsExamining) && 
  1919.      (int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
  1920.      (int) boards[currentMove][fromY][fromX] <= (int) BlackKing)) {
  1921.     /* User is moving for Black */
  1922.     if (WhiteOnMove(currentMove)) {
  1923.         DisplayError("It is White's turn", 0);
  1924.         return;
  1925.     }
  1926.     } else {
  1927.     /* User is moving for White */
  1928.     if (!WhiteOnMove(currentMove)) {
  1929.         DisplayError("It is Black's turn", 0);
  1930.         return;
  1931.     }
  1932.     }
  1933.  
  1934.     moveType = LegalityTest(boards[currentMove], FakeFlags(currentMove),
  1935.                 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
  1936.  
  1937.     if (moveType == BadMove) {
  1938.     DisplayError("Illegal move", 0);
  1939.     return;
  1940.     }
  1941.  
  1942.     /* If we need the chess program but it's dead, restart it */
  1943.     ResurrectChessProgram();
  1944.  
  1945.     /* A user move restarts a paused game*/
  1946.     if (pausing)
  1947.       PauseEvent();
  1948.     
  1949.     thinkOutput[0] = NULLCHAR;
  1950.     MakeMove(&moveType, fromX, fromY, toX, toY);
  1951.  
  1952.     if (appData.icsActive) {
  1953.     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
  1954.         gameMode == IcsExamining) {
  1955.         SendMoveToICS(moveType, fromX, fromY, toX, toY);
  1956.         ics_user_moved = 1;
  1957.     }
  1958.     } else {
  1959.     SendMoveToProgram(moveType, fromX, fromY, toX, toY,
  1960.               firstProgramPR, firstSendTime);
  1961.  
  1962.     if (currentMove == cmailOldMove + 1) {
  1963.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
  1964.     }
  1965.     }
  1966.     
  1967.     switch (gameMode) {
  1968.       case BeginningOfGame:
  1969.     if (appData.noChessProgram)
  1970.       lastGameMode = gameMode = EditGame;
  1971.     else
  1972.       lastGameMode = gameMode = MachinePlaysBlack;
  1973.     SetGameInfo();
  1974.     ModeHighlight();
  1975.     break;
  1976.  
  1977.       case EditGame:
  1978.     switch (MateTest(boards[currentMove], FakeFlags(currentMove),
  1979.              EP_UNKNOWN)) {
  1980.       case MT_NONE:
  1981.       case MT_CHECK:
  1982.         break;
  1983.       case MT_CHECKMATE:
  1984.         if (WhiteOnMove(currentMove)) {
  1985.         GameEnds(BlackWins, "Black mates", GE_PLAYER);
  1986.         } else {
  1987.         GameEnds(WhiteWins, "White mates", GE_PLAYER);
  1988.         }
  1989.         break;
  1990.       case MT_STALEMATE:
  1991.         GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
  1992.         break;
  1993.     }
  1994.     break;
  1995.  
  1996.       case MachinePlaysBlack:
  1997.       case MachinePlaysWhite:
  1998.       default:
  1999.     break;
  2000.     }
  2001. }
  2002.  
  2003. void HandleMachineMove(message, isr)
  2004.      char *message;
  2005.      InputSourceRef isr;
  2006. {
  2007.     char machineMove[MSG_SIZ], buf1[MSG_SIZ], buf2[MSG_SIZ];
  2008.     int fromX, fromY, toX, toY;
  2009.     ChessMove moveType;
  2010.     char promoChar;
  2011.     
  2012.     if (strncmp(message, "warning:", 8) == 0) {
  2013.     DisplayError(message, 0);
  2014.     return;
  2015.     }
  2016.     
  2017.     /*
  2018.      * If chess program startup fails, exit with an error message.
  2019.      * Attempts to recover here are futile.
  2020.      */
  2021.     if ((StrStr(message, "unknown host") != NULL)
  2022.     || (StrStr(message, "No remote directory") != NULL)
  2023.     || (StrStr(message, "not found") != NULL)
  2024.     || (StrStr(message, "No such file") != NULL)
  2025.     || (StrStr(message, "can't alloc") != NULL)
  2026.     || (StrStr(message, "Permission denied") != NULL)) {
  2027.     maybePondering = FALSE;
  2028.     sprintf(buf1,
  2029.         "Failed to start chess program %s on %s: %s\n",
  2030.         isr == firstProgramISR ? appData.firstChessProgram
  2031.         : appData.secondChessProgram,
  2032.         isr == firstProgramISR ? appData.firstHost
  2033.         : appData.secondHost,
  2034.         message);
  2035.     RemoveInputSource(isr);
  2036.     DisplayFatalError(buf1, 0, 1);
  2037.     return;
  2038.     }
  2039.     
  2040.     /*
  2041.      * If the move is illegal, cancel it and redraw the board.
  2042.      */
  2043.     if (strncmp(message, "Illegal move", 12) == 0) {
  2044.     maybePondering = FALSE;
  2045.     if (StrStr(message, "time") || StrStr(message, "otim") ||
  2046.         StrStr(message, timeTestStr)) {
  2047.         if (isr == firstProgramISR) {
  2048.         /* First program doesn't have "time" or "otim" command */
  2049.         firstSendTime = 0;
  2050.         return;
  2051.         } else if (isr == secondProgramISR) {
  2052.         /* Second program doesn't have "time" or "otim" command */
  2053.         secondSendTime = 0;
  2054.         return;
  2055.         }
  2056.     }
  2057.     if (forwardMostMove <= backwardMostMove) return;
  2058.     if (WhiteOnMove(forwardMostMove) !=
  2059.         ( (gameMode == MachinePlaysWhite && isr == firstProgramISR) ||
  2060.           (gameMode == TwoMachinesPlay && isr == secondProgramISR) )) {
  2061.         /* Bogus complaint about gnuchess's own hint move; gnuchess bug */
  2062. #ifndef ZIPPY
  2063.         sprintf(buf1, "%s chess program gave bogus error message:\n %s",
  2064.             isr == firstProgramISR ? "first" : "second", message);
  2065.         DisplayFatalError(buf1, 0, -1); /* don't exit */
  2066. #endif /*ZIPPY*/
  2067.         return;
  2068.     }
  2069.     if (pausing) PauseEvent();
  2070.     if (gameMode == PlayFromGameFile) {
  2071.         /* Stop reading this game file */
  2072.         gameMode = EditGame;
  2073.         ModeHighlight();
  2074.     }
  2075.     currentMove = --forwardMostMove;
  2076.     SwitchClocks();
  2077.     sprintf(buf1, "Illegal move \"%s\"\n(rejected by %schess program)",
  2078.         parseList[currentMove],
  2079.         isr == firstProgramISR ? "first " :
  2080.         (isr == secondProgramISR ? "second " : ""));
  2081.     DisplayError(buf1, 0);
  2082.     
  2083.     DrawPosition(FALSE, boards[currentMove]);
  2084.     return;
  2085.     }
  2086.     
  2087.     if (strncmp(message, "time", 4) == 0 && StrStr(message, "CHESS")) {
  2088.     /* Program has a broken "time" command that
  2089.        outputs a string not ending in newline.
  2090.        Don't use it. */
  2091.     if (isr == firstProgramISR) firstSendTime = 0;
  2092.     if (isr == secondProgramISR) secondSendTime = 0;
  2093.     }
  2094.     
  2095.     if (strncmp(message, "Hint:", 5) == 0) {
  2096.     if (hintRequested) {
  2097.         hintRequested = FALSE;
  2098.         sscanf(message, "Hint: %s", machineMove);
  2099.         if (ParseMachineMove(machineMove, forwardMostMove, &moveType,
  2100.                  &fromX, &fromY, &toX, &toY, &promoChar)) {
  2101.         moveType =
  2102.           CoordsToAlgebraic(boards[forwardMostMove],
  2103.                     FakeFlags(forwardMostMove), EP_UNKNOWN,
  2104.                     fromY, fromX, toY, toX, promoChar, buf1);
  2105.         sprintf(buf2, "Hint: %s", buf1);
  2106.         DisplayInformation(buf2);
  2107.         } else {
  2108.         /* Hint move was illegal!? */
  2109.         sprintf(buf1, "Illegal hint move \"%s\"\nfrom %schess program",
  2110.             machineMove, isr == firstProgramISR ? "first " :
  2111.             (isr == secondProgramISR ? "second " : ""));
  2112.         DisplayError(buf1, 0);
  2113.         }
  2114.     }
  2115.     return;
  2116.     }
  2117.     
  2118.     /*
  2119.      * win, lose or draw
  2120.      */
  2121.     if (strncmp(message, "White", 5) == 0) {
  2122.     GameEnds(WhiteWins, "White mates", GE_GNU);
  2123.     return;
  2124.     } else if (strncmp(message, "Black", 5) == 0) {
  2125.     GameEnds(BlackWins, "Black mates", GE_GNU);
  2126.     return;
  2127.     } else if (strncmp(message, "opponent mates!", 15) == 0) {
  2128.     switch (gameMode) {
  2129.       case MachinePlaysBlack:
  2130.         GameEnds(WhiteWins, "White mates", GE_GNU);
  2131.         break;
  2132.       case MachinePlaysWhite:
  2133.         GameEnds(BlackWins, "Black mates", GE_GNU);
  2134.         break;
  2135.       case TwoMachinesPlay:
  2136.         if (isr == firstProgramISR)
  2137.           GameEnds(WhiteWins, "White mates", GE_GNU);
  2138.         else
  2139.           GameEnds(BlackWins, "Black mates", GE_GNU);
  2140.         break;
  2141.       default:
  2142.         /* can't happen */
  2143.         break;
  2144.     }
  2145.     return;
  2146.     } else if (strncmp(message, "computer mates!", 15) == 0) {
  2147.     switch (gameMode) {
  2148.       case MachinePlaysBlack:
  2149.         GameEnds(BlackWins, "Black mates", GE_GNU);
  2150.         break;
  2151.       case MachinePlaysWhite:
  2152.         GameEnds(WhiteWins, "White mates", GE_GNU);
  2153.         break;
  2154.       case TwoMachinesPlay:
  2155.         if (isr == firstProgramISR)
  2156.           GameEnds(BlackWins, "Black mates", GE_GNU);
  2157.         else
  2158.           GameEnds(WhiteWins, "White mates", GE_GNU);
  2159.         break;
  2160.       default:
  2161.         /* can't happen */
  2162.         break;
  2163.     }
  2164.     return;
  2165.     } else if (strncmp(message, "Draw", 4) == 0) {
  2166. #ifdef ZIPPY
  2167.     if (appData.zippyPlay) {
  2168.         /* If this was a draw by repetition or the
  2169.            50-move rule, we have to claim it.
  2170.            */
  2171.         SendToICS("draw\n");
  2172.     }
  2173. #endif
  2174.     GameEnds(GameIsDrawn, "Draw", GE_GNU);
  2175.     return;
  2176.     }    
  2177.  
  2178.     if (appData.showThinking) {
  2179.     int plylev, curscore, time, nodes;
  2180.     char plyext;
  2181.     if (sscanf(message, "%d%c %d %d %d",
  2182.            &plylev, &plyext, &curscore, &time, &nodes) == 5) {
  2183.         sprintf(thinkOutput, "%s%+.2f  %s",
  2184.             gameMode == TwoMachinesPlay ?
  2185.               (isr == firstProgramISR ? "B" : "W") : "",
  2186.             ((double) curscore) / 100.0, &message[27]);
  2187.         if (currentMove == forwardMostMove)
  2188.           DisplayMove(currentMove - 1);
  2189.         return;
  2190.     } else if (thinkOutput[0] != NULLCHAR &&
  2191.            strncmp(message, "                           ", 27) == 0) {
  2192.         strcat(thinkOutput, &message[26]);
  2193.         if (currentMove == forwardMostMove)
  2194.           DisplayMove(currentMove - 1);
  2195.         return;
  2196.     }
  2197.     }
  2198.  
  2199.     if (bookRequested) {
  2200.     if (message[0] == '\t' || strncmp(message, "        ", 8) == 0) {
  2201.         sscanf(message, "%s %s %s", machineMove, buf1, buf2);
  2202.         sprintf(&bookOutput[strlen(bookOutput)], "%s   %s   %s    \n",
  2203.             machineMove, buf1, buf2);
  2204.     } else if (bookOutput[0] != NULLCHAR) {
  2205.         DisplayInformation(bookOutput);
  2206.         bookRequested = FALSE;
  2207.     }
  2208.     }
  2209.  
  2210.     if (StrStr(message, "...") != NULL) {
  2211.     /* Normal machine reply move */
  2212.     sscanf(message, "%s %s %s", buf1, buf2, machineMove);
  2213.     if (machineMove[0] == NULLCHAR)
  2214.       return;
  2215.     /* Fall through to code below */
  2216.     } else {
  2217.     return;        /* ignore noise */
  2218.     }
  2219.  
  2220.     /*
  2221.      * Normal machine reply move
  2222.      */
  2223.     hintRequested = FALSE;
  2224.     bookRequested = FALSE;
  2225.     maybePondering = TRUE;
  2226.     if (firstSendTime == 2 && isr == firstProgramISR) firstSendTime = 1;
  2227.     if (secondSendTime == 2 && isr == secondProgramISR) secondSendTime = 1;
  2228.  
  2229.     if (!ParseMachineMove(machineMove, forwardMostMove, &moveType,
  2230.               &fromX, &fromY, &toX, &toY, &promoChar)) {
  2231.     /* Machine move was illegal!?  Give message and continue. */
  2232.     sprintf(buf1, "Illegal move \"%s\" from machine",
  2233.         machineMove);
  2234.     DisplayError(buf1, 0);
  2235.     }
  2236.     
  2237.     strcat(machineMove, "\n");
  2238.     strcpy(moveList[forwardMostMove], machineMove);
  2239.     
  2240.     if (!pausing)
  2241.       currentMove = forwardMostMove;  /*display latest move*/
  2242.     
  2243.     MakeMove(&moveType, fromX, fromY, toX, toY);
  2244.     
  2245.     if (!pausing && appData.ringBellAfterMoves)
  2246.       RingBell();
  2247.     
  2248. #ifdef ZIPPY
  2249.     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) {
  2250.     SendMoveToICS(moveType, fromX, fromY, toX, toY);
  2251.     ics_user_moved = 1;
  2252.     }
  2253. #endif
  2254.  
  2255.     if (gameMode == TwoMachinesPlay) {
  2256.     if (WhiteOnMove(forwardMostMove)) {
  2257.         if (secondSendTime) 
  2258.           SendTimeRemaining(secondProgramPR);
  2259.         SendToProgram(machineMove, secondProgramPR);
  2260.         if (firstMove) {
  2261.         firstMove = FALSE;
  2262.         SendToProgram(appData.whiteString, secondProgramPR);
  2263.         }
  2264.     } else {
  2265.         if (firstSendTime)
  2266.           SendTimeRemaining(firstProgramPR);
  2267.         SendToProgram(machineMove, firstProgramPR);
  2268.         if (firstMove) {
  2269.         firstMove = FALSE;
  2270.         SendToProgram(appData.blackString, firstProgramPR);
  2271.         }
  2272.     }
  2273.     }
  2274. }
  2275.  
  2276.  
  2277. /* Parse a game score from the character string "game", and
  2278.    record it as the history of the current game.  The game
  2279.    score is NOT assumed to start from the standard position. 
  2280.    The display is not updated in any way.
  2281.    */
  2282. void ParseGameHistory(game)
  2283.      char *game;
  2284. {
  2285.     ChessMove moveType;
  2286.     int fromX, fromY, toX, toY, boardIndex;
  2287.     char promoChar;
  2288.     char *p, *q;
  2289.     char buf[MSG_SIZ];
  2290.  
  2291.     if (appData.debugMode)
  2292.       fprintf(debugFP, "Parsing game history: %s\n", game);
  2293.  
  2294.     gameInfo.event = StrSave("ICS game");
  2295.     gameInfo.site = StrSave(appData.icsHost);
  2296.     gameInfo.date = PGNDate();
  2297.     gameInfo.round = StrSave("-");
  2298.  
  2299.     /* Parse out names of players */
  2300.     while (*game == ' ') game++;
  2301.     p = buf;
  2302.     while (*game != ' ') *p++ = *game++;
  2303.     *p = NULLCHAR;
  2304.     gameInfo.white = StrSave(buf);
  2305.     while (*game == ' ') game++;
  2306.     p = buf;
  2307.     while (*game != ' ' && *game != '\n') *p++ = *game++;
  2308.     *p = NULLCHAR;
  2309.     gameInfo.black = StrSave(buf);
  2310.  
  2311.     /* Parse moves */
  2312.     boardIndex = 0;
  2313.     yynewstr(game);
  2314.     for (;;) {
  2315.     yyboardindex = boardIndex;
  2316.     moveType = (ChessMove) yylex();
  2317.     switch (moveType) {
  2318.       case WhitePromotionQueen:
  2319.       case BlackPromotionQueen:
  2320.       case WhitePromotionRook:
  2321.       case BlackPromotionRook:
  2322.       case WhitePromotionBishop:
  2323.       case BlackPromotionBishop:
  2324.       case WhitePromotionKnight:
  2325.       case BlackPromotionKnight:
  2326.       case NormalMove:
  2327.       case WhiteCapturesEnPassant:
  2328.       case BlackCapturesEnPassant:
  2329.       case WhiteKingSideCastle:
  2330.       case WhiteQueenSideCastle:
  2331.       case BlackKingSideCastle:
  2332.       case BlackQueenSideCastle:
  2333.       case WhiteKingSideCastleWild:
  2334.       case WhiteQueenSideCastleWild:
  2335.       case BlackKingSideCastleWild:
  2336.       case BlackQueenSideCastleWild:
  2337.         fromX = currentMoveString[0] - 'a';
  2338.         fromY = currentMoveString[1] - '1';
  2339.         toX = currentMoveString[2] - 'a';
  2340.         toY = currentMoveString[3] - '1';
  2341.         promoChar = currentMoveString[4];
  2342.         break;
  2343.       case BadMove:
  2344.         /* bug? */
  2345.         sprintf(buf, "Bad move in ICS output: \"%s\"", yy_text);
  2346.         DisplayError(buf, 0);
  2347.         return;
  2348.       case AmbiguousMove:
  2349.         /* bug? */
  2350.         sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
  2351.         DisplayError(buf, 0);
  2352.         return;
  2353.       case (ChessMove) 0:    /* end of file */
  2354.         if (boardIndex < backwardMostMove) {
  2355.         /* Oops, gap.  How did that happen? */
  2356.         return;
  2357.         }
  2358.         backwardMostMove = 0;
  2359.         if (boardIndex > forwardMostMove) {
  2360.         forwardMostMove = boardIndex;
  2361.         }
  2362.         return;
  2363.       case ElapsedTime:
  2364.         if (boardIndex > 0) {
  2365.         strcat(parseList[boardIndex-1], " ");
  2366.         strcat(parseList[boardIndex-1], yy_text);
  2367.         }
  2368.         continue;
  2369.       case Comment:
  2370.       case PGNTag:
  2371.       default:
  2372.         /* ignore */
  2373.         continue;
  2374.       case WhiteWins:
  2375.       case BlackWins:
  2376.       case GameIsDrawn:
  2377.       case GameUnfinished:
  2378.         if (gameMode == IcsExamining) {
  2379.         if (boardIndex < backwardMostMove) {
  2380.             /* Oops, gap.  How did that happen? */
  2381.             return;
  2382.         }
  2383.         backwardMostMove = 0;
  2384.         return;
  2385.         }
  2386.         gameInfo.result = moveType;
  2387.         p = strchr(yy_text, '{');
  2388.         if (p == NULL) p = strchr(yy_text, '(');
  2389.         if (p == NULL) {
  2390.         p = yy_text;
  2391.         if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
  2392.         } else {
  2393.         q = strchr(p, *p == '{' ? '}' : ')');
  2394.         if (q != NULL) *q = NULLCHAR;
  2395.         p++;
  2396.         }
  2397.         gameInfo.resultDetails = StrSave(p);
  2398.         continue;
  2399.     }
  2400.     if (boardIndex >= forwardMostMove &&
  2401.         !(gameMode == IcsObserving && ics_gamenum == -1)) {
  2402.         backwardMostMove = 0;
  2403.         return;
  2404.     }
  2405.     (void) CoordsToAlgebraic(boards[boardIndex], FakeFlags(boardIndex),
  2406.                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
  2407.                  parseList[boardIndex]);
  2408.     CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
  2409.     boardIndex++;
  2410.     ApplyMove(&moveType, fromX, fromY, toX, toY,
  2411.           boards[boardIndex]);
  2412.     }
  2413. }
  2414.  
  2415.  
  2416. /* Apply a move to the given board.  Oddity: moveType is ignored on
  2417.    input unless the move is seen to be a pawn promotion, in which case
  2418.    moveType tells us what to promote to.
  2419. */
  2420. void ApplyMove(moveType, fromX, fromY, toX, toY, board)
  2421.      ChessMove *moveType;
  2422.      int fromX, fromY, toX, toY;
  2423.      Board board;
  2424. {
  2425.     if (fromY == 0 && fromX == 4
  2426.     && board[fromY][fromX] == WhiteKing
  2427.     && toY == 0 && toX == 6) {
  2428.     *moveType = WhiteKingSideCastle;
  2429.      board[fromY][fromX] = EmptySquare;
  2430.      board[toY][toX] = WhiteKing;
  2431.      board[fromY][7] = EmptySquare;
  2432.      board[toY][5] = WhiteRook;
  2433.     } else if (fromY == 0 && fromX == 4
  2434.            && board[fromY][fromX] == WhiteKing
  2435.            && toY == 0 && toX == 2) {
  2436.     *moveType = WhiteQueenSideCastle;
  2437.      board[fromY][fromX] = EmptySquare;
  2438.      board[toY][toX] = WhiteKing;
  2439.      board[fromY][0] = EmptySquare;
  2440.      board[toY][3] = WhiteRook;
  2441.     } else if (fromY == 0 && fromX == 3
  2442.      && board[fromY][fromX] == WhiteKing
  2443.      && toY == 0 && toX == 5) {
  2444.      *moveType = WhiteKingSideCastleWild;
  2445.      board[fromY][fromX] = EmptySquare;
  2446.      board[toY][toX] = WhiteKing;
  2447.      board[fromY][7] = EmptySquare;
  2448.      board[toY][4] = WhiteRook;
  2449.     } else if (fromY == 0 && fromX == 3
  2450.             && board[fromY][fromX] == WhiteKing
  2451.             && toY == 0 && toX == 1) {
  2452.      *moveType = WhiteQueenSideCastleWild;
  2453.      board[fromY][fromX] = EmptySquare;
  2454.      board[toY][toX] = WhiteKing;
  2455.      board[fromY][0] = EmptySquare;
  2456.      board[toY][2] = WhiteRook;
  2457.     } else if (fromY == 6
  2458.            && board[fromY][fromX] == WhitePawn
  2459.            && toY == 7) {
  2460.     /* white pawn promotion */
  2461.     board[7][toX] = PromoPiece(*moveType);
  2462.     if (board[7][toX] == EmptySquare) {
  2463.         board[7][toX] = WhiteQueen;
  2464.         *moveType = WhitePromotionQueen;
  2465.     }
  2466.     board[6][fromX] = EmptySquare;
  2467.     } else if ((fromY == 4)
  2468.            && (toX != fromX)
  2469.            && (board[fromY][fromX] == WhitePawn)
  2470.            && (board[toY][toX] == EmptySquare)) {
  2471.     *moveType = WhiteCapturesEnPassant;
  2472.     board[fromY][fromX] = EmptySquare;
  2473.     board[toY][toX] = WhitePawn;
  2474.     board[toY - 1][toX] = EmptySquare;
  2475.    } else if (fromY == 7 && fromX == 4
  2476.            && board[fromY][fromX] == BlackKing
  2477.            && toY == 7 && toX == 6) {
  2478.     *moveType = BlackKingSideCastle;
  2479.      board[fromY][fromX] = EmptySquare;
  2480.      board[toY][toX] = BlackKing;
  2481.      board[fromY][7] = EmptySquare;
  2482.      board[toY][5] = BlackRook;
  2483.    } else if (fromY == 7 && fromX == 4
  2484.            && board[fromY][fromX] == BlackKing
  2485.            && toY == 7 && toX == 2) {
  2486.     *moveType = BlackQueenSideCastle;
  2487.      board[fromY][fromX] = EmptySquare;
  2488.      board[toY][toX] = BlackKing;
  2489.      board[fromY][0] = EmptySquare;
  2490.      board[toY][3] = BlackRook;
  2491.     } else if (fromY == 7 && fromX == 3
  2492.             && board[fromY][fromX] == BlackKing
  2493.             && toY == 7 && toX == 5) {
  2494.      *moveType = BlackKingSideCastleWild;
  2495.      board[fromY][fromX] = EmptySquare;
  2496.      board[toY][toX] = BlackKing;
  2497.      board[fromY][7] = EmptySquare;
  2498.      board[toY][4] = BlackRook;
  2499.     } else if (fromY == 7 && fromX == 3
  2500.             && board[fromY][fromX] == BlackKing
  2501.             && toY == 7 && toX == 1) {
  2502.      *moveType = BlackQueenSideCastleWild;
  2503.      board[fromY][fromX] = EmptySquare;
  2504.      board[toY][toX] = BlackKing;
  2505.      board[fromY][0] = EmptySquare;
  2506.      board[toY][2] = BlackRook;
  2507.     } else if (fromY == 1
  2508.            && board[fromY][fromX] == BlackPawn
  2509.            && toY == 0) {
  2510.     /* black pawn promotion */
  2511.     board[0][toX] = PromoPiece(*moveType);
  2512.     if (board[0][toX] == EmptySquare) {
  2513.         board[0][toX] = BlackQueen;
  2514.         *moveType = BlackPromotionQueen;
  2515.     }
  2516.     board[1][fromX] = EmptySquare;
  2517.     } else if ((fromY == 3)
  2518.            && (toX != fromX)
  2519.            && (board[fromY][fromX] == BlackPawn)
  2520.            && (board[toY][toX] == EmptySquare)) {
  2521.     *moveType = BlackCapturesEnPassant;
  2522.     board[fromY][fromX] = EmptySquare;
  2523.     board[toY][toX] = BlackPawn;
  2524.     board[toY + 1][toX] = EmptySquare;
  2525.     } else {
  2526.     *moveType = NormalMove;
  2527.     board[toY][toX] = board[fromY][fromX];
  2528.     board[fromY][fromX] = EmptySquare;
  2529.     }
  2530. }
  2531.  
  2532. /*
  2533.  * MakeMove() displays moves.  If they are illegal, GNU chess will detect
  2534.  * this and send an Illegal move message.  XBoard will then retract the move.
  2535.  *
  2536.  * Oddity: moveType is ignored on input unless the move is seen to be a
  2537.  * pawn promotion, in which case moveType tells us what to promote to.
  2538.  */
  2539. void MakeMove(moveType, fromX, fromY, toX, toY)
  2540.      ChessMove *moveType;
  2541.      int fromX, fromY, toX, toY;
  2542. {
  2543.     forwardMostMove++;
  2544.     if (commentList[forwardMostMove] != NULL) {
  2545.     free(commentList[forwardMostMove]);
  2546.     commentList[forwardMostMove] = NULL;
  2547.     }
  2548.     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
  2549.     ApplyMove(moveType, fromX, fromY, toX, toY,
  2550.           boards[forwardMostMove]);
  2551.     gameInfo.result = GameUnfinished;
  2552.     if (gameInfo.resultDetails != NULL) {
  2553.     free(gameInfo.resultDetails);
  2554.     gameInfo.resultDetails = NULL;
  2555.     }
  2556.     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
  2557.                  FakeFlags(forwardMostMove - 1), EP_UNKNOWN,
  2558.                  fromY, fromX, toY, toX,
  2559.                  ToLower(PieceToChar(PromoPiece(*moveType))),
  2560.                  parseList[forwardMostMove - 1]);
  2561.  
  2562.     if (!pausing || gameMode == PlayFromGameFile) {
  2563.     currentMove = forwardMostMove;
  2564.     }
  2565.  
  2566.     SwitchClocks();
  2567.     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
  2568.     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
  2569.  
  2570.     if (gameMode == PlayFromGameFile ?
  2571.     matchMode || (appData.timeDelay == 0 && !pausing) : pausing) return;
  2572.  
  2573.     DisplayMove(currentMove - 1);
  2574.     DrawPosition(FALSE, boards[currentMove]);
  2575. }
  2576.  
  2577. void InitChessProgram(hostName, programName, pr, isr, sendTime)
  2578.      char *hostName, *programName;
  2579.      ProcRef *pr;
  2580.      InputSourceRef *isr;
  2581.      int *sendTime;
  2582. {
  2583.     char cmdLine[MSG_SIZ];
  2584.     char buf[MSG_SIZ];
  2585.     int err;
  2586.     
  2587.     if (appData.noChessProgram) return;
  2588.     
  2589.     if (*appData.searchTime != NULLCHAR) {
  2590.     sprintf(cmdLine, "%s %d", programName, searchTime);
  2591.     } else if (appData.searchDepth > 0) {
  2592.     sprintf(cmdLine, "%s 1 9999", programName);
  2593.     } else {
  2594.     sprintf(cmdLine, "%s %d %s", programName,
  2595.         appData.movesPerSession, appData.timeControl);
  2596.     }
  2597.  
  2598.     if (strcmp(hostName, "localhost") == 0) {
  2599.     err = StartChildProcess(cmdLine, pr);
  2600.     } else if (*appData.remoteShell == NULLCHAR) {
  2601.     err = OpenRcmd(hostName, appData.remoteUser, cmdLine, pr);
  2602.     } else {
  2603.     if (*appData.remoteUser == NULLCHAR) {
  2604.         sprintf(buf, "%s %s %s", appData.remoteShell, hostName, cmdLine);
  2605.     } else {
  2606.         sprintf(buf, "%s -l %s %s %s", appData.remoteShell,
  2607.             appData.remoteUser, hostName, cmdLine);
  2608.     }
  2609.     err = StartChildProcess(buf, pr);
  2610.     }
  2611.     
  2612.     if (err != 0) {
  2613.     sprintf(buf, "Startup failure on '%s'", programName);
  2614.     DisplayFatalError(buf, err, -1); /* don't exit */
  2615.     *pr = NoProc;
  2616.     *isr = NULL;
  2617.     return;
  2618.     }
  2619.     
  2620.     *isr = AddInputSource(*pr, TRUE, ReceiveFromProgram);
  2621.  
  2622.     hintRequested = FALSE;
  2623.     bookRequested = FALSE;
  2624.     maybePondering = FALSE;
  2625.     SendToProgram(appData.initString, *pr);
  2626.     SendSearchDepth(*pr);
  2627.     if (appData.showThinking) {
  2628.     SendToProgram("post\n", *pr);
  2629.     }
  2630.     
  2631.     if (*sendTime == 2) {
  2632.     /* Does program have "time" and "otim" commands? */
  2633.     char buf[MSG_SIZ];
  2634.     sprintf(timeTestStr, "%ld", timeControl/10);
  2635.     sprintf(buf, "time %s\notim %s\nhelp\n", timeTestStr, timeTestStr);
  2636.     /* "help" is a kludge to work around a gnuchess bug;
  2637.        some versions do not send a newline at the end of
  2638.        their response to the time command */
  2639.     SendToProgram(buf, *pr);
  2640.     }
  2641. }
  2642.  
  2643.  
  2644. void GameEnds(result, resultDetails, whosays)
  2645.      ChessMove result;
  2646.      char *resultDetails;
  2647.      int whosays;
  2648. {
  2649.     char *quit = "quit\n";
  2650.     int dummy;
  2651.  
  2652.     /* If we're loading the game from a file, stop */
  2653.     (void) StopLoadGameTimer();
  2654.     if (gameFileFP != NULL) {
  2655.     gameFileFP = NULL;
  2656.     }
  2657.  
  2658.     /* If this is an ICS game, only ICS can really say it's done;
  2659.        if not, anyone can. */
  2660.     if (!((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack
  2661.        || gameMode == IcsObserving || gameMode == IcsExamining)
  2662.       && whosays != GE_ICS)) {
  2663.     StopClocks();
  2664.     
  2665.     if (resultDetails != NULL) {
  2666.         gameInfo.result = result;
  2667.         gameInfo.resultDetails = StrSave(resultDetails);
  2668.  
  2669.         if (currentMove == forwardMostMove)
  2670.           DisplayMove(currentMove - 1);
  2671.     
  2672.         if (forwardMostMove != 0) {
  2673.         if (gameMode != PlayFromGameFile && gameMode != EditGame) {
  2674.             if (*appData.saveGameFile != NULLCHAR) {
  2675.             SaveGameToFile(appData.saveGameFile);
  2676.             } else if (appData.autoSaveGames) {
  2677.             AutoSaveGame();
  2678.             }
  2679.             if (*appData.savePositionFile != NULLCHAR) {
  2680.             SavePositionToFile(appData.savePositionFile);
  2681.             }
  2682.         }
  2683.         }
  2684.     }
  2685.  
  2686.     if (appData.icsActive) {
  2687.         if (appData.quietPlay &&
  2688.         (gameMode == IcsPlayingWhite ||
  2689.          gameMode == IcsPlayingBlack)) {
  2690.         SendToICS("set shout 1\n");
  2691.         }
  2692.         gameMode = IcsIdle;
  2693.         ics_user_moved = FALSE;
  2694.     } else if (gameMode != EditGame) {
  2695.         lastGameMode = gameMode;
  2696.         gameMode = EndOfGame;
  2697.     }
  2698.     pausing = FALSE;
  2699.     ModeHighlight();
  2700.     }
  2701.  
  2702.     if (appData.noChessProgram) return;
  2703.     if (result == GameUnfinished) return;
  2704.  
  2705.     /* Kill off chess programs */
  2706.     if (firstProgramISR != NULL)
  2707.       RemoveInputSource(firstProgramISR);
  2708.     firstProgramISR = NULL;
  2709.     
  2710.     if (firstProgramPR != NoProc) {
  2711.     InterruptChildProcess(firstProgramPR);
  2712.     OutputToProcess(firstProgramPR, quit, strlen(quit), &dummy);
  2713.     DestroyChildProcess(firstProgramPR);
  2714.     }
  2715.     firstProgramPR = NoProc;
  2716.     
  2717.     if (secondProgramISR != NULL)
  2718.       RemoveInputSource(secondProgramISR);
  2719.     secondProgramISR = NULL;
  2720.     
  2721.     if (secondProgramPR != NoProc) {
  2722.     InterruptChildProcess(secondProgramPR);
  2723.     OutputToProcess(secondProgramPR, quit, strlen(quit), &dummy);
  2724.     DestroyChildProcess(secondProgramPR);
  2725.     }
  2726.     secondProgramPR = NoProc;
  2727.     
  2728.     if (matchMode) {
  2729.     exit(0);
  2730.     }
  2731. }
  2732.  
  2733.  
  2734. void ResurrectChessProgram()
  2735.      /* The chess program may have exited.
  2736.         If so, restart it and feed it all the moves made so far. */
  2737. {
  2738.     int i;
  2739.     
  2740.     if (appData.noChessProgram || firstProgramPR != NoProc) return;
  2741.     
  2742.     InitChessProgram(appData.firstHost, appData.firstChessProgram,
  2743.              &firstProgramPR, &firstProgramISR, &firstSendTime);
  2744.     SendToProgram("force\n", firstProgramPR);
  2745.     
  2746.     if (startedFromSetupPosition) {
  2747.     if (backwardMostMove % 2 == 1)
  2748.       SendToProgram("a3\n", firstProgramPR);
  2749.     SendBoard(firstProgramPR, boards[backwardMostMove]);
  2750.     }
  2751.     
  2752.     for (i = backwardMostMove; i < currentMove; i++) {
  2753.     SendToProgram(moveList[i], firstProgramPR);
  2754.     }
  2755.     
  2756.     if (!firstSendTime) {
  2757.     /* can't tell gnuchess what its clock should read,
  2758.        so we bow to its notion. */
  2759.     ResetClocks();
  2760.     timeRemaining[0][currentMove] = whiteTimeRemaining;
  2761.     timeRemaining[1][currentMove] = blackTimeRemaining;
  2762.     }
  2763. }
  2764.  
  2765. /*
  2766.  * Button procedures
  2767.  */
  2768. void Reset(redraw)
  2769.      int redraw;
  2770. {
  2771.     int i;
  2772.  
  2773.     pausing = pauseExamInvalid = FALSE;
  2774.     flipView = appData.flipView;
  2775.     startedFromSetupPosition = blackPlaysFirst = FALSE;
  2776.     firstMove = TRUE;
  2777.     whiteFlag = blackFlag = FALSE;
  2778.     maybePondering = FALSE;
  2779.     thinkOutput[0] = NULLCHAR;
  2780.     ClearGameInfo();
  2781.     ics_user_moved = FALSE;
  2782.     ics_getting_history = H_FALSE;
  2783.     ics_gamenum = -1;
  2784.     
  2785.     ResetFrontEnd();
  2786.  
  2787.     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
  2788.     lastGameMode = gameMode = BeginningOfGame;
  2789.     ModeHighlight();
  2790.     InitPosition(redraw);
  2791.     for (i = 0; i < MAX_MOVES; i++) {
  2792.     if (commentList[i] != NULL) {
  2793.         free(commentList[i]);
  2794.         commentList[i] = NULL;
  2795.     }
  2796.     }
  2797.     ResetClocks();
  2798.     timeRemaining[0][0] = whiteTimeRemaining;
  2799.     timeRemaining[1][0] = blackTimeRemaining;
  2800.     InitChessProgram(appData.firstHost, appData.firstChessProgram,
  2801.              &firstProgramPR, &firstProgramISR, &firstSendTime);
  2802.     DisplayTitle("");
  2803.     DisplayMessage("", NULL);
  2804. }
  2805.  
  2806.  
  2807. void LoadGameLoop()
  2808. {
  2809.     for (;;) {
  2810.     if (!LoadGameOneMove())
  2811.       return;
  2812.     if (matchMode || appData.timeDelay == 0)
  2813.       continue;
  2814.     if (appData.timeDelay < 0)
  2815.       return;
  2816.         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
  2817.     break;
  2818.     }
  2819. }
  2820.  
  2821. int LoadGameOneMove()
  2822. {
  2823.     int fromX = 0, fromY = 0, toX = 0, toY = 0, done, editAfterDone;
  2824.     ChessMove moveType;
  2825.     char move[MSG_SIZ];
  2826.     char *p, *q;
  2827.     
  2828.     if (gameFileFP == NULL)
  2829.       return FALSE;
  2830.     
  2831.     if (gameMode != PlayFromGameFile) {
  2832.     gameFileFP = NULL;
  2833.     return FALSE;
  2834.     }
  2835.     
  2836.     yyboardindex = forwardMostMove;
  2837.     moveType = (ChessMove) yylex();
  2838.     
  2839.     done = FALSE;
  2840.     editAfterDone = TRUE;
  2841.     switch (moveType) {
  2842.       case Comment:
  2843.     if (appData.debugMode) 
  2844.       fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
  2845.     p = yy_text;
  2846.     if (*p == '{' || *p == '[' || *p == '(') {
  2847.         p[strlen(p) - 1] = NULLCHAR;
  2848.         p++;
  2849.     }
  2850.     while (*p == '\n') p++;
  2851.     if (!matchMode && (pausing || appData.timeDelay != 0)) {
  2852.         DisplayComment(currentMove - 1, p);
  2853.     }
  2854.     AppendComment(currentMove, p);
  2855.     return TRUE;
  2856.  
  2857.       case WhiteCapturesEnPassant:
  2858.       case BlackCapturesEnPassant:
  2859.       case WhitePromotionQueen:
  2860.       case BlackPromotionQueen:
  2861.       case WhitePromotionRook:
  2862.       case BlackPromotionRook:
  2863.       case WhitePromotionBishop:
  2864.       case BlackPromotionBishop:
  2865.       case WhitePromotionKnight:
  2866.       case BlackPromotionKnight:
  2867.       case NormalMove:
  2868.       case WhiteKingSideCastle:
  2869.       case WhiteQueenSideCastle:
  2870.       case BlackKingSideCastle:
  2871.       case BlackQueenSideCastle:
  2872.       case WhiteKingSideCastleWild:
  2873.       case WhiteQueenSideCastleWild:
  2874.       case BlackKingSideCastleWild:
  2875.       case BlackQueenSideCastleWild:
  2876.     if (appData.debugMode)
  2877.       fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
  2878.     fromX = currentMoveString[0] - 'a';
  2879.     fromY = currentMoveString[1] - '1';
  2880.     toX = currentMoveString[2] - 'a';
  2881.     toY = currentMoveString[3] - '1';
  2882.     break;
  2883.  
  2884.       case WhiteWins:
  2885.       case BlackWins:
  2886.       case GameIsDrawn:
  2887.       case GameUnfinished:
  2888.     if (appData.debugMode)
  2889.       fprintf(debugFP, "Parsed game end: %s\n", yy_text);
  2890.     p = strchr(yy_text, '{');
  2891.     if (p == NULL) p = strchr(yy_text, '(');
  2892.     if (p == NULL) {
  2893.         p = yy_text;
  2894.         if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
  2895.     } else {
  2896.         q = strchr(p, *p == '{' ? '}' : ')');
  2897.         if (q != NULL) *q = NULLCHAR;
  2898.         p++;
  2899.     }
  2900.     GameEnds(moveType, p, GE_FILE);
  2901.     done = TRUE;
  2902.     editAfterDone = (moveType == GameUnfinished);
  2903.     if (cmailMsgLoaded) {
  2904.         flipView = WhiteOnMove(currentMove);
  2905.         if (moveType == GameUnfinished) flipView = !flipView;
  2906.         appData.flipView = flipView;
  2907.         if (appData.debugMode)
  2908.           fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
  2909.     }
  2910.     break;
  2911.  
  2912.       case (ChessMove) 0:  /* end of file */
  2913.     if (appData.debugMode)
  2914.       fprintf(debugFP, "Parser hit end of file\n");
  2915.     switch (MateTest(boards[currentMove], FakeFlags(currentMove),
  2916.              EP_UNKNOWN)) {
  2917.       case MT_NONE:
  2918.       case MT_CHECK:
  2919.         DisplayMessage("", "End of game file");
  2920.         break;
  2921.       case MT_CHECKMATE:
  2922.         if (WhiteOnMove(currentMove)) {
  2923.         GameEnds(BlackWins, "Black mates", GE_FILE);
  2924.         } else {
  2925.         GameEnds(WhiteWins, "White mates", GE_FILE);
  2926.         }
  2927.         editAfterDone = FALSE;
  2928.         break;
  2929.       case MT_STALEMATE:
  2930.         GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
  2931.         editAfterDone = FALSE;
  2932.         break;
  2933.     }
  2934.     done = TRUE;
  2935.     break;
  2936.  
  2937.       case XBoardGame:
  2938.       case GNUChessGame:
  2939.       case PGNTag:
  2940.     /* Reached start of next game in file */
  2941.     if (appData.debugMode)
  2942.       fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
  2943.     switch (MateTest(boards[currentMove], FakeFlags(currentMove),
  2944.              EP_UNKNOWN)) {
  2945.       case MT_NONE:
  2946.       case MT_CHECK:
  2947.         DisplayMessage("", "End of game");
  2948.         break;
  2949.       case MT_CHECKMATE:
  2950.         if (WhiteOnMove(currentMove)) {
  2951.         GameEnds(BlackWins, "Black mates", GE_FILE);
  2952.         } else {
  2953.         GameEnds(WhiteWins, "White mates", GE_FILE);
  2954.         }
  2955.         editAfterDone = FALSE;
  2956.         break;
  2957.       case MT_STALEMATE:
  2958.         GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
  2959.         editAfterDone = FALSE;
  2960.         break;
  2961.     }
  2962.     done = TRUE;
  2963.     break;
  2964.  
  2965.       case PositionDiagram:
  2966.     /* should not happen; ignore */
  2967.       case MoveNumberOne:
  2968.     /* ignore; we see too many bogus move numbers,
  2969.        especially in GNUChess game files */
  2970.       case ElapsedTime:
  2971.     /* ignore */
  2972.     if (appData.debugMode)
  2973.       fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
  2974.           yy_text, (int) moveType);
  2975.     return LoadGameOneMove(); /* tail recursion */
  2976.  
  2977.       default:
  2978.       case BadMove:
  2979.     if (appData.debugMode)
  2980.       fprintf(debugFP, "Parsed BadMove: %s\n", yy_text);
  2981.     sprintf(move, "Bad move: %d. %s%s",
  2982.         (forwardMostMove / 2) + 1,
  2983.         WhiteOnMove(forwardMostMove) ? "" : "... ", yy_text);
  2984.     DisplayError(move, 0);
  2985.     done = TRUE;
  2986.     break;
  2987.  
  2988.       case AmbiguousMove:
  2989.     if (appData.debugMode)
  2990.       fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
  2991.     sprintf(move, "Ambiguous move: %d. %s%s",
  2992.         (forwardMostMove / 2) + 1,
  2993.         WhiteOnMove(forwardMostMove) ? "" : "... ", yy_text);
  2994.     DisplayError(move, 0);
  2995.     done = TRUE;
  2996.     break;
  2997.     }
  2998.     
  2999.     if (done) {
  3000.     if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
  3001.         DrawPosition(FALSE, boards[currentMove]);
  3002.         DisplayBothClocks();
  3003.         if (!appData.matchMode && commentList[currentMove] != NULL)
  3004.           DisplayComment(currentMove - 1, commentList[currentMove]);
  3005.     }
  3006.     if (editAfterDone) {
  3007.         lastGameMode = gameMode;
  3008.         gameMode = EditGame;
  3009.         ModeHighlight();
  3010.     }
  3011.         (void) StopLoadGameTimer();
  3012.     gameFileFP = NULL;
  3013.     cmailOldMove = forwardMostMove;
  3014.     return FALSE;
  3015.     } else {
  3016.     strcat(currentMoveString, "\n");
  3017.     strcpy(moveList[forwardMostMove], currentMoveString);
  3018.     SendToProgram(currentMoveString, firstProgramPR);
  3019.     
  3020.     thinkOutput[0] = NULLCHAR;
  3021.     MakeMove(&moveType, fromX, fromY, toX, toY);
  3022.     
  3023.     return TRUE;
  3024.     }
  3025. }
  3026.  
  3027. /* Load the nth game from the given file */
  3028. int LoadGameFromFile(filename, n, title)
  3029.      char *filename;
  3030.      int n;
  3031.      char *title;
  3032. {
  3033.     FILE *f;
  3034.     char buf[MSG_SIZ];
  3035.  
  3036.     if (strcmp(filename, "-") == 0) {
  3037.     return LoadGame(stdin, n, "stdin");
  3038.     } else {
  3039.     f = fopen(filename, "r");
  3040.     if (f == NULL) {
  3041.         sprintf(buf, "Can't open \"%s\"", filename);
  3042.         DisplayError(buf, errno);
  3043.         return FALSE;
  3044.     } else {
  3045.         return LoadGame(f, n, title);
  3046.     }
  3047.     }
  3048. }
  3049.  
  3050.  
  3051. void MakeRegisteredMove()
  3052. {
  3053.     ChessMove moveType;
  3054.     
  3055.     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
  3056.         switch (cmailMoveType[lastLoadGameNumber - 1]) {
  3057.           case CMAIL_MOVE:
  3058.           case CMAIL_DRAW:
  3059.           strcpy(moveList[currentMove - 1],
  3060.              cmailMove[lastLoadGameNumber - 1]);
  3061.     
  3062.           moveType =
  3063.           PromoCharToMoveType(WhiteOnMove(currentMove),
  3064.                       cmailMove[lastLoadGameNumber - 1][4]);
  3065.           thinkOutput[0] = NULLCHAR;
  3066.           MakeMove(&moveType,
  3067.                cmailMove[lastLoadGameNumber - 1][0] - 'a',
  3068.                cmailMove[lastLoadGameNumber - 1][1] - '1',
  3069.                cmailMove[lastLoadGameNumber - 1][2] - 'a',
  3070.                cmailMove[lastLoadGameNumber - 1][3] - '1');
  3071.           
  3072.           switch (MateTest(boards[currentMove], FakeFlags(currentMove),
  3073.                    EP_UNKNOWN)) {
  3074.         case MT_NONE:
  3075.         case MT_CHECK:
  3076.           break;
  3077.             
  3078.         case MT_CHECKMATE:
  3079.           if (WhiteOnMove(currentMove)) {
  3080.               GameEnds(BlackWins, "Black mates", GE_PLAYER);
  3081.           } else {
  3082.               GameEnds(WhiteWins, "White mates", GE_PLAYER);
  3083.           }
  3084.           break;
  3085.             
  3086.         case MT_STALEMATE:
  3087.           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
  3088.           break;
  3089.           }
  3090.  
  3091.           break;
  3092.         
  3093.             case CMAIL_RESIGN:
  3094.           if (WhiteOnMove(currentMove)) {
  3095.           GameEnds(BlackWins, "White resigns", GE_PLAYER);
  3096.           } else {
  3097.           GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
  3098.           }
  3099.           break;
  3100.         
  3101.             case CMAIL_ACCEPT:
  3102.           GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
  3103.           break;
  3104.           
  3105.             default:
  3106.           break;
  3107.     }
  3108.     }
  3109.  
  3110.     return;
  3111. }
  3112.  
  3113. /* If the most recently loaded game was the nth game in file f,
  3114.    load the (n + offset)th game from file f.  The open file handle
  3115.    is cached; it is not reopened by name. */
  3116. int ReloadGame(offset)
  3117.      int offset;
  3118. {
  3119.     int retVal;
  3120.     
  3121.     if (lastLoadGameFP == NULL) {
  3122.     DisplayError("No game has been loaded", 0);
  3123.     return FALSE;
  3124.     }
  3125.     if (lastLoadGameNumber + offset <= 0) {
  3126.     DisplayError("Can't back up any further", 0);
  3127.     return FALSE;
  3128.     }
  3129.  
  3130.     if (cmailMsgLoaded) {
  3131.     if (lastLoadGameNumber + offset > nCmailGames) {
  3132.         DisplayError("No more games in this message", 0);
  3133.         return FALSE;
  3134.     }
  3135.     if (offset == 0) {
  3136.         cmailMsg[0] = NULLCHAR;
  3137.         if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
  3138.         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
  3139.         nCmailMovesRegistered --;
  3140.         }
  3141.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
  3142.         if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
  3143.         cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
  3144.         }
  3145.     } else {
  3146.         if (! RegisterMove()) return FALSE;
  3147.     }
  3148.     }
  3149.     
  3150.     rewind(lastLoadGameFP);
  3151.     retVal = LoadGame(lastLoadGameFP, lastLoadGameNumber + offset,
  3152.               lastLoadGameTitle);
  3153.  
  3154.     if ((cmailMsgLoaded) && (offset != 0)) {
  3155.     /* Make move registered during previous look at this game        */
  3156.     MakeRegisteredMove();
  3157.  
  3158.     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
  3159.         commentList[currentMove]
  3160.           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
  3161.         DisplayComment(currentMove - 1, commentList[currentMove]);
  3162.     }
  3163.     }
  3164.     
  3165.     return retVal;
  3166. }
  3167.  
  3168. /* Load the nth game from open file f, and cache the open file handle
  3169.    for possible later use by ReloadGame */
  3170. int LoadGame(f, gameNumber, title)
  3171.      FILE *f;
  3172.      int gameNumber;
  3173.      char *title;
  3174. {
  3175.     ChessMove cm, lastStart;
  3176.     char buf[MSG_SIZ];
  3177.  
  3178.     if (gameMode != BeginningOfGame) {
  3179.     Reset(FALSE);
  3180.     }
  3181.  
  3182.     gameFileFP = f;
  3183.     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
  3184.     fclose(lastLoadGameFP);
  3185.     }
  3186.     lastLoadGameFP = f;
  3187.     if (gameNumber > 0)
  3188.       lastLoadGameNumber = gameNumber;
  3189.     else
  3190.       lastLoadGameNumber = 1;
  3191.     strcpy(lastLoadGameTitle, title);
  3192.  
  3193.     /* Negative game number means to seek to that byte position
  3194.        and load the next game found. */
  3195.     if (gameNumber < 0) {
  3196.     fseek(f, -gameNumber, 0);
  3197.     gameNumber = 1;  /* do look for start marker, e.g. PGN tags */
  3198.     }
  3199.     yynewfile(f);
  3200.  
  3201.     if (gameNumber > 1) {
  3202.     sprintf(buf, "%s %d", title, gameNumber);
  3203.     DisplayTitle(buf);
  3204.     } else {
  3205.     DisplayTitle(title);
  3206.     }
  3207.     lastGameMode = gameMode = PlayFromGameFile;
  3208.     ModeHighlight();
  3209.     InitPosition(FALSE);
  3210.     StopClocks();
  3211.     if (firstProgramPR == NoProc) {
  3212.     InitChessProgram(appData.firstHost, appData.firstChessProgram,
  3213.              &firstProgramPR, &firstProgramISR, &firstSendTime);
  3214.     } else {
  3215.     SendToProgram(appData.initString, firstProgramPR);
  3216.     SendSearchDepth(firstProgramPR);
  3217.     }
  3218.     SendToProgram("force\n", firstProgramPR);
  3219.     
  3220.     /*
  3221.      * Skip the first gameNumber-1 games in the file.
  3222.      * Also skip over anything that precedes an identifiable 
  3223.      * start of game marker, to avoid being confused by 
  3224.      * garbage at the start of the file.  Currently 
  3225.      * recognized start of game markers are the move number "1",
  3226.      * the pattern "gnuchess .* game", the pattern
  3227.      * "^[#;%] [^ ]* game file", and a PGN tag block.  
  3228.      * A game that starts with one of the latter two patterns
  3229.      * will also have a move number 1, possibly
  3230.      * following a position diagram.
  3231.      * If gameNumber == 0, don't scan for a start marker or
  3232.      * parse PGN tags; just start parsing moves right away.
  3233.      */
  3234.     lastStart = (ChessMove) 0;
  3235.     cm = MoveNumberOne;  /* in case gameNumber == 0 already */
  3236.     while (gameNumber > 0) {
  3237.     yyboardindex = forwardMostMove;
  3238.     cm = (ChessMove) yylex();
  3239.     switch (cm) {
  3240.       case (ChessMove) 0:
  3241.         if (cmailMsgLoaded) {
  3242.         nCmailGames = CMAIL_MAX_GAMES - gameNumber;
  3243.         } else {
  3244.         Reset(TRUE);
  3245.         DisplayError("Game not found in file", 0);
  3246.         }
  3247.         
  3248.         return FALSE;
  3249.  
  3250.       case GNUChessGame:
  3251.       case XBoardGame:
  3252.         gameNumber--;
  3253.         lastStart = cm;
  3254.         break;
  3255.         
  3256.       case MoveNumberOne:
  3257.         switch (lastStart) {
  3258.           case GNUChessGame:
  3259.         break;  /* ignore */
  3260.           case XBoardGame:
  3261.           case PGNTag:
  3262.         lastStart = cm;  /* game counted already */
  3263.         break;
  3264.           case MoveNumberOne:
  3265.           case (ChessMove) 0:
  3266.         gameNumber--;   /* count this game */
  3267.         lastStart = cm;
  3268.         break;
  3269.           default:
  3270.         /* impossible */
  3271.         break;
  3272.         }
  3273.         break;
  3274.  
  3275.       case PGNTag:
  3276.         switch (lastStart) {
  3277.           case GNUChessGame:
  3278.           case PGNTag:
  3279.           case MoveNumberOne:
  3280.           case (ChessMove) 0:
  3281.         gameNumber--;   /* count this game */
  3282.         lastStart = cm;
  3283.         break;
  3284.           case XBoardGame:
  3285.         lastStart = cm;  /* game counted already */
  3286.         break;
  3287.           default:
  3288.         /* impossible */
  3289.         break;
  3290.         }
  3291.         if (gameNumber > 0) {
  3292.         do {
  3293.             yyboardindex = forwardMostMove;
  3294.             cm = (ChessMove) yylex();
  3295.         } while (cm == PGNTag || cm == Comment);
  3296.         }
  3297.         break;
  3298.  
  3299.       case WhiteWins:
  3300.       case BlackWins:
  3301.       case GameIsDrawn:
  3302.          if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
  3303.         if (   cmailResult[CMAIL_MAX_GAMES - gameNumber - 1]
  3304.             != CMAIL_OLD_RESULT) {
  3305.             nCmailResults ++ ;
  3306.             cmailResult[  CMAIL_MAX_GAMES
  3307.                 - gameNumber - 1] = CMAIL_OLD_RESULT;
  3308.         }
  3309.         }
  3310.         break;
  3311.  
  3312.       default:
  3313.         break;
  3314.     }
  3315.     }
  3316.     
  3317.     if (appData.debugMode)
  3318.       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
  3319.  
  3320.     if (cm == XBoardGame) {
  3321.     /* Skip any header junk before position diagram and/or move 1 */
  3322.     for (;;) {
  3323.         yyboardindex = forwardMostMove;
  3324.         cm = (ChessMove) yylex();
  3325.  
  3326.         if (cm == (ChessMove) 0 ||
  3327.         cm == GNUChessGame || cm == XBoardGame) {
  3328.         /* Empty game; pretend end-of-file and handle later */
  3329.         cm = (ChessMove) 0;
  3330.         break;
  3331.         }
  3332.  
  3333.         if (cm == MoveNumberOne || cm == PositionDiagram ||
  3334.         cm == PGNTag || cm == Comment)
  3335.           break;
  3336.     }
  3337.     }
  3338.  
  3339.     while (cm == PGNTag) {
  3340.     if (appData.debugMode) 
  3341.       fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
  3342.     ParsePGNTag(yy_text);
  3343.     yyboardindex = forwardMostMove;
  3344.     cm = (ChessMove) yylex();
  3345.     /* Handle comments interspersed among the tags */
  3346.     while (cm == Comment) {
  3347.         char *p;
  3348.         if (appData.debugMode) 
  3349.           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
  3350.         p = yy_text;
  3351.         if (*p == '{' || *p == '[' || *p == '(') {
  3352.         p[strlen(p) - 1] = NULLCHAR;
  3353.         p++;
  3354.         }
  3355.         while (*p == '\n') p++;
  3356.         AppendComment(currentMove, p);
  3357.         yyboardindex = forwardMostMove;
  3358.         cm = (ChessMove) yylex();
  3359.     }
  3360.     }
  3361.  
  3362.     if (gameInfo.event != NULL) {
  3363.     char *tags = PGNTags();
  3364.     if (cmailMsgLoaded) {
  3365.         tags = AppendCmailMsg(tags);
  3366.     }
  3367.     DisplayInformation(tags);
  3368.     free(tags);
  3369.     } else {
  3370.     /* Make something up, but don't display it now */
  3371.     SetGameInfo();
  3372.     }
  3373.  
  3374.     if (cm == PositionDiagram) {
  3375.     int i, j;
  3376.     char *p;
  3377.     Board initial_position;
  3378.  
  3379.     if (appData.debugMode)
  3380.           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
  3381.  
  3382.     if (gameInfo.fen == NULL) {
  3383.         p = yy_text;
  3384.         for (i = BOARD_SIZE - 1; i >= 0; i--)
  3385.           for (j = 0; j < BOARD_SIZE; p++)
  3386.         switch (*p) {
  3387.           case '[':
  3388.           case '-':
  3389.           case ' ':
  3390.           case '\t':
  3391.           case '\n':
  3392.           case '\r':
  3393.             break;
  3394.           default:
  3395.             initial_position[i][j++] = CharToPiece(*p);
  3396.             break;
  3397.         }
  3398.         while (*p == ' ' || *p == '\t' ||
  3399.            *p == '\n' || *p == '\r') p++;
  3400.     
  3401.         if (strncmp(p, "black", strlen("black"))==0)
  3402.           blackPlaysFirst = TRUE;
  3403.         else
  3404.           blackPlaysFirst = FALSE;
  3405.         startedFromSetupPosition = TRUE;
  3406.     
  3407.         if (blackPlaysFirst) {
  3408.         currentMove = forwardMostMove = backwardMostMove = 1;
  3409.         CopyBoard(boards[0], initial_position);
  3410.         CopyBoard(boards[1], initial_position);
  3411.         strcpy(moveList[0], "");
  3412.         strcpy(parseList[0], "");
  3413.         timeRemaining[0][1] = whiteTimeRemaining;
  3414.         timeRemaining[1][1] = blackTimeRemaining;
  3415.         SendToProgram("a3\n", firstProgramPR);
  3416.         SendCurrentBoard(firstProgramPR);
  3417.         if (commentList[0] != NULL) {
  3418.             commentList[1] = commentList[0];
  3419.             commentList[0] = NULL;
  3420.         }
  3421.         } else {
  3422.         currentMove = forwardMostMove = backwardMostMove = 0;
  3423.         CopyBoard(boards[0], initial_position);
  3424.         SendCurrentBoard(firstProgramPR);
  3425.         }
  3426.     }
  3427.     yyboardindex = forwardMostMove;
  3428.     cm = (ChessMove) yylex();
  3429.     }
  3430.  
  3431.     if (gameInfo.fen != NULL) {
  3432.     Board initial_position;
  3433.     startedFromSetupPosition = TRUE;
  3434.     if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
  3435.         Reset(TRUE);
  3436.         DisplayError("Bad FEN position in file", 0);
  3437.         return FALSE;
  3438.     }
  3439.     if (blackPlaysFirst) {
  3440.         currentMove = forwardMostMove = backwardMostMove = 1;
  3441.         CopyBoard(boards[0], initial_position);
  3442.         CopyBoard(boards[1], initial_position);
  3443.         strcpy(moveList[0], "");
  3444.         strcpy(parseList[0], "");
  3445.         timeRemaining[0][1] = whiteTimeRemaining;
  3446.         timeRemaining[1][1] = blackTimeRemaining;
  3447.         SendToProgram("a3\n", firstProgramPR);
  3448.         SendCurrentBoard(firstProgramPR);
  3449.         if (commentList[0] != NULL) {
  3450.         commentList[1] = commentList[0];
  3451.         commentList[0] = NULL;
  3452.         }
  3453.     } else {
  3454.         currentMove = forwardMostMove = backwardMostMove = 0;
  3455.         CopyBoard(boards[0], initial_position);
  3456.         SendCurrentBoard(firstProgramPR);
  3457.     }
  3458.     free(gameInfo.fen);
  3459.     gameInfo.fen = NULL;
  3460.     }
  3461.  
  3462.     while (cm == Comment) {
  3463.     char *p;
  3464.     if (appData.debugMode) 
  3465.       fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
  3466.     p = yy_text;
  3467.     if (*p == '{' || *p == '[' || *p == '(') {
  3468.         p[strlen(p) - 1] = NULLCHAR;
  3469.         p++;
  3470.     }
  3471.     while (*p == '\n') p++;
  3472.     AppendComment(currentMove, p);
  3473.     yyboardindex = forwardMostMove;
  3474.     cm = (ChessMove) yylex();
  3475.     }
  3476.  
  3477.     if (cm == (ChessMove) 0 || cm == WhiteWins || cm == BlackWins ||
  3478.     cm == GameIsDrawn || cm == GameUnfinished) {
  3479.     DisplayMessage("", "No moves in game");
  3480.     if (cmailMsgLoaded) {
  3481.         if (appData.debugMode)
  3482.           fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
  3483.         flipView = appData.flipView = FALSE;
  3484.     }
  3485.     DrawPosition(FALSE, boards[currentMove]);
  3486.     DisplayBothClocks();
  3487.     lastGameMode = gameMode;
  3488.     gameMode = EditGame;
  3489.     ModeHighlight();
  3490.     gameFileFP = NULL;
  3491.     cmailOldMove = 0;
  3492.     return TRUE;
  3493.     }
  3494.  
  3495.     if (cm != MoveNumberOne && cm != GNUChessGame) {
  3496.     char buf[MSG_SIZ];
  3497.     Reset(TRUE);
  3498.     sprintf(buf, "Unexpected token in game: '%s' (%d)", yy_text, (int) cm);
  3499.     DisplayError(buf, 0);
  3500.     return FALSE;
  3501.     }
  3502.  
  3503.     if (commentList[currentMove] != NULL) {
  3504.     if (!matchMode && (pausing || appData.timeDelay != 0)) {
  3505.         DisplayComment(currentMove - 1, commentList[currentMove]);
  3506.     }
  3507.     if (!matchMode && appData.timeDelay != 0) {
  3508.         DrawPosition(FALSE, boards[currentMove]);
  3509.         if (appData.timeDelay > 0)
  3510.           StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
  3511.         return TRUE;
  3512.     }
  3513.     }
  3514.     if (!matchMode && appData.timeDelay != 0)
  3515.       DrawPosition(FALSE, boards[currentMove]);
  3516.  
  3517.     LoadGameLoop();
  3518.     
  3519.     return TRUE;
  3520. }
  3521.  
  3522. /* Load the nth position from the given file */
  3523. int LoadPositionFromFile(filename, n, title)
  3524.      char *filename;
  3525.      int n;
  3526.      char *title;
  3527. {
  3528.     FILE *f;
  3529.     char buf[MSG_SIZ];
  3530.  
  3531.     if (strcmp(filename, "-") == 0) {
  3532.     return LoadPosition(stdin, n, "stdin");
  3533.     } else {
  3534.     f = fopen(filename, "r");
  3535.     if (f == NULL) {
  3536.         sprintf(buf, "Can't open \"%s\"", filename);
  3537.         DisplayError(buf, errno);
  3538.         return FALSE;
  3539.     } else {
  3540.         return LoadPosition(f, n, title);
  3541.     }
  3542.     }
  3543. }
  3544.  
  3545. /* Load the nth position from the given open file, and close it */
  3546. int LoadPosition(fp, positionNumber, title)
  3547.      FILE *fp;
  3548.      int positionNumber;
  3549.      char *title;
  3550. {
  3551.     char *p, line[MSG_SIZ];
  3552.     Board initial_position;
  3553.     int i, j, fenMode;
  3554.     
  3555.     if (gameMode != BeginningOfGame) {
  3556.     Reset(TRUE);
  3557.     }
  3558.  
  3559.     if (positionNumber > 1) {
  3560.     sprintf(line, "%s %d", title, positionNumber);
  3561.     DisplayTitle(line);
  3562.     } else {
  3563.     DisplayTitle(title);
  3564.     }
  3565.     lastGameMode = gameMode = EditGame;
  3566.     ModeHighlight();
  3567.     startedFromSetupPosition = TRUE;
  3568.     
  3569.     if (firstProgramPR == NoProc)
  3570.       InitChessProgram(appData.firstHost, appData.firstChessProgram,
  3571.                &firstProgramPR, &firstProgramISR, &firstSendTime);
  3572.     
  3573.     /* Negative position number means to seek to that byte offset */
  3574.     if (positionNumber < 0) {
  3575.     fseek(fp, -positionNumber, 0);
  3576.     positionNumber = 1;
  3577.     }
  3578.     /* See if this file is FEN or old-style xboard */
  3579.     if (fgets(line, MSG_SIZ, fp) == NULL) {
  3580.     Reset(TRUE);
  3581.     DisplayError("Position not found in file", 0);
  3582.     return FALSE;
  3583.     }
  3584.     switch (line[0]) {
  3585.       case '#':  case 'x':
  3586.       default:
  3587.     fenMode = FALSE;
  3588.     break;
  3589.       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
  3590.       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
  3591.       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
  3592.       case '7':  case '8':
  3593.     fenMode = TRUE;
  3594.     break;
  3595.     }
  3596.  
  3597.     if (positionNumber >= 2  /* don't look for '#' if positionNumber == 1 */) {
  3598.     if (fenMode || line[0] == '#') positionNumber--;
  3599.     while (positionNumber > 0) {
  3600.         /* skip postions before number positionNumber */
  3601.         if (fgets(line, MSG_SIZ, fp) == NULL) {
  3602.         Reset(TRUE);
  3603.         DisplayError("Position not found in file", 0);
  3604.         return FALSE;
  3605.         }
  3606.         if (fenMode || line[0] == '#') positionNumber--;
  3607.     }
  3608.     }
  3609.  
  3610.     if (fenMode) {
  3611.     if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
  3612.         Reset(TRUE);
  3613.         DisplayError("Bad FEN position in file", 0);
  3614.         return FALSE;
  3615.     }
  3616.     } else {
  3617.     (void) fgets(line, MSG_SIZ, fp);
  3618.     (void) fgets(line, MSG_SIZ, fp);
  3619.     
  3620.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  3621.         (void) fgets(line, MSG_SIZ, fp);
  3622.         for (p = line, j = 0; j < BOARD_SIZE; p++) {
  3623.         if (*p == ' ')
  3624.           continue;
  3625.         initial_position[i][j++] = CharToPiece(*p);
  3626.         }
  3627.     }
  3628.     
  3629.     blackPlaysFirst = FALSE;
  3630.     if (!feof(fp)) {
  3631.         (void) fgets(line, MSG_SIZ, fp);
  3632.         if (strncmp(line, "black", strlen("black"))==0)
  3633.           blackPlaysFirst = TRUE;
  3634.     }
  3635.     }
  3636.     fclose(fp);
  3637.     
  3638.     if (blackPlaysFirst) {
  3639.     CopyBoard(boards[0], initial_position);
  3640.     strcpy(moveList[0], "");
  3641.     strcpy(parseList[0], "");
  3642.     currentMove = forwardMostMove = backwardMostMove = 1;
  3643.     CopyBoard(boards[1], initial_position);
  3644.     SendToProgram("force\na3\n", firstProgramPR);
  3645.     SendCurrentBoard(firstProgramPR);
  3646.     DisplayMessage("", "Black to play");
  3647.     } else {
  3648.     currentMove = forwardMostMove = backwardMostMove = 0;
  3649.     CopyBoard(boards[0], initial_position);
  3650.     SendCurrentBoard(firstProgramPR);
  3651.     SendToProgram("force\n", firstProgramPR);
  3652.     DisplayMessage("", "White to play");
  3653.     }
  3654.     
  3655.     ResetClocks();
  3656.     timeRemaining[0][1] = whiteTimeRemaining;
  3657.     timeRemaining[1][1] = blackTimeRemaining;
  3658.  
  3659.     DrawPosition(FALSE, boards[currentMove]);
  3660.     
  3661.     return TRUE;
  3662. }
  3663.  
  3664.  
  3665. void CopyPlayerNameIntoFileName(dest, src)
  3666.      char **dest, *src;
  3667. {
  3668.     while (*src != NULLCHAR && *src != ',') {
  3669.     if (*src == ' ') {
  3670.         *(*dest)++ = '_';
  3671.         src++;
  3672.     } else {
  3673.         *(*dest)++ = *src++;
  3674.     }
  3675.     }
  3676. }
  3677.  
  3678. char *DefaultFileName(ext)
  3679.      char *ext;
  3680. {
  3681.     static char def[MSG_SIZ];
  3682.     char *p;
  3683.  
  3684.     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
  3685.     p = def;
  3686.     CopyPlayerNameIntoFileName(&p, gameInfo.white);
  3687.     *p++ = '-';
  3688.     CopyPlayerNameIntoFileName(&p, gameInfo.black);
  3689.     *p++ = '.';
  3690.     strcpy(p, ext);
  3691.     } else {
  3692.     def[0] = NULLCHAR;
  3693.     }
  3694.     return def;
  3695. }
  3696.  
  3697. /* Save the current game to the given file */
  3698. int SaveGameToFile(filename)
  3699.      char *filename;
  3700. {
  3701.     FILE *f;
  3702.     char buf[MSG_SIZ];
  3703.  
  3704.     if (strcmp(filename, "-") == 0) {
  3705.     return SaveGame(toUserFP, 0, NULL);
  3706.     } else {
  3707.     f = fopen(filename, "a");
  3708.     if (f == NULL) {
  3709.         sprintf(buf, "Can't open \"%s\"", filename);
  3710.         DisplayError(buf, errno);
  3711.         return FALSE;
  3712.     } else {
  3713.         return SaveGame(f, 0, NULL);
  3714.     }
  3715.     }
  3716. }
  3717.  
  3718. char *SavePart(str)
  3719.      char *str;
  3720. {
  3721.     static char buf[MSG_SIZ];
  3722.     char *p;
  3723.     
  3724.     p = strchr(str, ' ');
  3725.     if (p == NULL) return str;
  3726.     strncpy(buf, str, p - str);
  3727.     buf[p - str] = NULLCHAR;
  3728.     return buf;
  3729. }
  3730.  
  3731. #define PGN_MAX_LINE 75
  3732.  
  3733. int SaveGamePGN(f)
  3734.      FILE *f;
  3735. {
  3736.     int i, offset, linelen, newblock;
  3737.     time_t tm;
  3738.     char *movetext;
  3739.     char numtext[32];
  3740.     int movelen, numlen, blank;
  3741.     
  3742.     tm = time((time_t *) NULL);
  3743.     
  3744.     PrintPGNTags(f);
  3745.     
  3746.     if (backwardMostMove > 0 || startedFromSetupPosition) {
  3747.     fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n",
  3748.         PositionToFEN(backwardMostMove));
  3749.     fprintf(f, "\n{--------------\n");
  3750.     PrintPosition(f, backwardMostMove);
  3751.     fprintf(f, "--------------}\n");
  3752.     } else {
  3753.     fprintf(f, "\n");
  3754.     }
  3755.  
  3756.     i = backwardMostMove;
  3757.     offset = backwardMostMove & (~1L);  /* output move numbers start at 1 */
  3758.     linelen = 0;
  3759.     newblock = TRUE;
  3760.  
  3761.     while (i < forwardMostMove) {
  3762.     /* Print comments preceding this move */
  3763.     if (commentList[i] != NULL) {
  3764.         if (linelen > 0) fprintf(f, "\n");
  3765.         fprintf(f, "{\n%s}\n", commentList[i]);
  3766.         linelen = 0;
  3767.         newblock = TRUE;
  3768.     }
  3769.  
  3770.     /* Format move number */
  3771.         if ((i % 2) == 0) {
  3772.         sprintf(numtext, "%d.", (i - offset)/2 + 1);
  3773.     } else {
  3774.         if (newblock) {
  3775.         sprintf(numtext, "%d...", (i - offset)/2 + 1);
  3776.         } else {
  3777.         numtext[0] = NULLCHAR;
  3778.         }
  3779.     }
  3780.     numlen = strlen(numtext);
  3781.     newblock = FALSE;
  3782.  
  3783.     /* Print move number */
  3784.     blank = linelen > 0 && numlen > 0;
  3785.     if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
  3786.         fprintf(f, "\n");
  3787.         linelen = 0;
  3788.         blank = 0;
  3789.     }
  3790.     if (blank) {
  3791.         fprintf(f, " ");
  3792.         linelen++;
  3793.     }
  3794.     fprintf(f, numtext);
  3795.     linelen += numlen;
  3796.  
  3797.     /* Get move */
  3798.         movetext = SavePart(parseList[i]);
  3799.     movelen = strlen(movetext);
  3800.  
  3801.     /* Print move */
  3802.     blank = linelen > 0 && movelen > 0;
  3803.     if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
  3804.         fprintf(f, "\n");
  3805.         linelen = 0;
  3806.         blank = 0;
  3807.     }
  3808.     if (blank) {
  3809.         fprintf(f, " ");
  3810.         linelen++;
  3811.     }
  3812.     fprintf(f, movetext);
  3813.     linelen += movelen;
  3814.  
  3815.     i++;
  3816.     }
  3817.     
  3818.     /* Start a new line */
  3819.     if (linelen > 0) fprintf(f, "\n");
  3820.  
  3821.     /* Print comments after last move */
  3822.     if (commentList[i] != NULL) {
  3823.     fprintf(f, "{\n%s}\n", commentList[i]);
  3824.     }
  3825.  
  3826.     /* Print result */
  3827.     if (gameInfo.resultDetails != NULL &&
  3828.     gameInfo.resultDetails[0] != NULLCHAR) {
  3829.     fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
  3830.         PGNResult(gameInfo.result));
  3831.     } else {
  3832.     fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
  3833.     }
  3834.  
  3835.     fclose(f);
  3836.     return TRUE;
  3837. }
  3838.  
  3839. int SaveGameOldStyle(f)
  3840.      FILE *f;
  3841. {
  3842.     int i, offset;
  3843.     time_t tm;
  3844.     extern char *programName;
  3845.     
  3846.     tm = time((time_t *) NULL);
  3847.     
  3848.     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
  3849.     PrintOpponents(f);
  3850.     
  3851.     if (backwardMostMove > 0 || startedFromSetupPosition) {
  3852.     fprintf(f, "\n[--------------\n");
  3853.     PrintPosition(f, backwardMostMove);
  3854.     fprintf(f, "--------------]\n");
  3855.     } else {
  3856.     fprintf(f, "\n");
  3857.     }
  3858.  
  3859.     i = backwardMostMove;
  3860.     offset = backwardMostMove & (~1L);  /* output move numbers start at 1 */
  3861.  
  3862.     while (i < forwardMostMove) {
  3863.     if (commentList[i] != NULL) {
  3864.         fprintf(f, "[%s]\n", commentList[i]);
  3865.     }
  3866.  
  3867.     if ((i % 2) == 1) {
  3868.         fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
  3869.         i++;
  3870.     } else {
  3871.         fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
  3872.         i++;
  3873.         if (commentList[i] != NULL) {
  3874.         fprintf(f, "\n");
  3875.         continue;
  3876.         }
  3877.         if (i >= forwardMostMove) {
  3878.         fprintf(f, "\n");
  3879.         break;
  3880.         }
  3881.         fprintf(f, "%s\n", parseList[i]);
  3882.         i++;
  3883.     }
  3884.     }
  3885.     
  3886.     if (commentList[i] != NULL) {
  3887.     fprintf(f, "[%s]\n", commentList[i]);
  3888.     }
  3889.  
  3890.     /* This isn't really the old style, but it's close enough */
  3891.     if (gameInfo.resultDetails != NULL &&
  3892.     gameInfo.resultDetails[0] != NULLCHAR) {
  3893.     fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
  3894.         gameInfo.resultDetails);
  3895.     } else {
  3896.     fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
  3897.     }
  3898.  
  3899.     fclose(f);
  3900.     return TRUE;
  3901. }
  3902.  
  3903. /* Save the current game to open file f and close the file */
  3904. int SaveGame(f, dummy, dummy2)
  3905.      FILE *f;
  3906.      int dummy;
  3907.      char *dummy2;
  3908. {
  3909.     if (appData.oldSaveStyle)
  3910.       return SaveGameOldStyle(f);
  3911.     else
  3912.       return SaveGamePGN(f);
  3913. }
  3914.  
  3915. /* Save the current position to the given file */
  3916. int SavePositionToFile(filename)
  3917.      char *filename;
  3918. {
  3919.     FILE *f;
  3920.     char buf[MSG_SIZ];
  3921.  
  3922.     if (strcmp(filename, "-") == 0) {
  3923.     return SavePosition(toUserFP, 0, NULL);
  3924.     } else {
  3925.     f = fopen(filename, "a");
  3926.     if (f == NULL) {
  3927.         sprintf(buf, "Can't open \"%s\"", filename);
  3928.         DisplayError(buf, errno);
  3929.         return FALSE;
  3930.     } else {
  3931.         SavePosition(f, 0, NULL);
  3932.         return TRUE;
  3933.     }
  3934.     }
  3935. }
  3936.  
  3937. /* Save the current position to the given open file and close the file */
  3938. int SavePosition(f, dummy, dummy2)
  3939.      FILE *f;
  3940.      int dummy;
  3941.      char *dummy2;
  3942. {
  3943.     time_t tm;
  3944.     extern char *programName;
  3945.     char *fen;
  3946.     
  3947.     if (appData.oldSaveStyle) {
  3948.     tm = time((time_t *) NULL);
  3949.     
  3950.     fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
  3951.     PrintOpponents(f);
  3952.     fprintf(f, "[--------------\n");
  3953.     PrintPosition(f, currentMove);
  3954.     fprintf(f, "--------------]\n");
  3955.     } else {
  3956.     fen = PositionToFEN(currentMove);
  3957.     fprintf(f, "%s\n", fen);
  3958.     free(fen);
  3959.     }
  3960.     fclose(f);
  3961.     return TRUE;
  3962. }
  3963.  
  3964. void ReloadCmailGameEvent(unregister)
  3965.      int unregister;
  3966. {
  3967.     static char *inFilename = NULL;
  3968.     static char *outFilename;
  3969.     int i;
  3970.     struct stat inbuf, outbuf;
  3971.     int status;
  3972.     
  3973.     Reset(TRUE);
  3974.  
  3975.     /* Any registered moves are unregistered if unregister is set,       */
  3976.     /* i.e. invoked by the signal handler                                */
  3977.     if (unregister) {
  3978.     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
  3979.         cmailMoveRegistered[i] = FALSE;
  3980.         if (cmailCommentList[i] != NULL) {
  3981.         free(cmailCommentList[i]);
  3982.         cmailCommentList[i] = NULL;
  3983.         }
  3984.     }
  3985.     nCmailMovesRegistered = 0;
  3986.     }
  3987.     
  3988.     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
  3989.     cmailResult[i] = CMAIL_NOT_RESULT;
  3990.     }
  3991.     nCmailResults = 0;
  3992.  
  3993.     if (inFilename == NULL) {
  3994.     /* Because the filenames are static they only get malloced once  */
  3995.     /* and they never get freed                                      */
  3996.     inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
  3997.     sprintf(inFilename, "%s.game.in", appData.cmailGameName);
  3998.  
  3999.     outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
  4000.     sprintf(outFilename, "%s.out", appData.cmailGameName);
  4001.     }
  4002.     
  4003.     status = stat(outFilename, &outbuf);
  4004.     if (status < 0) {
  4005.     cmailMailedMove = FALSE;
  4006.     } else {
  4007.     status = stat(inFilename, &inbuf);
  4008.     cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
  4009.     }
  4010.     
  4011.     /* LoadGameFromFile(CMAIL_MAX_GAMES) sets nCmailGames side-effect.   */
  4012.     /* Set cmailMsgLoaded before calling LoadGameFromFile to inhibit the */
  4013.     /* error message about not finding the game                          */
  4014.     cmailMsgLoaded = TRUE;              /* Global variable     */
  4015.     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, appData.cmailGameName);
  4016.     
  4017.     /* Load first game in the file                                       */
  4018.     LoadGameFromFile(inFilename, 1, appData.cmailGameName);
  4019.  
  4020.     /* Make move registered before reloading the whole message           */
  4021.     MakeRegisteredMove();
  4022.     
  4023.     return;
  4024. }
  4025.  
  4026. int RegisterMove()
  4027. {
  4028.     FILE *f;
  4029.     char string[MSG_SIZ];
  4030.  
  4031.     if (   cmailMailedMove
  4032.     || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
  4033.     return TRUE;                  /* Allow free viewing  */
  4034.     }
  4035.  
  4036.     /* Unregister move to ensure that we don't leave RegisterMove        */
  4037.     /* with the move registered when the conditions for registering no   */
  4038.     /* longer hold                                                       */
  4039.     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
  4040.     cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
  4041.     nCmailMovesRegistered --;
  4042.  
  4043.     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
  4044.     {
  4045.         free(cmailCommentList[lastLoadGameNumber - 1]);
  4046.         cmailCommentList[lastLoadGameNumber - 1] = NULL;
  4047.     }
  4048.     }
  4049.  
  4050.     if (cmailOldMove == -1) {
  4051.     DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
  4052.     return FALSE;
  4053.     }
  4054.  
  4055.     if (currentMove > cmailOldMove + 1) {
  4056.     DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
  4057.     return FALSE;
  4058.     }
  4059.  
  4060.     if (currentMove < cmailOldMove) {
  4061.         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
  4062.         return FALSE;
  4063.     }
  4064.  
  4065.     if (forwardMostMove > currentMove) {
  4066.     /* Silently truncate extra moves */
  4067.     TruncateGame();
  4068.     }
  4069.  
  4070.     if (   (currentMove == cmailOldMove + 1)
  4071.     || (   (currentMove == cmailOldMove)
  4072.         && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
  4073.         || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
  4074.     if (gameInfo.result != GameUnfinished) {
  4075.         cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
  4076.     }
  4077.  
  4078.     if (commentList[currentMove] != NULL) {
  4079.         cmailCommentList[lastLoadGameNumber - 1]
  4080.           = StrSave(commentList[currentMove]);
  4081.     }
  4082.     strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
  4083.  
  4084.     if (appData.debugMode)
  4085.       fprintf(debugFP, "Saving %s\n", cmailMove[lastLoadGameNumber - 1]);
  4086.  
  4087.     sprintf(string,
  4088.         "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
  4089.     
  4090.     f = fopen(string, "w");
  4091.     if (appData.oldSaveStyle) {
  4092.         SaveGameOldStyle(f);
  4093.         fclose(f);
  4094.         
  4095.         sprintf(string, "%s.pos.out", appData.cmailGameName);
  4096.         f = fopen(string, "w");
  4097.         SavePosition(f, 0, NULL);
  4098.         fclose(f);
  4099.     } else {
  4100.         fprintf(f, "{--------------\n");
  4101.         PrintPosition(f, currentMove);
  4102.         fprintf(f, "--------------}\n\n");
  4103.         
  4104.         SaveGame(f, 0, NULL);
  4105.         fclose(f);
  4106.     }
  4107.     
  4108.     cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
  4109.     nCmailMovesRegistered ++;
  4110.     } else if (nCmailGames == 1) {
  4111.     DisplayError("You have not made a move yet.", 0);
  4112.     return FALSE;
  4113.     }
  4114.  
  4115.     return TRUE;
  4116. }
  4117.     
  4118. void MailMoveEvent()
  4119. {
  4120.     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
  4121.     FILE *commandOutput;
  4122.     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
  4123.     int nBytes;
  4124.     int nBuffers;
  4125.     int i;
  4126.     int archived;
  4127.     char *arcDir;
  4128.  
  4129.     if (! cmailMsgLoaded) {
  4130.     DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
  4131.     return;
  4132.     }
  4133.  
  4134.     if (nCmailGames == nCmailResults) {
  4135.         DisplayError("No unfinished games", 0);
  4136.     return;
  4137.     }
  4138.  
  4139. #ifdef CMAIL_PROHIBIT_REMAIL
  4140.     if (cmailMailedMove) {
  4141.     sprintf(msg, "You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line.", appData.cmailGameName);
  4142.         DisplayError(msg, 0);
  4143.     return;
  4144.     }
  4145. #endif
  4146.  
  4147.     if (! (cmailMailedMove || RegisterMove())) return;
  4148.     
  4149.     if (   cmailMailedMove
  4150.     || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
  4151.     sprintf(string, partCommandString,
  4152.         appData.debugMode ? " -v" : "", appData.cmailGameName);
  4153.     commandOutput = popen(string, "r");
  4154.  
  4155.     if (commandOutput == NULL) {
  4156.         DisplayError("Failed to invoke cmail", 0);
  4157.     } else {
  4158.         for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
  4159.         nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
  4160.         }
  4161.         if (nBuffers > 1) {
  4162.         (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
  4163.         (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
  4164.         nBytes = MSG_SIZ - 1;
  4165.         } else {
  4166.         (void) memcpy(msg, buffer, nBytes);
  4167.         }
  4168.         *(msg + nBytes) = '\0';          /* \0 for end-of-string*/
  4169.  
  4170.         if(StrStr(msg, "Mailed cmail message to ") != NULL) {
  4171.         cmailMailedMove = TRUE;          /* Prevent >1 moves    */
  4172.  
  4173.         archived = TRUE;
  4174.         for (i = 0; i < nCmailGames; i ++) {
  4175.             if (cmailResult[i] == CMAIL_NOT_RESULT) {
  4176.             archived = FALSE;
  4177.             }
  4178.         }
  4179.         if (   archived
  4180.             && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
  4181.                 != NULL)) {
  4182.             sprintf(buffer, "%s/%s.%s.archive",
  4183.                 arcDir,
  4184.                 appData.cmailGameName,
  4185.                 gameInfo.date);
  4186.             LoadGameFromFile(buffer, 1, buffer);
  4187.             cmailMsgLoaded = FALSE;
  4188.         }
  4189.         }
  4190.  
  4191.         DisplayInformation(msg);
  4192.         pclose(commandOutput);
  4193.     }
  4194.     } else {
  4195.     if ((*cmailMsg) != '\0') {
  4196.         DisplayInformation(cmailMsg);
  4197.     }
  4198.     }
  4199.  
  4200.     return;
  4201. }
  4202.  
  4203. char *AppendCmailMsg(tags)
  4204.      char *tags;
  4205. {
  4206.     int  prependComma = 0;
  4207.     char number[5];
  4208.     char *extendedTags;
  4209.     char string[MSG_SIZ];              /* Space for game-list */
  4210.     int  i;
  4211.     
  4212.     if (cmailMailedMove) {
  4213.     sprintf(cmailMsg, "Waiting for reply from opponent\n");
  4214.     } else {
  4215.     /* Create a list of games left */
  4216.     sprintf(string, "[");
  4217.     for (i = 0; i < nCmailGames; i ++) {
  4218.         if (! (   cmailMoveRegistered[i]
  4219.            || (cmailResult[i] == CMAIL_OLD_RESULT))) {
  4220.         if (prependComma) {
  4221.             sprintf(number, ",%d", i + 1);
  4222.         } else {
  4223.             sprintf(number, "%d", i + 1);
  4224.             prependComma = 1;
  4225.         }
  4226.         
  4227.         strcat(string, number);
  4228.         }
  4229.     }
  4230.     strcat(string, "]");
  4231.  
  4232.     if (nCmailMovesRegistered + nCmailResults == 0) {
  4233.         switch (nCmailGames) {
  4234.           case 1:
  4235.         sprintf(cmailMsg,
  4236.             "Still need to make move for game\n");
  4237.         break;
  4238.         
  4239.           case 2:
  4240.         sprintf(cmailMsg,
  4241.             "Still need to make moves for both games\n");
  4242.         break;
  4243.         
  4244.           default:
  4245.         sprintf(cmailMsg,
  4246.             "Still need to make moves for all %d games\n",
  4247.             nCmailGames);
  4248.         break;
  4249.         }
  4250.     } else {
  4251.         switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
  4252.           case 1:
  4253.         sprintf(cmailMsg,
  4254.             "Still need to make a move for game %s\n",
  4255.             string);
  4256.         break;
  4257.         
  4258.           case 0:
  4259.         if (nCmailResults == nCmailGames) {
  4260.             sprintf(cmailMsg, "No unfinished games\n");
  4261.         } else {
  4262.             sprintf(cmailMsg, "Ready to send mail\n");
  4263.         }
  4264.         break;
  4265.         
  4266.           default:
  4267.         sprintf(cmailMsg,
  4268.             "Still need to make moves for games %s\n",
  4269.             string);
  4270.         }
  4271.     }
  4272.     }
  4273.  
  4274.     extendedTags = (char *) malloc(strlen(tags) + strlen(cmailMsg) + 2); 
  4275.     sprintf(extendedTags, "%s\n%s", tags, cmailMsg);
  4276.     free(tags);
  4277.  
  4278.     return extendedTags;
  4279. }
  4280.  
  4281. void ResetGameEvent()
  4282. {
  4283.     Reset(TRUE);
  4284.     cmailMsgLoaded = FALSE;
  4285.     if (appData.icsActive)
  4286.       SendToICS("refresh\n");
  4287. }
  4288.  
  4289. static int exitrecur = 0;
  4290.  
  4291. void ExitEvent(status)
  4292.      int status;
  4293. {
  4294.     if (exitrecur++ > 0) exit(status);  /* error during exit processing */
  4295.  
  4296.     if (icsPR != NoProc) {
  4297.     DestroyChildProcess(icsPR);
  4298.     }
  4299.     /* Save game if resource set and not already saved by GameEnds() */
  4300.     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
  4301.     if (*appData.saveGameFile != NULLCHAR) {
  4302.         SaveGameToFile(appData.saveGameFile);
  4303.     } else if (appData.autoSaveGames) {
  4304.         AutoSaveGame();
  4305.     }
  4306.     if (*appData.savePositionFile != NULLCHAR) {
  4307.         SavePositionToFile(appData.savePositionFile);
  4308.     }
  4309.     }
  4310.     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
  4311.  
  4312.     exit(status);
  4313. }
  4314.  
  4315. void PauseEvent()
  4316. {
  4317.     if (pausing) {
  4318.     pausing = FALSE;
  4319.     ModeHighlight();
  4320.     if (gameMode == MachinePlaysWhite ||
  4321.         gameMode == MachinePlaysBlack) {
  4322.         StartClocks();
  4323.     } else {
  4324.         DisplayBothClocks();
  4325.     }
  4326.     if (gameMode == PlayFromGameFile) {
  4327.         if (appData.timeDelay >= 0) LoadGameLoop();
  4328.     } else if (gameMode == IcsExamining && pauseExamInvalid) {
  4329.         Reset(FALSE);
  4330.         SendToICS("refresh\n");
  4331.     } else if (currentMove < forwardMostMove) {
  4332.         ForwardInner(forwardMostMove);
  4333.     }
  4334.     pauseExamInvalid = FALSE;
  4335.     } else {
  4336.     switch (gameMode) {
  4337.       default:
  4338.         return;
  4339.       case IcsExamining:
  4340.         pauseExamForwardMostMove = forwardMostMove;
  4341.         pauseExamInvalid = FALSE;
  4342.         /* fall through */
  4343.       case IcsObserving:
  4344.       case IcsPlayingWhite:
  4345.       case IcsPlayingBlack:
  4346.         pausing = TRUE;
  4347.         ModeHighlight();
  4348.         return;
  4349.       case PlayFromGameFile:
  4350.         (void) StopLoadGameTimer();
  4351.         pausing = TRUE;
  4352.         ModeHighlight();
  4353.         break;
  4354.       case BeginningOfGame:
  4355.         if (appData.icsActive) return;
  4356.         /* else fall through */
  4357.       case MachinePlaysWhite:
  4358.       case MachinePlaysBlack:
  4359.       case TwoMachinesPlay:
  4360.         if (forwardMostMove == 0)
  4361.           return;        /* don't pause if no one has moved */
  4362.         if ((gameMode == MachinePlaysWhite &&
  4363.          !WhiteOnMove(forwardMostMove)) ||
  4364.         (gameMode == MachinePlaysBlack &&
  4365.          WhiteOnMove(forwardMostMove))) {
  4366.         StopClocks();
  4367.         }
  4368.         pausing = TRUE;
  4369.         ModeHighlight();
  4370.         break;
  4371.     }
  4372.     }
  4373. }
  4374.  
  4375. void EditCommentEvent()
  4376. {
  4377.     char title[MSG_SIZ];
  4378.  
  4379.     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
  4380.     strcpy(title, "Edit comment");
  4381.     } else {
  4382.     sprintf(title, "Edit comment on %d. %s%s", (currentMove - 1) / 2 + 1,
  4383.         WhiteOnMove(currentMove - 1) ? "" : "... ",
  4384.         parseList[currentMove - 1]);
  4385.     }
  4386.  
  4387.     EditCommentPopUp(currentMove, title, commentList[currentMove]);
  4388. }
  4389.  
  4390.  
  4391. void MachineWhiteEvent()
  4392. {
  4393.     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
  4394.       return;
  4395.     if (gameMode == PlayFromGameFile || gameMode == TwoMachinesPlay ||
  4396.     gameMode == EndOfGame)
  4397.       EditGameEvent();
  4398.     if (gameMode == EditPosition) EditPositionDone();
  4399.    
  4400.     if (!WhiteOnMove(gameMode == EditGame ? currentMove : forwardMostMove)) {
  4401.     DisplayError("It is not White's turn", 0);
  4402.     return;
  4403.     }
  4404.     if (gameMode == EditGame) forwardMostMove = currentMove;
  4405.     ResurrectChessProgram(); /* in case it isn't running */
  4406.  
  4407.     lastGameMode = gameMode = MachinePlaysWhite;
  4408.     pausing = FALSE;
  4409.     ModeHighlight();
  4410.     SetGameInfo();
  4411.     SendToProgram(appData.whiteString, firstProgramPR);
  4412.     StartClocks();
  4413. }
  4414.  
  4415. void MachineBlackEvent()
  4416. {
  4417.     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
  4418.       return;
  4419.     if (gameMode == PlayFromGameFile || gameMode == TwoMachinesPlay ||
  4420.     gameMode == EndOfGame)
  4421.       EditGameEvent();
  4422.     if (gameMode == EditPosition) EditPositionDone();
  4423.     
  4424.     if (WhiteOnMove(gameMode == EditGame ? currentMove : forwardMostMove)) {
  4425.     DisplayError("It is not Black's turn", 0);
  4426.     return;
  4427.     }
  4428.     if (gameMode == EditGame) forwardMostMove = currentMove;
  4429.     ResurrectChessProgram(); /* in case it isn't running */
  4430.  
  4431.     lastGameMode = gameMode = MachinePlaysBlack;
  4432.     pausing = FALSE;
  4433.     ModeHighlight();
  4434.     SetGameInfo();
  4435.     SendToProgram(appData.blackString, firstProgramPR);
  4436.     StartClocks();
  4437. }
  4438.  
  4439.  
  4440. void TwoMachinesEvent()
  4441. {
  4442.     int i;
  4443.     
  4444.     if (appData.noChessProgram) return;
  4445.     
  4446.     switch (gameMode) {
  4447.       case TwoMachinesPlay:
  4448.     return;
  4449.       case MachinePlaysWhite:
  4450.       case MachinePlaysBlack:
  4451.       case BeginningOfGame:
  4452.       case PlayFromGameFile:
  4453.       case EndOfGame:
  4454.     EditGameEvent();
  4455.     if (gameMode != EditGame) return;
  4456.     break;
  4457.       case EditPosition:
  4458.     EditPositionDone();
  4459.     break;
  4460.       case EditGame:
  4461.       default:
  4462.     break;
  4463.     }
  4464.     
  4465.     forwardMostMove = currentMove;
  4466.     ResurrectChessProgram(); /* in case first program isn't running */
  4467.  
  4468.     InitChessProgram(appData.secondHost, appData.secondChessProgram,
  4469.              &secondProgramPR, &secondProgramISR, &secondSendTime);
  4470.     if (startedFromSetupPosition) {
  4471.     if (blackPlaysFirst) {
  4472.         SendToProgram("force\na3\n", secondProgramPR);
  4473.         SendBoard(secondProgramPR, boards[backwardMostMove]);
  4474.     } else {
  4475.         SendBoard(secondProgramPR, boards[backwardMostMove]);
  4476.         SendToProgram("force\n", secondProgramPR);
  4477.     }
  4478.     } else {
  4479.     SendToProgram("force\n", secondProgramPR);
  4480.     }
  4481.     for (i = backwardMostMove; i < forwardMostMove; i++) {
  4482.     SendToProgram(moveList[i], secondProgramPR);
  4483.     }
  4484.     lastGameMode = gameMode = TwoMachinesPlay;
  4485.     pausing = FALSE;
  4486.     ModeHighlight();
  4487.     SetGameInfo();
  4488.     firstMove = TRUE;
  4489.     if (WhiteOnMove(forwardMostMove))
  4490.       SendToProgram(appData.whiteString, secondProgramPR);
  4491.     else
  4492.       SendToProgram(appData.blackString, firstProgramPR);
  4493.     
  4494.     if (!firstSendTime || !secondSendTime) {
  4495.     ResetClocks();
  4496.     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
  4497.     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
  4498.     }
  4499.     StartClocks();
  4500. }
  4501.  
  4502.  
  4503. void IcsClientEvent()
  4504. {
  4505.     if (!appData.icsActive) return;
  4506.     switch (gameMode) {
  4507.       case IcsPlayingWhite:
  4508.       case IcsPlayingBlack:
  4509.       case IcsObserving:
  4510.       case IcsIdle:
  4511.       case BeginningOfGame:
  4512.       case IcsExamining:
  4513.     return;
  4514.  
  4515.       case EditGame:
  4516.     break;
  4517.  
  4518.       case EditPosition:
  4519.     EditPositionDone();
  4520.     break;
  4521.  
  4522.       default:
  4523.     EditGameEvent();
  4524.     break;
  4525.     }
  4526.  
  4527.     gameMode = IcsIdle;
  4528.     ModeHighlight();
  4529.     return;
  4530. }
  4531.  
  4532.  
  4533. void EditGameEvent()
  4534. {
  4535.     int i;
  4536.     char str[MSG_SIZ];
  4537.     
  4538.     switch (gameMode) {
  4539.       case MachinePlaysWhite:
  4540.     if (WhiteOnMove(forwardMostMove)) {
  4541.         DisplayError("Wait until your turn,\nor select Move Now", 0);
  4542.         return;
  4543.     }
  4544.     SendToProgram("force\n", firstProgramPR);
  4545.     break;
  4546.       case MachinePlaysBlack:
  4547.     if (!WhiteOnMove(forwardMostMove)) {
  4548.         DisplayError("Wait until your turn,\nor select Move Now", 0);
  4549.         return;
  4550.     }
  4551.     SendToProgram("force\n", firstProgramPR);
  4552.     break;
  4553.       case BeginningOfGame:
  4554.     SendToProgram("force\n", firstProgramPR);
  4555.     break;
  4556.       case PlayFromGameFile:
  4557.         (void) StopLoadGameTimer();
  4558.     if (gameFileFP != NULL) {
  4559.         gameFileFP = NULL;
  4560.     }
  4561.     break;
  4562.       case EditPosition:
  4563.     EditPositionDone();
  4564.     break;
  4565.       case TwoMachinesPlay:
  4566.     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
  4567.     ResurrectChessProgram();
  4568.     break;
  4569.       case EndOfGame:
  4570.     ResurrectChessProgram();
  4571.     break;
  4572.       case IcsPlayingBlack:
  4573.       case IcsPlayingWhite:
  4574.     DisplayError("Aren't you playing a game?\nAdjourn or finish before you try to edit locally.", 0);
  4575.     return;
  4576.       case IcsObserving:
  4577.     SendToICS("observe\n");
  4578.     sprintf(str, "Aren't you observing game %d?\nAttempting to stop observing it.", ics_gamenum);
  4579.     DisplayError(str, 0);
  4580.     break;
  4581.       case IcsExamining:
  4582.     DisplayError("Aren't you examining a game?\nStop examining before you try to edit locally.", 0);
  4583.     return;
  4584.       case IcsIdle:
  4585.     break;
  4586.       case EditGame:
  4587.       default:
  4588.     return;
  4589.     }
  4590.     
  4591.     pausing = FALSE;
  4592.     StopClocks();
  4593.  
  4594.     if (gameMode == MachinePlaysWhite ||
  4595.     gameMode == MachinePlaysBlack ||
  4596.     gameMode == TwoMachinesPlay ||
  4597.     gameMode == PlayFromGameFile) {
  4598.     i = forwardMostMove;
  4599.     while (i > currentMove) {
  4600.         SendToProgram("undo\n", firstProgramPR);
  4601.         i--;
  4602.     }
  4603.     whiteTimeRemaining = timeRemaining[0][currentMove];
  4604.     blackTimeRemaining = timeRemaining[1][currentMove];
  4605.     DisplayBothClocks();
  4606.     if (whiteFlag || blackFlag) {
  4607.         whiteFlag = blackFlag = 0;
  4608.     }
  4609.     DisplayTitle("");
  4610.     }        
  4611.     
  4612.     lastGameMode = gameMode = EditGame;
  4613.     ModeHighlight();
  4614.     SetGameInfo();
  4615. }
  4616.  
  4617.  
  4618. void EditPositionEvent()
  4619. {
  4620.     if (gameMode == EditPosition) {
  4621.     EditGameEvent();
  4622.     return;
  4623.     }
  4624.     
  4625.     EditGameEvent();
  4626.     if (gameMode != EditGame) return;
  4627.     
  4628.     lastGameMode = gameMode = EditPosition;
  4629.     ModeHighlight();
  4630.     SetGameInfo();
  4631.     if (currentMove > 0)
  4632.       CopyBoard(boards[0], boards[currentMove]);
  4633.     
  4634.     blackPlaysFirst = !WhiteOnMove(currentMove);
  4635.     ResetClocks();
  4636.     currentMove = forwardMostMove = backwardMostMove = 0;
  4637. }
  4638.  
  4639. void EditPositionDone()
  4640. {
  4641.     startedFromSetupPosition = TRUE;
  4642.     SendToProgram(appData.initString, firstProgramPR);
  4643.     SendSearchDepth(firstProgramPR);
  4644.     if (blackPlaysFirst) {
  4645.     strcpy(moveList[0], "");
  4646.     strcpy(parseList[0], "");
  4647.     currentMove = forwardMostMove = backwardMostMove = 1;
  4648.     CopyBoard(boards[1], boards[0]);
  4649.     SendToProgram("force\na3\n", firstProgramPR);
  4650.     SendCurrentBoard(firstProgramPR);
  4651.     DisplayTitle("");
  4652.     } else {
  4653.     currentMove = forwardMostMove = backwardMostMove = 0;
  4654.     SendCurrentBoard(firstProgramPR);
  4655.     SendToProgram("force\n", firstProgramPR);
  4656.     DisplayTitle("");
  4657.     }
  4658.     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
  4659.     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
  4660.     lastGameMode = gameMode = EditGame;
  4661.     ModeHighlight();
  4662. }
  4663.  
  4664. void SetWhiteToPlayEvent()
  4665. {
  4666.     if (gameMode != EditPosition) return;
  4667.     blackPlaysFirst = FALSE;
  4668.     DisplayBothClocks(); /* works because currentMove is 0 */
  4669. }
  4670.  
  4671. void SetBlackToPlayEvent()
  4672. {
  4673.     if (gameMode != EditPosition) return;
  4674.     blackPlaysFirst = TRUE;
  4675.     currentMove = 1;    /* kludge */
  4676.     DisplayBothClocks();
  4677.     currentMove = 0;
  4678. }
  4679.  
  4680. void EditPositionMenuEvent(selection, x, y)
  4681.      ChessSquare selection;
  4682.      int x, y;
  4683. {
  4684.     char buf[MSG_SIZ];
  4685.  
  4686.     if (gameMode != EditPosition && gameMode != IcsExamining) return;
  4687.     switch (selection) {
  4688.       case ClearBoard:
  4689.     for (x = 0; x < BOARD_SIZE; x++)
  4690.       for (y = 0; y < BOARD_SIZE; y++) {
  4691.           if (gameMode == IcsExamining) {
  4692.           if (boards[currentMove][y][x] != EmptySquare) {
  4693.               sprintf(buf, "x@%c%c\n", 'a' + x, '1' + y);
  4694.               SendToICS(buf);
  4695.           }
  4696.           } else /* gameMode == EditPosition */ {
  4697.           boards[0][y][x] = EmptySquare;
  4698.           }
  4699.       }
  4700.     if (gameMode == EditPosition)
  4701.       DrawPosition(FALSE, boards[0]);
  4702.     break;
  4703.  
  4704.       case WhitePlay:
  4705.     SetWhiteToPlayEvent();
  4706.     break;
  4707.  
  4708.       case BlackPlay:
  4709.     SetBlackToPlayEvent();
  4710.     break;
  4711.  
  4712.       case EmptySquare:
  4713.     if (gameMode == IcsExamining) {
  4714.         sprintf(buf, "x@%c%c\n", 'a' + x, '1' + y);
  4715.         SendToICS(buf);
  4716.     } else /* gameMode == EditPosition */ {
  4717.         boards[0][y][x] = EmptySquare;
  4718.         DrawPosition(FALSE, boards[0]);
  4719.     }
  4720.     break;
  4721.  
  4722.       default:
  4723.     if (gameMode == IcsExamining) {
  4724.         sprintf(buf, "%c@%c%c\n",
  4725.             PieceToChar(selection), 'a' + x, '1' + y);
  4726.         SendToICS(buf);
  4727.     } else /* gameMode == EditPosition */ {
  4728.         boards[0][y][x] = selection;
  4729.         DrawPosition(FALSE, boards[0]);
  4730.     }
  4731.     break;
  4732.     }
  4733. }
  4734.  
  4735.  
  4736. void AcceptEvent()
  4737. {
  4738.     /* Accept a pending offer of any kind from opponent */
  4739.     
  4740.     if (appData.icsActive) {
  4741.     SendToICS("accept\n");
  4742.     } else if (cmailMsgLoaded) {
  4743.     if (currentMove == cmailOldMove &&
  4744.         commentList[cmailOldMove] != NULL &&
  4745.         StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
  4746.            "Black offers a draw" : "White offers a draw")) {
  4747.         TruncateGame();
  4748.         GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
  4749.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
  4750.     } else {
  4751.         DisplayError("There is no pending offer on this move", 0);
  4752.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
  4753.     }
  4754.     } else {
  4755.     /* Currently GNU Chess doesn't make offers at all */
  4756.     }
  4757. }
  4758.  
  4759. void DeclineEvent()
  4760. {
  4761.     /* Decline a pending offer of any kind from opponent */
  4762.     
  4763.     if (appData.icsActive) {
  4764.     SendToICS("decline\n");
  4765.     } else if (cmailMsgLoaded) {
  4766.     if (currentMove == cmailOldMove &&
  4767.         commentList[cmailOldMove] != NULL &&
  4768.         StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
  4769.            "Black offers a draw" : "White offers a draw")) {
  4770. #ifdef NOTDEF
  4771.         AppendComment(cmailOldMove, "Draw declined");
  4772.         DisplayComment(cmailOldMove - 1, "Draw declined");
  4773. #endif /*NOTDEF*/
  4774.     } else {
  4775.         DisplayError("There is no pending offer on this move", 0);
  4776.     }
  4777.     } else {
  4778.     /* Currently GNU Chess doesn't make offers at all */
  4779.     }
  4780. }
  4781.  
  4782. void CallFlagEvent()
  4783. {
  4784.     /* Call your opponent's flag (claim a win on time) */
  4785.     if (appData.icsActive) {
  4786.     SendToICS("flag\n");
  4787.     } else {
  4788.     switch (gameMode) {
  4789.       default:
  4790.         return;
  4791.       case MachinePlaysWhite:
  4792.         if (whiteFlag) {
  4793.         if (blackFlag)
  4794.           GameEnds(GameIsDrawn, "Both players ran out of time",
  4795.                GE_PLAYER);
  4796.         else
  4797.           GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
  4798.         } else {
  4799.         DisplayError("Your opponent is not out of time", 0);
  4800.         }
  4801.         break;
  4802.       case MachinePlaysBlack:
  4803.         if (blackFlag) {
  4804.         if (whiteFlag)
  4805.           GameEnds(GameIsDrawn, "Both players ran out of time",
  4806.                GE_PLAYER);
  4807.         else
  4808.           GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
  4809.         } else {
  4810.         DisplayError("Your opponent is not out of time", 0);
  4811.         }
  4812.         break;
  4813.     }
  4814.     }
  4815. }
  4816.  
  4817. void DrawEvent()
  4818. {
  4819.     /* Offer draw or accept pending draw offer from opponent */
  4820.     
  4821.     if (appData.icsActive) {
  4822.     /* Note: tournament rules require draw offers to be
  4823.        made after you make your move but before you punch
  4824.        your clock.  Currently ICS doesn't let you do that;
  4825.        instead, you immediately punch your clock after making
  4826.        a move, but you can offer a draw at any time. */
  4827.     
  4828.     SendToICS("draw\n");
  4829.     } else if (cmailMsgLoaded) {
  4830.     if (currentMove == cmailOldMove &&
  4831.         commentList[cmailOldMove] != NULL &&
  4832.         StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
  4833.            "Black offers a draw" : "White offers a draw")) {
  4834.         GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
  4835.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
  4836.     } else if (currentMove == cmailOldMove + 1) {
  4837.         char *offer = WhiteOnMove(cmailOldMove) ?
  4838.           "White offers a draw" : "Black offers a draw";
  4839.         AppendComment(currentMove, offer);
  4840.         DisplayComment(currentMove - 1, offer);
  4841.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
  4842.     } else {
  4843.         DisplayError("You must make your move before offering a draw", 0);
  4844.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
  4845.     }
  4846.     } else {
  4847.     /* Currently GNU Chess doesn't offer or accept draws */
  4848.     }
  4849. }
  4850.  
  4851. void AdjournEvent()
  4852. {
  4853.     /* Offer Adjourn or accept pending Adjourn offer from opponent */
  4854.     
  4855.     if (appData.icsActive) {
  4856.     SendToICS("adjourn\n");
  4857.     } else {
  4858.     /* Currently GNU Chess doesn't offer or accept Adjourns */
  4859.     }
  4860. }
  4861.  
  4862.  
  4863. void AbortEvent()
  4864. {
  4865.     /* Offer Abort or accept pending Abort offer from opponent */
  4866.     
  4867.     if (appData.icsActive) {
  4868.     SendToICS("abort\n");
  4869.     } else {
  4870.     GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
  4871.     }
  4872. }
  4873.  
  4874. void ResignEvent()
  4875. {
  4876.     /* Resign.  You can do this even if it's not your turn. */
  4877.     
  4878.     if (appData.icsActive) {
  4879.     SendToICS("resign\n");
  4880.     } else {
  4881.     switch (gameMode) {
  4882.       case MachinePlaysWhite:
  4883.         GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
  4884.         break;
  4885.       case MachinePlaysBlack:
  4886.         GameEnds(BlackWins, "White resigns", GE_PLAYER);
  4887.         break;
  4888.       case EditGame:
  4889.         if (cmailMsgLoaded) {
  4890.         TruncateGame();
  4891.         if (WhiteOnMove(cmailOldMove)) {
  4892.             GameEnds(BlackWins, "White resigns", GE_PLAYER);
  4893.         } else {
  4894.             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
  4895.         }
  4896.         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
  4897.         }
  4898.         break;
  4899.       default:
  4900.         break;
  4901.     }
  4902.     }
  4903. }
  4904.  
  4905.  
  4906. void StopObservingEvent()
  4907. {
  4908.     /* Stop observing current game */
  4909.     SendToICS("observe\n");
  4910. }
  4911.  
  4912. void StopExaminingEvent()
  4913. {
  4914.     /* Stop observing current game */
  4915.     SendToICS("unexamine\n");
  4916. }
  4917.  
  4918. void ForwardInner(target)
  4919.      int target;
  4920. {
  4921.     int limit;
  4922.  
  4923.     if (gameMode == EditPosition)
  4924.       return;
  4925.     
  4926.     if (gameMode == PlayFromGameFile && !pausing)
  4927.       PauseEvent();
  4928.     
  4929.     if (gameMode == IcsExamining && pausing)
  4930.       limit = pauseExamForwardMostMove;
  4931.     else
  4932.       limit = forwardMostMove;
  4933.  
  4934.     if (currentMove >= limit) {
  4935.     if (gameFileFP != NULL)
  4936.       (void) LoadGameOneMove();
  4937.     return;
  4938.     }
  4939.     
  4940.     if (target > limit) target = limit;
  4941.     if (gameMode == EditGame) {
  4942.     while (currentMove < target) {
  4943.         SendToProgram(moveList[currentMove++], firstProgramPR);
  4944.     }
  4945.     } else {
  4946.     currentMove = target;
  4947.     }
  4948.     
  4949.     if (gameMode == EditGame || gameMode == EndOfGame) {
  4950.     whiteTimeRemaining = timeRemaining[0][currentMove];
  4951.     blackTimeRemaining = timeRemaining[1][currentMove];
  4952.     }
  4953.     DisplayBothClocks();
  4954.     DisplayMove(currentMove - 1);
  4955.     DrawPosition(FALSE, boards[currentMove]);
  4956.     if (commentList[currentMove] != NULL) {
  4957.     DisplayComment(currentMove - 1, commentList[currentMove]);
  4958.     }
  4959. }
  4960.  
  4961.  
  4962. void ForwardEvent()
  4963. {
  4964.     if (gameMode == IcsExamining && !pausing) {
  4965.     SendToICS("forward\n");
  4966.     } else {
  4967.     ForwardInner(currentMove + 1);
  4968.     }
  4969. }
  4970.  
  4971. void ToEndEvent()
  4972. {
  4973.     if (gameMode == IcsExamining && !pausing) {
  4974.     SendToICS("forward 999999\n");
  4975.     } else {
  4976.     ForwardInner(forwardMostMove);
  4977.     }
  4978. }
  4979.  
  4980. void BackwardInner(target)
  4981.      int target;
  4982. {
  4983.     if ((currentMove <= backwardMostMove) || (gameMode == EditPosition))
  4984.       return;
  4985.     
  4986.     if (gameMode == PlayFromGameFile && !pausing)
  4987.       PauseEvent();
  4988.     
  4989.     if (gameMode == EditGame) {
  4990.     while (currentMove > target) {
  4991.         SendToProgram("undo\n", firstProgramPR);
  4992.         currentMove--;
  4993.     }
  4994.     } else {
  4995.     currentMove = target;
  4996.     }
  4997.     
  4998.     if (gameMode == EditGame || gameMode == EndOfGame) {
  4999.     whiteTimeRemaining = timeRemaining[0][currentMove];
  5000.     blackTimeRemaining = timeRemaining[1][currentMove];
  5001.     }
  5002.     DisplayBothClocks();
  5003.     DisplayMove(currentMove - 1);
  5004.     DrawPosition(FALSE, boards[currentMove]);
  5005.     if (commentList[currentMove] != NULL) {
  5006.     DisplayComment(currentMove - 1, commentList[currentMove]);
  5007.     }
  5008. }
  5009.  
  5010. void BackwardEvent()
  5011. {
  5012.     if (gameMode == IcsExamining && !pausing) {
  5013.     SendToICS("backward\n");
  5014.     } else {
  5015.     BackwardInner(currentMove - 1);
  5016.     }
  5017. }
  5018.  
  5019. void ToStartEvent()
  5020. {
  5021.     if (gameMode == IcsExamining && !pausing) {
  5022.     SendToICS("backward 999999\n");
  5023.     } else {
  5024.     BackwardInner(backwardMostMove);
  5025.     }
  5026. }
  5027.  
  5028. void RevertEvent()
  5029. {
  5030.     if (gameMode != IcsExamining) {
  5031.     DisplayError("You are not examining a game", 0);
  5032.     return;
  5033.     }
  5034.     if (pausing) {
  5035.     DisplayError("You can't revert while pausing", 0);
  5036.     return;
  5037.     }
  5038.     SendToICS("revert\n");
  5039. }
  5040.  
  5041. void RetractMoveEvent()
  5042. {
  5043.     switch (gameMode) {
  5044.       case MachinePlaysWhite:
  5045.       case MachinePlaysBlack:
  5046.     if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
  5047.         DisplayError("Wait until your turn,\nor select Move Now", 0);
  5048.         return;
  5049.     }
  5050.     if (forwardMostMove < 2) return;
  5051.     currentMove = forwardMostMove = forwardMostMove - 2;
  5052.     whiteTimeRemaining = timeRemaining[0][currentMove];
  5053.     blackTimeRemaining = timeRemaining[1][currentMove];
  5054.     DisplayBothClocks();
  5055.     DisplayMove(currentMove - 1);
  5056.     DrawPosition(FALSE, boards[currentMove]);
  5057.     SendToProgram("remove\n", firstProgramPR);
  5058.     break;
  5059.  
  5060.       case BeginningOfGame:
  5061.       default:
  5062.     break;
  5063.  
  5064.       case IcsPlayingWhite:
  5065.       case IcsPlayingBlack:
  5066.     if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
  5067.         SendToICS("takeback 2\n");
  5068.     } else {
  5069.         SendToICS("takeback 1\n");
  5070.     }
  5071.     break;
  5072.     }
  5073. }
  5074.  
  5075. void MoveNowEvent()
  5076. {
  5077.     switch (gameMode) {
  5078.       case MachinePlaysWhite:
  5079.     if (!WhiteOnMove(forwardMostMove)) {
  5080.         DisplayError("It is your turn", 0);
  5081.         return;
  5082.     }
  5083.     InterruptChildProcess(firstProgramPR);
  5084.     break;
  5085.       case MachinePlaysBlack:
  5086.     if (WhiteOnMove(forwardMostMove)) {
  5087.         DisplayError("It is your turn", 0);
  5088.         return;
  5089.     }
  5090.     InterruptChildProcess(firstProgramPR);
  5091.     break;
  5092.       case TwoMachinesPlay:
  5093.     if (WhiteOnMove(forwardMostMove)) {
  5094.         InterruptChildProcess(secondProgramPR);
  5095.     } else {
  5096.         InterruptChildProcess(firstProgramPR);
  5097.     }
  5098.     break;
  5099.       case BeginningOfGame:
  5100.       default:
  5101.     break;
  5102.     }
  5103. }
  5104.  
  5105. void TruncateGameEvent()
  5106. {
  5107.     EditGameEvent();
  5108.     if (gameMode != EditGame) return;
  5109.     TruncateGame();
  5110. }
  5111.  
  5112. void TruncateGame()
  5113. {
  5114.     if (forwardMostMove > currentMove) {
  5115.     if (gameInfo.resultDetails != NULL) {
  5116.         free(gameInfo.resultDetails);
  5117.         gameInfo.resultDetails = NULL;
  5118.         gameInfo.result = GameUnfinished;
  5119.     }
  5120.     forwardMostMove = currentMove;
  5121.     }
  5122. }
  5123.  
  5124. void HintEvent()
  5125. {
  5126.     if (appData.noChessProgram) return;
  5127.     switch (gameMode) {
  5128.       case MachinePlaysWhite:
  5129.     if (WhiteOnMove(forwardMostMove)) {
  5130.         DisplayError("Wait until your turn", 0);
  5131.         return;
  5132.     }
  5133.     break;
  5134.       case BeginningOfGame:
  5135.       case MachinePlaysBlack:
  5136.     if (!WhiteOnMove(forwardMostMove)) {
  5137.         DisplayError("Wait until your turn", 0);
  5138.         return;
  5139.     }
  5140.     break;
  5141.       default:
  5142.     DisplayError("No hint available", 0);
  5143.     return;
  5144.     }
  5145.     SendToProgram("hint\n", firstProgramPR);
  5146.     hintRequested = TRUE;
  5147. }
  5148.  
  5149. void BookEvent()
  5150. {
  5151.     if (appData.noChessProgram) return;
  5152.     switch (gameMode) {
  5153.       case MachinePlaysWhite:
  5154.     if (WhiteOnMove(forwardMostMove)) {
  5155.         DisplayError("Wait until your turn", 0);
  5156.         return;
  5157.     }
  5158.     break;
  5159.       case BeginningOfGame:
  5160.       case MachinePlaysBlack:
  5161.     if (!WhiteOnMove(forwardMostMove)) {
  5162.         DisplayError("Wait until your turn", 0);
  5163.         return;
  5164.     }
  5165.     break;
  5166.       case EditPosition:
  5167.     EditPositionDone();
  5168.     break;
  5169.       case TwoMachinesPlay:
  5170.     return;
  5171.       default:
  5172.     break;
  5173.     }
  5174.     SendToProgram("bk\n", firstProgramPR);
  5175.     bookOutput[0] = NULLCHAR;
  5176.     bookRequested = TRUE;
  5177. }
  5178.  
  5179. void AboutGameEvent()
  5180. {
  5181.     if (gameInfo.event != NULL) {
  5182.     char *tags = PGNTags();
  5183.     if (cmailMsgLoaded) {
  5184.         tags = AppendCmailMsg(tags);
  5185.     }
  5186.     DisplayInformation(tags);
  5187.     free(tags);
  5188.     } else {
  5189.     DisplayError("No information available", 0);
  5190.     }
  5191. }
  5192.  
  5193. /* end button procedures */
  5194.  
  5195. void PrintPosition(fp, move)
  5196.      FILE *fp;
  5197.      int move;
  5198. {
  5199.     int i, j;
  5200.     
  5201.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  5202.     for (j = 0; j < BOARD_SIZE; j++) {
  5203.         fprintf(fp, "%c", PieceToChar(boards[move][i][j]));
  5204.         fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
  5205.     }
  5206.     }
  5207.     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
  5208.       fprintf(fp, "white to play\n");
  5209.     else
  5210.       fprintf(fp, "black to play\n");
  5211. }
  5212.  
  5213. void PrintOpponents(fp)
  5214.      FILE *fp;
  5215. {
  5216.     if (gameInfo.white != NULL) {
  5217.     fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
  5218.     } else {
  5219.     fprintf(fp, "\n");
  5220.     }
  5221. }
  5222.  
  5223. void ClearGameInfo()
  5224. {
  5225.     if (gameInfo.event != NULL) free(gameInfo.event);
  5226.     gameInfo.event = NULL;
  5227.     if (gameInfo.site != NULL) free(gameInfo.site);
  5228.     gameInfo.site = NULL;
  5229.     if (gameInfo.date != NULL) free(gameInfo.date);
  5230.     gameInfo.date = NULL;
  5231.     if (gameInfo.round != NULL) free(gameInfo.round);
  5232.     gameInfo.round = NULL;
  5233.     if (gameInfo.white != NULL) free(gameInfo.white);
  5234.     gameInfo.white = NULL;
  5235.     if (gameInfo.black != NULL) free(gameInfo.black);
  5236.     gameInfo.black = NULL;
  5237.     gameInfo.result = GameUnfinished;
  5238.     if (gameInfo.resultDetails != NULL) {
  5239.     free(gameInfo.resultDetails);
  5240.     gameInfo.resultDetails = NULL;
  5241.     }
  5242.     if (gameInfo.fen != NULL) free(gameInfo.fen);
  5243.     gameInfo.fen = NULL;
  5244.     if (gameInfo.timeControl != NULL) free(gameInfo.timeControl);
  5245.     gameInfo.timeControl = NULL;
  5246.     if (gameInfo.extraTags != NULL) free(gameInfo.extraTags);
  5247.     gameInfo.extraTags = NULL;
  5248. }
  5249.  
  5250. void SetGameInfo()
  5251. {
  5252.     char buf[MSG_SIZ];
  5253.  
  5254.     /* This routine is used only for certain modes */
  5255.     ClearGameInfo();
  5256.     switch (gameMode) {
  5257.       case MachinePlaysWhite:
  5258.     gameInfo.event = StrSave("GNU Chess game");
  5259.     gameInfo.site = StrSave(HostName());
  5260.     gameInfo.date = PGNDate();
  5261.     gameInfo.round = StrSave("-");
  5262.     if (strcmp(appData.firstHost, "localhost") != 0) {
  5263.         sprintf(buf, "%s@%s",
  5264.             appData.firstChessProgram, appData.firstHost);
  5265.         gameInfo.white = StrSave(buf);
  5266.     } else {
  5267.         gameInfo.white = StrSave(appData.firstChessProgram);
  5268.     }
  5269.     gameInfo.black = StrSave(UserName());
  5270.     sprintf(buf, "%d/%ld", appData.movesPerSession, timeControl/1000);
  5271.     gameInfo.timeControl = StrSave(buf);
  5272.     break;
  5273.       case MachinePlaysBlack:
  5274.     gameInfo.event = StrSave("GNU Chess game");
  5275.     gameInfo.site = StrSave(HostName());
  5276.     gameInfo.date = PGNDate();
  5277.     gameInfo.round = StrSave("-");
  5278.     gameInfo.white = StrSave(UserName());
  5279.     sprintf(buf, "%s@%s", appData.firstChessProgram, appData.firstHost);
  5280.     if (strcmp(appData.firstHost, "localhost") != 0) {
  5281.         sprintf(buf, "%s@%s",
  5282.             appData.firstChessProgram, appData.firstHost);
  5283.         gameInfo.black = StrSave(buf);
  5284.     } else {
  5285.         gameInfo.black = StrSave(appData.firstChessProgram);
  5286.     }
  5287.     sprintf(buf, "%d/%ld", appData.movesPerSession, timeControl/1000);
  5288.     gameInfo.timeControl = StrSave(buf);
  5289.     break;
  5290.       case TwoMachinesPlay:
  5291.     gameInfo.event = StrSave("GNU Chess game");
  5292.     gameInfo.site = StrSave(HostName());
  5293.     gameInfo.date = PGNDate();
  5294.     gameInfo.round = StrSave("-");
  5295.     if (strcmp(appData.secondHost, "localhost") != 0) {
  5296.         sprintf(buf, "%s@%s",
  5297.             appData.secondChessProgram, appData.secondHost);
  5298.         gameInfo.white = StrSave(buf);
  5299.     } else {
  5300.         gameInfo.white = StrSave(appData.secondChessProgram);
  5301.     }
  5302.     if (strcmp(appData.firstHost, "localhost") != 0) {
  5303.         sprintf(buf, "%s@%s",
  5304.             appData.firstChessProgram, appData.firstHost);
  5305.         gameInfo.black = StrSave(buf);
  5306.     } else {
  5307.         gameInfo.black = StrSave(appData.firstChessProgram);
  5308.     }
  5309.     sprintf(buf, "%d/%ld", appData.movesPerSession, timeControl/1000);
  5310.     gameInfo.timeControl = StrSave(buf);
  5311.     break;
  5312.       case EditGame:
  5313.     gameInfo.event = StrSave("Edited game");
  5314.     gameInfo.site = StrSave(HostName());
  5315.     gameInfo.date = PGNDate();
  5316.     gameInfo.round = StrSave("-");
  5317.     gameInfo.white = StrSave("-");
  5318.     gameInfo.black = StrSave("-");
  5319.     break;
  5320.       case EditPosition:
  5321.     gameInfo.event = StrSave("Edited position");
  5322.     gameInfo.site = StrSave(HostName());
  5323.     gameInfo.date = PGNDate();
  5324.     gameInfo.round = StrSave("-");
  5325.     gameInfo.white = StrSave("-");
  5326.     gameInfo.black = StrSave("-");
  5327.     break;
  5328.       case IcsPlayingWhite:
  5329.       case IcsPlayingBlack:
  5330.       case IcsObserving:
  5331.       case IcsExamining:
  5332.     break;
  5333.       case PlayFromGameFile:
  5334.     gameInfo.event = StrSave("Game from non-PGN file");
  5335.     gameInfo.site = StrSave(HostName());
  5336.     gameInfo.date = PGNDate();
  5337.     gameInfo.round = StrSave("-");
  5338.     gameInfo.white = StrSave("?");
  5339.     gameInfo.black = StrSave("?");
  5340.     break;
  5341.       default:
  5342.     break;
  5343.     }
  5344. }
  5345.  
  5346. void ParsePGNTag(tag)
  5347.      char *tag;
  5348. {
  5349.     char *name, *value, *p, *oldTags;
  5350.     int len;
  5351.  
  5352.     name = tag;
  5353.     while (!isalpha(*name) && !isdigit(*name)) name++;
  5354.     p = name;
  5355.     while (*p != ' ' && *p != '\t' && *p != '\n') p++;
  5356.     *p = NULLCHAR;
  5357.     value = strchr(p + 1, '"') + 1;
  5358.     p = strrchr(value, '"');
  5359.     *p = NULLCHAR;
  5360.  
  5361.     if (strcmp(name, "Event") == 0) {
  5362.     gameInfo.event = StrSave(value);
  5363.     } else if (strcmp(name, "Site") == 0) {
  5364.     gameInfo.site = StrSave(value);
  5365.     } else if (strcmp(name, "Date") == 0) {
  5366.     gameInfo.date = StrSave(value);
  5367.     } else if (strcmp(name, "Round") == 0) {
  5368.     gameInfo.round = StrSave(value);
  5369.     } else if (strcmp(name, "White") == 0) {
  5370.     gameInfo.white = StrSave(value);
  5371.     } else if (strcmp(name, "Black") == 0) {
  5372.     gameInfo.black = StrSave(value);
  5373.     } else if (strcmp(name, "Result") == 0) {
  5374.     if (strcmp(value, "1-0") == 0)
  5375.       gameInfo.result = WhiteWins;
  5376.     else if (strcmp(value, "0-1") == 0)
  5377.       gameInfo.result = BlackWins;
  5378.     else if (strcmp(value, "1/2-1/2") == 0)
  5379.       gameInfo.result = GameIsDrawn;
  5380.     else
  5381.       gameInfo.result = GameUnfinished;
  5382.     } else if (strcmp(name, "FEN") == 0) {
  5383.     gameInfo.fen = StrSave(value);
  5384.     } else if (strcmp(name, "SetUp") == 0) {
  5385.     /* ignore on input; presence of FEN governs */
  5386.     } else {
  5387.     if (gameInfo.extraTags == NULL)
  5388.       oldTags = "";
  5389.     else
  5390.       oldTags = gameInfo.extraTags;
  5391.     /* Buffer size includes 7 bytes of space for [ ""]\n\0 */
  5392.     len = strlen(oldTags) + strlen(value) + strlen(name) + 7;
  5393.     p = (char *) malloc(len);
  5394.     sprintf(p, "%s[%s \"%s\"]\n", oldTags, name, value);
  5395.     if (gameInfo.extraTags != NULL) free(gameInfo.extraTags);
  5396.     gameInfo.extraTags = p;
  5397.     }
  5398. }
  5399.  
  5400. char *PGNTagsStatic()
  5401. {
  5402.     static char buf[8192];
  5403.     char buf1[MSG_SIZ];
  5404.  
  5405.     buf[0] = NULLCHAR;
  5406.  
  5407.     if (gameInfo.event != NULL) {
  5408.     sprintf(buf1, "[Event \"%s\"]\n", gameInfo.event);
  5409.     strcat(buf, buf1);
  5410.     }
  5411.     if (gameInfo.site != NULL) {
  5412.     sprintf(buf1, "[Site \"%s\"]\n", gameInfo.site);
  5413.     strcat(buf, buf1);
  5414.     }
  5415.     if (gameInfo.date != NULL) {
  5416.     sprintf(buf1, "[Date \"%s\"]\n", gameInfo.date);
  5417.     strcat(buf, buf1);
  5418.     }
  5419.     if (gameInfo.round != NULL) {
  5420.     sprintf(buf1, "[Round \"%s\"]\n", gameInfo.round);
  5421.     strcat(buf, buf1);
  5422.     }
  5423.     if (gameInfo.white != NULL) {
  5424.     sprintf(buf1, "[White \"%s\"]\n", gameInfo.white);
  5425.     strcat(buf, buf1);
  5426.     }
  5427.     if (gameInfo.black != NULL) {
  5428.     sprintf(buf1, "[Black \"%s\"]\n", gameInfo.black);
  5429.     strcat(buf, buf1);
  5430.     }
  5431.     sprintf(buf1, "[Result \"%s\"]\n", PGNResult(gameInfo.result));
  5432.     strcat(buf, buf1);
  5433.     if (gameInfo.timeControl != NULL) {
  5434.     sprintf(buf1, "[TimeControl \"%s\"]\n", gameInfo.timeControl);
  5435.     strcat(buf, buf1);
  5436.     }
  5437.     if (gameInfo.extraTags != NULL) {
  5438.     strcat(buf, gameInfo.extraTags);
  5439.     }
  5440.     return buf;
  5441. }
  5442.  
  5443.  
  5444. void PrintPGNTags(fp)
  5445.      FILE *fp;
  5446. {
  5447.     fprintf(fp, "%s", PGNTagsStatic());
  5448. }
  5449.  
  5450. char *PGNTags()
  5451. {
  5452.     return StrSave(PGNTagsStatic());
  5453. }
  5454.  
  5455. void ReplaceComment(index, text)
  5456.      int index;
  5457.      char *text;
  5458. {
  5459.     int len;
  5460.  
  5461.     while (*text == '\n') text++;
  5462.     len = strlen(text);
  5463.     while (len > 0 && text[len - 1] == '\n') len--;
  5464.  
  5465.     if (commentList[index] != NULL)
  5466.       free(commentList[index]);
  5467.  
  5468.     if (len == 0) {
  5469.     commentList[index] = NULL;
  5470.     return;
  5471.     }
  5472.     commentList[index] = (char *) malloc(len + 2);
  5473.     strncpy(commentList[index], text, len);
  5474.     commentList[index][len] = '\n';
  5475.     commentList[index][len + 1] = NULLCHAR;
  5476. }
  5477.  
  5478. void AppendComment(index, text)
  5479.      int index;
  5480.      char *text;
  5481. {
  5482.     int oldlen, len;
  5483.     char *old;
  5484.  
  5485.     while (*text == '\n') text++;
  5486.     len = strlen(text);
  5487.     while (len > 0 && text[len - 1] == '\n') len--;
  5488.  
  5489.     if (len == 0) return;
  5490.  
  5491.     if (commentList[index] != NULL) {
  5492.     old = commentList[index];
  5493.     oldlen = strlen(old);
  5494.     commentList[index] = (char *) malloc(oldlen + len + 2);
  5495.     strcpy(commentList[index], old);
  5496.     free(old);
  5497.     strncpy(&commentList[index][oldlen], text, len);
  5498.     commentList[index][oldlen + len] = '\n';
  5499.     commentList[index][oldlen + len + 1] = NULLCHAR;
  5500.     } else {
  5501.     commentList[index] = (char *) malloc(len + 2);
  5502.     strncpy(commentList[index], text, len);
  5503.     commentList[index][len] = '\n';
  5504.     commentList[index][len + 1] = NULLCHAR;
  5505.     }
  5506. }
  5507.  
  5508. void SendToProgram(message, pr)
  5509.      char *message;
  5510.      ProcRef pr;
  5511. {
  5512.     int count, outCount, error;
  5513.     char *which;
  5514.     char buf[MSG_SIZ];
  5515.  
  5516.     if (pr == NULL) return;
  5517.     Attention(pr);
  5518.     lastMsgPR = pr;
  5519.     which = (pr == firstProgramPR) ? "first" : "second";
  5520.     
  5521.     if (appData.debugMode)
  5522.       fprintf(debugFP, "Sending to %s: %s", which, message);
  5523.     
  5524.     count = strlen(message);
  5525.     outCount = OutputToProcess(pr, message, count, &error);
  5526.     if (outCount < count) {
  5527.     sprintf(buf, "Error writing to %s chess program", which);
  5528.     DisplayFatalError(buf, error, 1);
  5529.     }
  5530. }
  5531.  
  5532. void ReceiveFromProgram(isr, message, count, error)
  5533.      InputSourceRef isr;
  5534.      char *message;
  5535.      int count;
  5536.      int error;
  5537. {
  5538.     char *end_str, *name, *which;
  5539.     char buf[MSG_SIZ];
  5540.  
  5541.     if (count <= 0) {
  5542.     if (isr == firstProgramISR) {
  5543.         which = "first";
  5544.         name = appData.firstChessProgram;
  5545.     } else if (isr == secondProgramISR) {
  5546.         which = "second";
  5547.         name = appData.secondChessProgram;
  5548.     } else {
  5549.         return;
  5550.     }
  5551.     if (count == 0) {
  5552.         sprintf(buf,
  5553.             "Error: %s chess program (%s) exited unexpectedly",
  5554.             which, name);
  5555.         RemoveInputSource(isr);
  5556.         DisplayFatalError(buf, 0, -1); /* don't exit */
  5557.     } else {
  5558.         sprintf(buf,
  5559.             "Error reading from %s chess program (%s)",
  5560.             which, name);
  5561.         RemoveInputSource(isr);
  5562.         DisplayFatalError(buf, error, -1); /* don't exit */
  5563.     }
  5564.     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
  5565.     return;
  5566.     }
  5567.     
  5568.     if ((end_str = strchr(message, '\r')) != NULL)
  5569.       *end_str = NULLCHAR;
  5570.     if ((end_str = strchr(message, '\n')) != NULL)
  5571.       *end_str = NULLCHAR;
  5572.     
  5573.     if (appData.debugMode) {
  5574.     fprintf(debugFP, "Received from %s: %s\n",
  5575.         isr == firstProgramISR ? "first" : "second", message);
  5576.     }
  5577.     HandleMachineMove(message, isr);
  5578. }
  5579.  
  5580.  
  5581. void SendSearchDepth(pr)
  5582.      ProcRef pr;
  5583. {
  5584.     char message[MSG_SIZ];
  5585.     
  5586.     if (appData.searchDepth <= 0) return;
  5587.     
  5588.     sprintf(message, "depth\n%d\nhelp\n", appData.searchDepth);
  5589.     /* note kludge: "help" command forces gnuchessx to print
  5590.        out something that ends with a newline. */
  5591.     SendToProgram(message, pr);
  5592. }
  5593.  
  5594. void SendTimeRemaining(pr)
  5595.      ProcRef pr;
  5596. {
  5597.     char message[MSG_SIZ];
  5598.     long time, otime;
  5599.  
  5600.     /* Note: this routine must be called when the clocks are stopped
  5601.        or when they have *just* been set or switched; otherwise
  5602.        it will be off by the time since the current tick started.
  5603.     */
  5604.     if (WhiteOnMove(forwardMostMove)) {
  5605.     time = whiteTimeRemaining / 10;
  5606.     otime = (blackTimeRemaining - ics_increment) / 10;
  5607.     } else {
  5608.     time = blackTimeRemaining / 10;
  5609.     otime = (whiteTimeRemaining - ics_increment) / 10;
  5610.     }
  5611.     if (time <= 0) time = 1;
  5612.     if (otime <= 0) otime = 1;
  5613.     
  5614.     sprintf(message, "time %ld\notim %ld\n", time, otime);
  5615.     SendToProgram(message, pr);
  5616. }
  5617.  
  5618. void ShowThinkingEvent(newState)
  5619.      int newState;
  5620. {
  5621.     char *command;
  5622.  
  5623.     if (newState == appData.showThinking) return;
  5624.     switch (gameMode) {
  5625.       case MachinePlaysWhite:
  5626.     if (WhiteOnMove(forwardMostMove)) {
  5627.         DisplayError("Wait until your turn", 0);
  5628.         return;
  5629.     }
  5630.     break;
  5631.       case BeginningOfGame:
  5632.       case MachinePlaysBlack:
  5633.     if (!WhiteOnMove(forwardMostMove)) {
  5634.         DisplayError("Wait until your turn", 0);
  5635.         return;
  5636.     }
  5637.     break;
  5638.       case EditPosition:
  5639.     EditPositionDone();
  5640.     break;
  5641.       case TwoMachinesPlay:
  5642.     return;
  5643.       default:
  5644.     break;
  5645.     }
  5646.     if (newState) {
  5647.     command = "post\n";
  5648.     } else {
  5649.     command = "nopost\n";
  5650.     }
  5651.     SendToProgram(command, firstProgramPR);
  5652.     appData.showThinking = newState;
  5653. }
  5654.  
  5655. void DisplayMove(moveNumber)
  5656.      int moveNumber;
  5657. {
  5658.     char message[MSG_SIZ];
  5659.     char res[MSG_SIZ];
  5660.  
  5661.     if (moveNumber == forwardMostMove - 1 &&
  5662.     gameInfo.resultDetails != NULL) {
  5663.     if (gameInfo.resultDetails[0] == NULLCHAR) {
  5664.         sprintf(res, " %s", PGNResult(gameInfo.result));
  5665.     } else {
  5666.         sprintf(res, " {%s} %s",
  5667.             gameInfo.resultDetails, PGNResult(gameInfo.result));
  5668.     }
  5669.     } else {
  5670.     res[0] = NULLCHAR;
  5671.     }
  5672.     
  5673.     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
  5674.     DisplayMessage(res, NULL);
  5675.     } else {
  5676.     sprintf(message, "%d. %s%s%s", moveNumber / 2 + 1,
  5677.         WhiteOnMove(moveNumber) ? "" : "... ",
  5678.         parseList[moveNumber], res);
  5679.     DisplayMessage(message,
  5680.                moveNumber == forwardMostMove - 1 ? thinkOutput : NULL);
  5681.     }
  5682. }
  5683.  
  5684. void DisplayComment(moveNumber, text)
  5685.      int moveNumber;
  5686.      char *text;
  5687. {
  5688.     char title[MSG_SIZ];
  5689.  
  5690.     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
  5691.     strcpy(title, "Comment");
  5692.     } else {
  5693.     sprintf(title, "Comment on %d. %s%s", moveNumber / 2 + 1,
  5694.         WhiteOnMove(moveNumber) ? "" : "... ",
  5695.         parseList[moveNumber]);
  5696.     }
  5697.  
  5698.     CommentPopUp(title, text);
  5699. }
  5700.  
  5701. /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
  5702.  * might be busy thinking on our time.  It can be omitted if your
  5703.  * gnuchess is configured to stop thinking immediately on any user
  5704.  * input.  However, that gnuchess feature depends on the FIONREAD
  5705.  * ioctl, which does not work properly on some flavors of Unix.
  5706.  */
  5707. void Attention(pr)
  5708.      ProcRef pr;
  5709. {
  5710. #if defined(ATTENTION)
  5711.     if (appData.noChessProgram || (pr == NoProc)) return;
  5712.     switch (gameMode) {
  5713.       case MachinePlaysWhite:
  5714.       case MachinePlaysBlack:
  5715.       case TwoMachinesPlay:
  5716.       case IcsPlayingWhite:
  5717.       case IcsPlayingBlack:
  5718.     if (forwardMostMove > backwardMostMove + 1 && maybePondering) {
  5719.         if (appData.debugMode)
  5720.           fprintf(debugFP, "Interrupting %s\n",
  5721.               pr == firstProgramPR ? "first" : "second");
  5722.         InterruptChildProcess(pr);
  5723.         maybePondering = FALSE;
  5724.     }
  5725.     break;
  5726.       default:
  5727.     break;
  5728.     }
  5729. #endif /*ATTENTION*/
  5730. }
  5731.  
  5732. void CheckFlags()
  5733. {
  5734.     if (whiteTimeRemaining <= 0) {
  5735.     if (!whiteFlag) {
  5736.         whiteFlag = TRUE;
  5737.         if (appData.icsActive) {
  5738.         if (appData.autoCallFlag &&
  5739.             gameMode == IcsPlayingBlack && !blackFlag)
  5740.           SendToICS("flag\n");
  5741.         } else {
  5742.         if (blackFlag)
  5743.           DisplayTitle("Both flags fell");
  5744.         else
  5745.           DisplayTitle("White's flag fell");
  5746.         }
  5747.     }
  5748.     }
  5749.     if (blackTimeRemaining <= 0) {
  5750.     if (!blackFlag) {
  5751.         blackFlag = TRUE;
  5752.         if (appData.icsActive) {
  5753.         if (appData.autoCallFlag &&
  5754.             gameMode == IcsPlayingWhite && !whiteFlag)
  5755.           SendToICS("flag\n");
  5756.         } else {
  5757.         if (whiteFlag)
  5758.           DisplayTitle("Both flags fell");
  5759.         else
  5760.           DisplayTitle("Black's flag fell");
  5761.         }
  5762.     }
  5763.     }
  5764. }
  5765.  
  5766. void CheckTimeControl()
  5767. {
  5768.     if (!appData.clockMode || appData.icsActive ||
  5769.     gameMode == PlayFromGameFile || forwardMostMove == 0) return;
  5770.     /*
  5771.      * add time to clocks when time control is achieved
  5772.      */
  5773.     if ((forwardMostMove % (appData.movesPerSession * 2)) == 0) {
  5774.     whiteTimeRemaining += timeControl;
  5775.     blackTimeRemaining += timeControl;
  5776.     }
  5777. }
  5778.  
  5779. void DisplayBothClocks()
  5780. {
  5781.     DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
  5782.     DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
  5783. }
  5784.  
  5785.  
  5786. /* Timekeeping seems to be a portability nightmare.  I think everyone
  5787.    has ftime(), but I'm really not sure, so I'm including some ifdefs
  5788.    to use other calls if you don't.  Clocks will be less accurate if
  5789.    you have neither ftime nor gettimeofday.
  5790. */
  5791.  
  5792. /* Get the current time as a TimeMark */
  5793. void GetTimeMark(tm)
  5794.      TimeMark *tm;
  5795. {
  5796. #if HAVE_GETTIMEOFDAY
  5797.  
  5798.     struct timeval timeVal;
  5799.     struct timezone timeZone;
  5800.  
  5801.     gettimeofday(&timeVal, &timeZone);
  5802.     tm->sec = (long) timeVal.tv_sec; 
  5803.     tm->ms = (int) (timeVal.tv_usec / 1000L);
  5804.  
  5805. #else /*!HAVE_GETTIMEOFDAY*/
  5806. #if HAVE_FTIME
  5807.  
  5808. #include <sys/timeb.h>
  5809.     struct timeb timeB;
  5810.  
  5811.     ftime(&timeB);
  5812.     tm->sec = (long) timeB.time;
  5813.     tm->ms = (int) timeB.millitm;
  5814.  
  5815. #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
  5816.     tm->sec = (long) time(NULL);
  5817.     tm->ms = 0;
  5818. #endif
  5819. #endif
  5820. }
  5821.  
  5822. /* Return the difference in milliseconds between two
  5823.    time marks.  We assume the difference will fit in a long!
  5824. */
  5825. long SubtractTimeMarks(tm2, tm1)
  5826.      TimeMark *tm2, *tm1;
  5827. {
  5828.     return 1000L*(tm2->sec - tm1->sec) +
  5829.            (long) (tm2->ms - tm1->ms);
  5830. }
  5831.  
  5832.  
  5833. /*
  5834.  * Code to manage the game clocks.
  5835.  *
  5836.  * In tournament play, black starts the clock and then white makes a move.
  5837.  * We give the human user a slight advantage if he is playing white---the
  5838.  * clocks don't run until he makes his first move, so it takes zero time.
  5839.  * Also, we doesn't account for network lag, so we could get
  5840.  * out of sync with GNU Chess's clock -- but then, referees are always right.
  5841.  */
  5842.  
  5843. static TimeMark tickStartTM;
  5844. static long intendedTickLength;
  5845.  
  5846. long NextTickLength(timeRemaining)
  5847.      long timeRemaining;
  5848. {
  5849.     long nominalTickLength, nextTickLength;
  5850.  
  5851.     if (timeRemaining > 0L && timeRemaining <= 1000L)
  5852.       nominalTickLength = 100L;
  5853.     else
  5854.       nominalTickLength = 1000L;
  5855.     nextTickLength = timeRemaining % nominalTickLength;
  5856.     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
  5857.  
  5858.     return nextTickLength;
  5859. }
  5860.  
  5861. /* Stop clocks and reset to a fresh time control */
  5862. void ResetClocks() 
  5863. {
  5864.     (void) StopClockTimer();
  5865.     whiteTimeRemaining = timeControl;
  5866.     blackTimeRemaining = timeControl;
  5867.     if (whiteFlag || blackFlag) {
  5868.     DisplayTitle("");
  5869.     whiteFlag = blackFlag = FALSE;
  5870.     }
  5871.     DisplayBothClocks();
  5872. }
  5873.     
  5874. #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
  5875.  
  5876. /* Decrement running clock by amount of time that has passed */
  5877. void DecrementClocks()
  5878. {
  5879.     long timeRemaining;
  5880.     long lastTickLength, fudge;
  5881.     TimeMark now;
  5882.  
  5883.     if (!appData.clockMode) return;
  5884.     
  5885.     GetTimeMark(&now);
  5886.  
  5887.     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
  5888.  
  5889.     /* Fudge if we woke up a little too soon */
  5890.     fudge = intendedTickLength - lastTickLength;
  5891.     if (fudge < 0 || fudge > FUDGE) fudge = 0;
  5892.  
  5893.     if (WhiteOnMove(forwardMostMove)) {
  5894.     timeRemaining = whiteTimeRemaining -= lastTickLength;
  5895.     DisplayWhiteClock(whiteTimeRemaining - fudge,
  5896.               WhiteOnMove(currentMove));
  5897.     } else {
  5898.     timeRemaining = blackTimeRemaining -= lastTickLength;
  5899.     DisplayBlackClock(blackTimeRemaining - fudge,
  5900.               !WhiteOnMove(currentMove));
  5901.     }
  5902.     
  5903.     CheckFlags();
  5904.     
  5905.     tickStartTM = now;
  5906.     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
  5907.     StartClockTimer(intendedTickLength);
  5908. }
  5909.  
  5910.     
  5911. /* A player has just moved, so stop the previously running
  5912.    clock and (if in clock mode) start the other one.
  5913.    We redisplay both clocks in case we're in ICS mode, because
  5914.    ICS gives us an update to both clocks after every move.
  5915. */
  5916. void SwitchClocks()
  5917. {
  5918.     long lastTickLength;
  5919.     TimeMark now;
  5920.  
  5921.     GetTimeMark(&now);
  5922.  
  5923.     if (StopClockTimer() && appData.clockMode) {
  5924.     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
  5925.     if (WhiteOnMove(forwardMostMove)) {
  5926.         whiteTimeRemaining -= lastTickLength;
  5927.     } else {
  5928.         blackTimeRemaining -= lastTickLength;
  5929.     }
  5930.     CheckFlags();
  5931.     }
  5932.     CheckTimeControl();
  5933.     if (!((gameMode == PlayFromGameFile) &&
  5934.       (matchMode || (appData.timeDelay == 0 && !pausing)))) {
  5935.     DisplayBothClocks();
  5936.     }
  5937.  
  5938.     if (!appData.clockMode) return;
  5939.  
  5940.     switch (gameMode) {
  5941.       case MachinePlaysBlack:
  5942.       case MachinePlaysWhite:
  5943.       case BeginningOfGame:
  5944.     if (pausing) return;
  5945.     break;
  5946.  
  5947.       case EditGame:
  5948.       case PlayFromGameFile:
  5949.       case IcsExamining:
  5950.     return;
  5951.  
  5952.       default:
  5953.     break;
  5954.     }
  5955.  
  5956.     tickStartTM = now;
  5957.     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
  5958.       whiteTimeRemaining : blackTimeRemaining);
  5959.     StartClockTimer(intendedTickLength);
  5960. }
  5961.     
  5962.  
  5963. /* Stop both clocks */
  5964. void StopClocks()
  5965. {    
  5966.     long lastTickLength;
  5967.     TimeMark now;
  5968.  
  5969.     if (!StopClockTimer()) return;
  5970.     if (!appData.clockMode) return;
  5971.  
  5972.     GetTimeMark(&now);
  5973.  
  5974.     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
  5975.     if (WhiteOnMove(forwardMostMove)) {
  5976.     whiteTimeRemaining -= lastTickLength;
  5977.     DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
  5978.     } else {
  5979.     blackTimeRemaining -= lastTickLength;
  5980.     DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
  5981.     }
  5982.     CheckFlags();
  5983. }
  5984.     
  5985. /* Start clock of player on move.  Time may have been reset, so
  5986.    if clock is already running, stop and restart it. */
  5987. void StartClocks()
  5988. {
  5989.     (void) StopClockTimer(); /* in case it was running already */
  5990.     DisplayBothClocks();
  5991.  
  5992.     if (!appData.clockMode) return;
  5993.  
  5994.     GetTimeMark(&tickStartTM);
  5995.     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
  5996.       whiteTimeRemaining : blackTimeRemaining);
  5997.     StartClockTimer(intendedTickLength);
  5998. }
  5999.  
  6000. char *TimeString(ms)
  6001.      long ms;
  6002. {
  6003.     long second, minute, hour, day;
  6004.     char *sign = "";
  6005.     static char buf[32];
  6006.     
  6007.     if (ms > 0 && ms <= 900) {
  6008.     /* convert milliseconds to tenths, rounding up */
  6009.     sprintf(buf, " 0.%1ld ", (ms+99L)/100L);
  6010.     return buf;
  6011.     }
  6012.  
  6013.     /* convert milliseconds to seconds, rounding up */
  6014.     /* use floating point to avoid strangeness of integer division
  6015.        with negative dividends on many machines */
  6016.     second = (long) floor(((double) (ms + 999L)) / 1000.0);
  6017.  
  6018.     if (second < 0) {
  6019.     sign = "-";
  6020.     second = -second;
  6021.     }
  6022.     
  6023.     day = second / (60 * 60 * 24);
  6024.     second = second % (60 * 60 * 24);
  6025.     hour = second / (60 * 60);
  6026.     second = second % (60 * 60);
  6027.     minute = second / 60;
  6028.     second = second % 60;
  6029.     
  6030.     if (day > 0)
  6031.       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
  6032.           sign, day, hour, minute, second);
  6033.     else if (hour > 0)
  6034.       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
  6035.     else
  6036.       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
  6037.     
  6038.     return buf;
  6039. }
  6040.  
  6041.  
  6042. /*
  6043.  * This is necessary because some C libraries aren't ANSI C compliant yet.
  6044.  */
  6045. char *StrStr(string, match)
  6046.      char *string, *match;
  6047. {
  6048.     int i, length;
  6049.     
  6050.     length = strlen(match);
  6051.     
  6052.     for (i = strlen(string) - length; i >= 0; i--, string++)
  6053.       if (!strncmp(match, string, length))
  6054.     return string;
  6055.     
  6056.     return NULL;
  6057. }
  6058.  
  6059. #ifndef _amigados
  6060. int StrCaseCmp(s1, s2)
  6061.      char *s1, *s2;
  6062. {
  6063.     char c1, c2;
  6064.     
  6065.     for (;;) {
  6066.     c1 = ToLower(*s1++);
  6067.     c2 = ToLower(*s2++);
  6068.     if (c1 > c2) return 1;
  6069.     if (c1 < c2) return -1;
  6070.     if (c1 == NULLCHAR) return 0;
  6071.     }
  6072. }
  6073.  
  6074.  
  6075. int ToLower(c)
  6076.      int c;
  6077. {
  6078.     return isupper(c) ? tolower(c) : c;
  6079. }
  6080.  
  6081.  
  6082. int ToUpper(c)
  6083.      int c;
  6084. {
  6085.     return islower(c) ? toupper(c) : c;
  6086. }
  6087. #endif /* !_amigados    */
  6088.  
  6089. char *StrSave(s)
  6090.      char *s;
  6091. {
  6092.     char *ret;
  6093.  
  6094.     ret = (char *) malloc(strlen(s) + 1);
  6095.     strcpy(ret, s);
  6096.     return ret;
  6097. }
  6098.  
  6099. char *PGNDate()
  6100. {
  6101.     time_t clock;
  6102.     struct tm *tm;
  6103.     char buf[MSG_SIZ];
  6104.  
  6105.     clock = time((time_t *)NULL);
  6106.     tm = localtime(&clock);
  6107.     sprintf(buf, "%04d.%02d.%02d",
  6108.         tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
  6109.     return StrSave(buf);
  6110. }
  6111.  
  6112. char *PGNResult(result)
  6113.      ChessMove result;
  6114. {
  6115.     switch (result) {
  6116.       case GameUnfinished:
  6117.       default:
  6118.     return "*";
  6119.       case WhiteWins:
  6120.     return "1-0";
  6121.       case BlackWins:
  6122.     return "0-1";
  6123.       case GameIsDrawn:
  6124.     return "1/2-1/2";
  6125.     }
  6126. }
  6127.  
  6128. char *PositionToFEN(move)
  6129.      int move;
  6130. {
  6131.     int i, j, fromX, fromY, toX, toY;
  6132.     int whiteToPlay;
  6133.     char buf[128];
  6134.     char *p, *q;
  6135.     int emptycount;
  6136.  
  6137.     whiteToPlay = (gameMode == EditPosition) ?
  6138.       !blackPlaysFirst : (move % 2 == 0);
  6139.     p = buf;
  6140.  
  6141.     /* Piece placement data */
  6142.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  6143.     emptycount = 0;
  6144.     for (j = 0; j < BOARD_SIZE; j++) {
  6145.         if (boards[move][i][j] == EmptySquare) {
  6146.         emptycount++;
  6147.         } else {
  6148.         if (emptycount > 0) {
  6149.             *p++ = '0' + emptycount;
  6150.             emptycount = 0;
  6151.         }
  6152.         *p++ = PieceToChar(boards[move][i][j]);
  6153.         }
  6154.     }
  6155.     if (emptycount > 0) {
  6156.         *p++ = '0' + emptycount;
  6157.         emptycount = 0;
  6158.     }
  6159.     *p++ = '/';
  6160.     }
  6161.     *(p - 1) = ' ';
  6162.  
  6163.     /* Active color */
  6164.     *p++ = whiteToPlay ? 'w' : 'b';
  6165.     *p++ = ' ';
  6166.  
  6167.     /* !!We don't keep track of castling availability, so fake it */
  6168.     q = p;
  6169.     if (boards[move][0][4] == WhiteKing) {
  6170.     if (boards[move][0][7] == WhiteRook) *p++ = 'K';
  6171.     if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
  6172.     }
  6173.     if (boards[move][7][4] == BlackKing) {
  6174.     if (boards[move][7][7] == BlackRook) *p++ = 'k';
  6175.     if (boards[move][7][0] == BlackRook) *p++ = 'q';
  6176.     }        
  6177.     if (q == p) *p++ = '-';
  6178.     *p++ = ' ';
  6179.  
  6180.     /* En passant target square */
  6181.     if (move > backwardMostMove) {
  6182.     fromX = moveList[move - 1][0] - 'a';
  6183.     fromY = moveList[move - 1][1] - '1';
  6184.     toX = moveList[move - 1][2] - 'a';
  6185.     toY = moveList[move - 1][3] - '1';
  6186.     if (fromY == (whiteToPlay ? 6 : 1) &&
  6187.         toY == (whiteToPlay ? 4 : 3) &&
  6188.         boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
  6189.         fromX == toX) {
  6190.         /* 2-square pawn move just happened */
  6191.         *p++ = toX + 'a';
  6192.         *p++ = whiteToPlay ? '6' : '3';
  6193.     } else {
  6194.         *p++ = '-';
  6195.     }
  6196.     } else {
  6197.     *p++ = '-';
  6198.     }
  6199.  
  6200.     /* !!We don't keep track of halfmove clock for 50-move rule */
  6201.     strcpy(p, " 0 ");
  6202.     p += 3;
  6203.  
  6204.     /* Fullmove number */
  6205.     sprintf(p, "%d", (move / 2) + 1);
  6206.     
  6207.     return StrSave(buf);
  6208. }
  6209.  
  6210. Boolean ParseFEN(board, blackPlaysFirst, fen)
  6211.      Board board;
  6212.      int *blackPlaysFirst;
  6213.      char *fen;
  6214. {
  6215.     int i, j;
  6216.     char *p;
  6217.     int emptycount;
  6218.  
  6219.     p = fen;
  6220.  
  6221.     /* Piece placement data */
  6222.     for (i = BOARD_SIZE - 1; i >= 0; i--) {
  6223.     j = 0;
  6224.     while (j < BOARD_SIZE) {
  6225.         if (isdigit(*p)) {
  6226.         emptycount = *p++ - '0';
  6227.         if (j + emptycount > BOARD_SIZE) return FALSE;
  6228.         while (emptycount--) board[i][j++] = EmptySquare;
  6229.         } else {
  6230.         board[i][j++] = CharToPiece(*p++);
  6231.         }
  6232.     }
  6233.     if (*p != '/' && *p != ' ') return FALSE;
  6234.     p++;
  6235.     }
  6236.  
  6237.     /* Active color */
  6238.     switch (*p) {
  6239.       case 'w':
  6240.     *blackPlaysFirst = FALSE;
  6241.     break;
  6242.       case 'b': 
  6243.     *blackPlaysFirst = TRUE;
  6244.     break;
  6245.       default:
  6246.     return FALSE;
  6247.     }
  6248.     
  6249.     /* !!We ignore the rest of the FEN notation */
  6250.     return TRUE;
  6251. }
  6252.