changes from H.G. Muller; version 4.3.2
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.\r
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.\r
7  *\r
8  * The following terms apply to Digital Equipment Corporation's copyright\r
9  * interest in XBoard:\r
10  * ------------------------------------------------------------------------\r
11  * All Rights Reserved\r
12  *\r
13  * Permission to use, copy, modify, and distribute this software and its\r
14  * documentation for any purpose and without fee is hereby granted,\r
15  * provided that the above copyright notice appear in all copies and that\r
16  * both that copyright notice and this permission notice appear in\r
17  * supporting documentation, and that the name of Digital not be\r
18  * used in advertising or publicity pertaining to distribution of the\r
19  * software without specific, written prior permission.\r
20  *\r
21  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
22  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
23  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
24  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
25  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
26  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
27  * SOFTWARE.\r
28  * ------------------------------------------------------------------------\r
29  *\r
30  * The following terms apply to the enhanced version of XBoard distributed\r
31  * by the Free Software Foundation:\r
32  * ------------------------------------------------------------------------\r
33  * This program is free software; you can redistribute it and/or modify\r
34  * it under the terms of the GNU General Public License as published by\r
35  * the Free Software Foundation; either version 2 of the License, or\r
36  * (at your option) any later version.\r
37  *\r
38  * This program is distributed in the hope that it will be useful,\r
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
41  * GNU General Public License for more details.\r
42  *\r
43  * You should have received a copy of the GNU General Public License\r
44  * along with this program; if not, write to the Free Software\r
45  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
46  * ------------------------------------------------------------------------\r
47  *\r
48  * See the file ChangeLog for a revision history.  */\r
49 \r
50 /* [AS] Also useful here for debugging */\r
51 #ifdef WIN32\r
52 #include <windows.h>\r
53 \r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
55 \r
56 #else\r
57 \r
58 #define DoSleep( n )\r
59 \r
60 #endif\r
61 \r
62 #include "config.h"\r
63 \r
64 #include <assert.h>\r
65 #include <stdio.h>\r
66 #include <ctype.h>\r
67 #include <errno.h>\r
68 #include <sys/types.h>\r
69 #include <sys/stat.h>\r
70 #include <math.h>\r
71 \r
72 #if STDC_HEADERS\r
73 # include <stdlib.h>\r
74 # include <string.h>\r
75 #else /* not STDC_HEADERS */\r
76 # if HAVE_STRING_H\r
77 #  include <string.h>\r
78 # else /* not HAVE_STRING_H */\r
79 #  include <strings.h>\r
80 # endif /* not HAVE_STRING_H */\r
81 #endif /* not STDC_HEADERS */\r
82 \r
83 #if HAVE_SYS_FCNTL_H\r
84 # include <sys/fcntl.h>\r
85 #else /* not HAVE_SYS_FCNTL_H */\r
86 # if HAVE_FCNTL_H\r
87 #  include <fcntl.h>\r
88 # endif /* HAVE_FCNTL_H */\r
89 #endif /* not HAVE_SYS_FCNTL_H */\r
90 \r
91 #if TIME_WITH_SYS_TIME\r
92 # include <sys/time.h>\r
93 # include <time.h>\r
94 #else\r
95 # if HAVE_SYS_TIME_H\r
96 #  include <sys/time.h>\r
97 # else\r
98 #  include <time.h>\r
99 # endif\r
100 #endif\r
101 \r
102 #if defined(_amigados) && !defined(__GNUC__)\r
103 struct timezone {\r
104     int tz_minuteswest;\r
105     int tz_dsttime;\r
106 };\r
107 extern int gettimeofday(struct timeval *, struct timezone *);\r
108 #endif\r
109 \r
110 #if HAVE_UNISTD_H\r
111 # include <unistd.h>\r
112 #endif\r
113 \r
114 #include "common.h"\r
115 #include "frontend.h"\r
116 #include "backend.h"\r
117 #include "parser.h"\r
118 #include "moves.h"\r
119 #if ZIPPY\r
120 # include "zippy.h"\r
121 #endif\r
122 #include "backendz.h"\r
123 \r
124 /* A point in time */\r
125 typedef struct {\r
126     long sec;  /* Assuming this is >= 32 bits */\r
127     int ms;    /* Assuming this is >= 16 bits */\r
128 } TimeMark;\r
129 \r
130 /* Search stats from chessprogram */\r
131 typedef struct {\r
132   char movelist[2*MSG_SIZ]; /* Last PV we were sent */\r
133   int depth;              /* Current search depth */\r
134   int nr_moves;           /* Total nr of root moves */\r
135   int moves_left;         /* Moves remaining to be searched */\r
136   char move_name[MOVE_LEN];  /* Current move being searched, if provided */\r
137   unsigned long nodes;    /* # of nodes searched */\r
138   int time;               /* Search time (centiseconds) */\r
139   int score;              /* Score (centipawns) */\r
140   int got_only_move;      /* If last msg was "(only move)" */\r
141   int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */\r
142   int ok_to_send;         /* handshaking between send & recv */\r
143   int line_is_book;       /* 1 if movelist is book moves */\r
144   int seen_stat;          /* 1 if we've seen the stat01: line */\r
145 } ChessProgramStats;\r
146 \r
147 int establish P((void));\r
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
149                          char *buf, int count, int error));\r
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
151                       char *buf, int count, int error));\r
152 void SendToICS P((char *s));\r
153 void SendToICSDelayed P((char *s, long msdelay));\r
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
155                       int toX, int toY));\r
156 void InitPosition P((int redraw));\r
157 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
158 int AutoPlayOneMove P((void));\r
159 int LoadGameOneMove P((ChessMove readAhead));\r
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
161 int LoadPositionFromFile P((char *filename, int n, char *title));\r
162 int SavePositionToFile P((char *filename));\r
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
164                   Board board));\r
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
166 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
168                    /*char*/int promoChar));\r
169 void BackwardInner P((int target));\r
170 void ForwardInner P((int target));\r
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
172 void EditPositionDone P((void));\r
173 void PrintOpponents P((FILE *fp));\r
174 void PrintPosition P((FILE *fp, int move));\r
175 void StartChessProgram P((ChessProgramState *cps));\r
176 void SendToProgram P((char *message, ChessProgramState *cps));\r
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
179                            char *buf, int count, int error));\r
180 void SendTimeControl P((ChessProgramState *cps,\r
181                         int mps, long tc, int inc, int sd, int st));\r
182 char *TimeControlTagValue P((void));\r
183 void Attention P((ChessProgramState *cps));\r
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
185 void ResurrectChessProgram P((void));\r
186 void DisplayComment P((int moveNumber, char *text));\r
187 void DisplayMove P((int moveNumber));\r
188 void DisplayAnalysis P((void));\r
189 \r
190 void ParseGameHistory P((char *game));\r
191 void ParseBoard12 P((char *string));\r
192 void StartClocks P((void));\r
193 void SwitchClocks P((void));\r
194 void StopClocks P((void));\r
195 void ResetClocks P((void));\r
196 char *PGNDate P((void));\r
197 void SetGameInfo P((void));\r
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
199 int RegisterMove P((void));\r
200 void MakeRegisteredMove P((void));\r
201 void TruncateGame P((void));\r
202 int looking_at P((char *, int *, char *));\r
203 void CopyPlayerNameIntoFileName P((char **, char *));\r
204 char *SavePart P((char *));\r
205 int SaveGameOldStyle P((FILE *));\r
206 int SaveGamePGN P((FILE *));\r
207 void GetTimeMark P((TimeMark *));\r
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
209 int CheckFlags P((void));\r
210 long NextTickLength P((long));\r
211 void CheckTimeControl P((void));\r
212 void show_bytes P((FILE *, char *, int));\r
213 int string_to_rating P((char *str));\r
214 void ParseFeatures P((char* args, ChessProgramState *cps));\r
215 void InitBackEnd3 P((void));\r
216 void FeatureDone P((ChessProgramState* cps, int val));\r
217 void InitChessProgram P((ChessProgramState *cps));\r
218 \r
219 void GetInfoFromComment( int, char * );\r
220 \r
221 extern int tinyLayout, smallLayout;\r
222 static ChessProgramStats programStats;\r
223 \r
224 /* States for ics_getting_history */\r
225 #define H_FALSE 0\r
226 #define H_REQUESTED 1\r
227 #define H_GOT_REQ_HEADER 2\r
228 #define H_GOT_UNREQ_HEADER 3\r
229 #define H_GETTING_MOVES 4\r
230 #define H_GOT_UNWANTED_HEADER 5\r
231 \r
232 /* whosays values for GameEnds */\r
233 #define GE_ICS 0\r
234 #define GE_ENGINE 1\r
235 #define GE_PLAYER 2\r
236 #define GE_FILE 3\r
237 #define GE_XBOARD 4\r
238 #define GE_ENGINE1 5\r
239 #define GE_ENGINE2 6\r
240 \r
241 /* Maximum number of games in a cmail message */\r
242 #define CMAIL_MAX_GAMES 20\r
243 \r
244 /* Different types of move when calling RegisterMove */\r
245 #define CMAIL_MOVE   0\r
246 #define CMAIL_RESIGN 1\r
247 #define CMAIL_DRAW   2\r
248 #define CMAIL_ACCEPT 3\r
249 \r
250 /* Different types of result to remember for each game */\r
251 #define CMAIL_NOT_RESULT 0\r
252 #define CMAIL_OLD_RESULT 1\r
253 #define CMAIL_NEW_RESULT 2\r
254 \r
255 /* Telnet protocol constants */\r
256 #define TN_WILL 0373\r
257 #define TN_WONT 0374\r
258 #define TN_DO   0375\r
259 #define TN_DONT 0376\r
260 #define TN_IAC  0377\r
261 #define TN_ECHO 0001\r
262 #define TN_SGA  0003\r
263 #define TN_PORT 23\r
264 \r
265 /* [AS] */\r
266 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
267 {\r
268     assert( dst != NULL );\r
269     assert( src != NULL );\r
270     assert( count > 0 );\r
271 \r
272     strncpy( dst, src, count );\r
273     dst[ count-1 ] = '\0';\r
274     return dst;\r
275 }\r
276 \r
277 static char * safeStrCat( char * dst, const char * src, size_t count )\r
278 {\r
279     size_t  dst_len;\r
280 \r
281     assert( dst != NULL );\r
282     assert( src != NULL );\r
283     assert( count > 0 );\r
284 \r
285     dst_len = strlen(dst);\r
286 \r
287     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
288 \r
289     safeStrCpy( dst + dst_len, src, count - dst_len );\r
290 \r
291     return dst;\r
292 }\r
293 \r
294 /* Fake up flags for now, as we aren't keeping track of castling\r
295    availability yet */\r
296 int\r
297 PosFlags(index)\r
298 {\r
299   int flags = F_ALL_CASTLE_OK;\r
300   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
301   switch (gameInfo.variant) {\r
302   case VariantSuicide:\r
303   case VariantGiveaway:\r
304     flags |= F_IGNORE_CHECK;\r
305     flags &= ~F_ALL_CASTLE_OK;\r
306     break;\r
307   case VariantAtomic:\r
308     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
309     break;\r
310   case VariantKriegspiel:\r
311     flags |= F_KRIEGSPIEL_CAPTURE;\r
312     break;\r
313   case VariantNoCastle:\r
314     flags &= ~F_ALL_CASTLE_OK;\r
315     break;\r
316   default:\r
317     break;\r
318   }\r
319   return flags;\r
320 }\r
321 \r
322 FILE *gameFileFP, *debugFP;\r
323 \r
324 /* \r
325     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
326     into a fixed-size buffer. Because of this, we must be prepared to\r
327     receive strings as long as the size of the input buffer, which is currently\r
328     set to 4K for Windows and 8K for the rest.\r
329     So, we must either allocate sufficiently large buffers here, or\r
330     reduce the size of the input buffer in the input reading part.\r
331 */\r
332 \r
333 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
334 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
335 char thinkOutput1[MSG_SIZ*10];\r
336 \r
337 ChessProgramState first, second;\r
338 \r
339 /* premove variables */\r
340 int premoveToX = 0;\r
341 int premoveToY = 0;\r
342 int premoveFromX = 0;\r
343 int premoveFromY = 0;\r
344 int premovePromoChar = 0;\r
345 int gotPremove = 0;\r
346 Boolean alarmSounded;\r
347 /* end premove variables */\r
348 \r
349 #define ICS_GENERIC 0\r
350 #define ICS_ICC 1\r
351 #define ICS_FICS 2\r
352 #define ICS_CHESSNET 3 /* not really supported */\r
353 int ics_type = ICS_GENERIC;\r
354 char *ics_prefix = "$";\r
355 \r
356 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
357 int pauseExamForwardMostMove = 0;\r
358 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
359 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
360 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
361 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
362 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
363 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
364 int whiteFlag = FALSE, blackFlag = FALSE;\r
365 int userOfferedDraw = FALSE;\r
366 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
367 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
368 int cmailMoveType[CMAIL_MAX_GAMES];\r
369 long ics_clock_paused = 0;\r
370 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
371 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
372 GameMode gameMode = BeginningOfGame;\r
373 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
374 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
375 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
376 int hiddenThinkOutputState = 0; /* [AS] */\r
377 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
378 int adjudicateLossPlies = 6;\r
379 char white_holding[64], black_holding[64];\r
380 TimeMark lastNodeCountTime;\r
381 long lastNodeCount=0;\r
382 int have_sent_ICS_logon = 0;\r
383 int movesPerSession;\r
384 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
385 long timeControl_2; /* [AS] Allow separate time controls */\r
386 long timeRemaining[2][MAX_MOVES];\r
387 int matchGame = 0;\r
388 TimeMark programStartTime;\r
389 char ics_handle[MSG_SIZ];\r
390 int have_set_title = 0;\r
391 \r
392 /* animateTraining preserves the state of appData.animate\r
393  * when Training mode is activated. This allows the\r
394  * response to be animated when appData.animate == TRUE and\r
395  * appData.animateDragging == TRUE.\r
396  */\r
397 Boolean animateTraining;\r
398 \r
399 GameInfo gameInfo;\r
400 \r
401 AppData appData;\r
402 \r
403 Board boards[MAX_MOVES];\r
404 /* [HGM] Following 7 needed for accurate legality tests: */\r
405 char  epStatus[MAX_MOVES];\r
406 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
407 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
408 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE];\r
409 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
410 int   initialRulePlies, FENrulePlies;\r
411 char  FENepStatus;\r
412 \r
413 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
414     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
415         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
416     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
417         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
418 };\r
419 \r
420 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
421     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
422         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
423     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
424         BlackKing, BlackKing, BlackKnight, BlackRook }\r
425 };\r
426 \r
427 #ifdef FAIRY\r
428 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
429     { WhiteFairyRook, WhiteFairyKnight, WhiteFairyBishop, WhiteQueen,\r
430         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
431     { BlackFairyRook, BlackFairyKnight, BlackFairyBishop, BlackQueen,\r
432         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
433 };\r
434 \r
435 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
436     { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn,\r
437         WhiteKing, WhiteFairyBishop, WhiteKnight, WhiteRook },\r
438     { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn,\r
439         BlackKing, BlackFairyBishop, BlackKnight, BlackRook }\r
440 };\r
441 \r
442 #if (BOARD_SIZE>=9)\r
443 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
444     { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn,\r
445         WhiteKing, WhiteFairyPawn, WhiteFairyBishop, WhiteKnight, WhiteRook },\r
446     { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn,\r
447         BlackKing, BlackFairyPawn, BlackFairyBishop, BlackKnight, BlackRook }\r
448 };\r
449 #else\r
450 #define XiangqiPosition FIDEPosition\r
451 #endif\r
452 \r
453 #if (BOARD_SIZE>=10)\r
454 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
455     { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen, \r
456         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
457     { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen, \r
458         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
459 };\r
460 \r
461 #ifdef GOTHIC\r
462 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
463     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
464         WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook },\r
465     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
466         BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook }\r
467 };\r
468 #else // !GOTHIC\r
469 #define GothicArray CapablancaArray\r
470 #endif // !GOTHIC\r
471 \r
472 #else // !(BOARD_SIZE>=10)\r
473 #define CapablancaArray FIDEArray\r
474 #define GothicArray FIDEArray\r
475 #endif // !(BOARD_SIZE>=10)\r
476 \r
477 #if (BOARD_SIZE>=12)\r
478 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
479     { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteBishop, WhiteFairyKing, WhiteKing,\r
480         WhiteFairyPawn, WhiteFairyRook, WhiteBishop, WhiteFairyBishop, WhiteKnight, WhiteRook },\r
481     { BlackRook, BlackKnight, BlackFairyBishop, BlackBishop, BlackFairyKing, BlackKing,\r
482         BlackFairyPawn, BlackFairyRook, BlackBishop, BlackFairyBishop, BlackKnight, BlackRook }\r
483 };\r
484 #else // !(BOARD_SIZE>=12)\r
485 #define CourierArray CapablancaArray\r
486 #endif // !(BOARD_SIZE>=12)\r
487 #endif // FAIRY\r
488 \r
489 \r
490 Board initialPosition;\r
491 \r
492 \r
493 /* Convert str to a rating. Checks for special cases of "----",\r
494 \r
495    "++++", etc. Also strips ()'s */\r
496 int\r
497 string_to_rating(str)\r
498   char *str;\r
499 {\r
500   while(*str && !isdigit(*str)) ++str;\r
501   if (!*str)\r
502     return 0;   /* One of the special "no rating" cases */\r
503   else\r
504     return atoi(str);\r
505 }\r
506 \r
507 void\r
508 ClearProgramStats()\r
509 {\r
510     /* Init programStats */\r
511     programStats.movelist[0] = 0;\r
512     programStats.depth = 0;\r
513     programStats.nr_moves = 0;\r
514     programStats.moves_left = 0;\r
515     programStats.nodes = 0;\r
516     programStats.time = 100;\r
517     programStats.score = 0;\r
518     programStats.got_only_move = 0;\r
519     programStats.got_fail = 0;\r
520     programStats.line_is_book = 0;\r
521 }\r
522 \r
523 void\r
524 InitBackEnd1()\r
525 {\r
526     int matched, min, sec;\r
527 \r
528     GetTimeMark(&programStartTime);\r
529 \r
530     ClearProgramStats();\r
531     programStats.ok_to_send = 1;\r
532     programStats.seen_stat = 0;\r
533 \r
534     /*\r
535      * Initialize game list\r
536      */\r
537     ListNew(&gameList);\r
538 \r
539 \r
540     /*\r
541      * Internet chess server status\r
542      */\r
543     if (appData.icsActive) {\r
544         appData.matchMode = FALSE;\r
545         appData.matchGames = 0;\r
546 #if ZIPPY       \r
547         appData.noChessProgram = !appData.zippyPlay;\r
548 #else\r
549         appData.zippyPlay = FALSE;\r
550         appData.zippyTalk = FALSE;\r
551         appData.noChessProgram = TRUE;\r
552 #endif\r
553         if (*appData.icsHelper != NULLCHAR) {\r
554             appData.useTelnet = TRUE;\r
555             appData.telnetProgram = appData.icsHelper;\r
556         }\r
557     } else {\r
558         appData.zippyTalk = appData.zippyPlay = FALSE;\r
559     }\r
560 \r
561     /* [AS] Initialize pv info list [HGM] and game state */\r
562     {\r
563         int i, j;\r
564 \r
565         for( i=0; i<MAX_MOVES; i++ ) {\r
566             pvInfoList[i].depth = -1;\r
567             epStatus[i]=EP_NONE;\r
568             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
569         }\r
570     }\r
571 \r
572     /*\r
573      * Parse timeControl resource\r
574      */\r
575     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
576                           appData.movesPerSession)) {\r
577         char buf[MSG_SIZ];\r
578         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
579         DisplayFatalError(buf, 0, 2);\r
580     }\r
581 \r
582     /*\r
583      * Parse searchTime resource\r
584      */\r
585     if (*appData.searchTime != NULLCHAR) {\r
586         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
587         if (matched == 1) {\r
588             searchTime = min * 60;\r
589         } else if (matched == 2) {\r
590             searchTime = min * 60 + sec;\r
591         } else {\r
592             char buf[MSG_SIZ];\r
593             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
594             DisplayFatalError(buf, 0, 2);\r
595         }\r
596     }\r
597 \r
598     /* [AS] Adjudication threshold */\r
599     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
600     \r
601     first.which = "first";\r
602     second.which = "second";\r
603     first.maybeThinking = second.maybeThinking = FALSE;\r
604     first.pr = second.pr = NoProc;\r
605     first.isr = second.isr = NULL;\r
606     first.sendTime = second.sendTime = 2;\r
607     first.sendDrawOffers = 1;\r
608     if (appData.firstPlaysBlack) {\r
609         first.twoMachinesColor = "black\n";\r
610         second.twoMachinesColor = "white\n";\r
611     } else {\r
612         first.twoMachinesColor = "white\n";\r
613         second.twoMachinesColor = "black\n";\r
614     }\r
615     first.program = appData.firstChessProgram;\r
616     second.program = appData.secondChessProgram;\r
617     first.host = appData.firstHost;\r
618     second.host = appData.secondHost;\r
619     first.dir = appData.firstDirectory;\r
620     second.dir = appData.secondDirectory;\r
621     first.other = &second;\r
622     second.other = &first;\r
623     first.initString = appData.initString;\r
624     second.initString = appData.secondInitString;\r
625     first.computerString = appData.firstComputerString;\r
626     second.computerString = appData.secondComputerString;\r
627     first.useSigint = second.useSigint = TRUE;\r
628     first.useSigterm = second.useSigterm = TRUE;\r
629     first.reuse = appData.reuseFirst;\r
630     second.reuse = appData.reuseSecond;\r
631     first.useSetboard = second.useSetboard = FALSE;\r
632     first.useSAN = second.useSAN = FALSE;\r
633     first.usePing = second.usePing = FALSE;\r
634     first.lastPing = second.lastPing = 0;\r
635     first.lastPong = second.lastPong = 0;\r
636     first.usePlayother = second.usePlayother = FALSE;\r
637     first.useColors = second.useColors = TRUE;\r
638     first.useUsermove = second.useUsermove = FALSE;\r
639     first.sendICS = second.sendICS = FALSE;\r
640     first.sendName = second.sendName = appData.icsActive;\r
641     first.sdKludge = second.sdKludge = FALSE;\r
642     first.stKludge = second.stKludge = FALSE;\r
643     TidyProgramName(first.program, first.host, first.tidy);\r
644     TidyProgramName(second.program, second.host, second.tidy);\r
645     first.matchWins = second.matchWins = 0;\r
646     strcpy(first.variants, appData.variant);\r
647     strcpy(second.variants, appData.variant);\r
648     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
649     first.analyzing = second.analyzing = FALSE;\r
650     first.initDone = second.initDone = FALSE;\r
651 \r
652     /* New features added by Tord: */\r
653     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
654     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
655     /* End of new features added by Tord. */\r
656 \r
657     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
658     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
659     first.isUCI = appData.firstIsUCI; /* [AS] */\r
660     second.isUCI = appData.secondIsUCI; /* [AS] */\r
661     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
662     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
663 \r
664     if (appData.firstProtocolVersion > PROTOVER ||\r
665         appData.firstProtocolVersion < 1) {\r
666       char buf[MSG_SIZ];\r
667       sprintf(buf, "protocol version %d not supported",\r
668               appData.firstProtocolVersion);\r
669       DisplayFatalError(buf, 0, 2);\r
670     } else {\r
671       first.protocolVersion = appData.firstProtocolVersion;\r
672     }\r
673 \r
674     if (appData.secondProtocolVersion > PROTOVER ||\r
675         appData.secondProtocolVersion < 1) {\r
676       char buf[MSG_SIZ];\r
677       sprintf(buf, "protocol version %d not supported",\r
678               appData.secondProtocolVersion);\r
679       DisplayFatalError(buf, 0, 2);\r
680     } else {\r
681       second.protocolVersion = appData.secondProtocolVersion;\r
682     }\r
683 \r
684     if (appData.icsActive) {\r
685         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
686     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
687         appData.clockMode = FALSE;\r
688         first.sendTime = second.sendTime = 0;\r
689     }\r
690     \r
691 #if ZIPPY\r
692     /* Override some settings from environment variables, for backward\r
693        compatibility.  Unfortunately it's not feasible to have the env\r
694        vars just set defaults, at least in xboard.  Ugh.\r
695     */\r
696     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
697       ZippyInit();\r
698     }\r
699 #endif\r
700     \r
701     if (appData.noChessProgram) {\r
702         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
703                                         + strlen(PATCHLEVEL));\r
704         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
705     } else {\r
706         char *p, *q;\r
707         q = first.program;\r
708         while (*q != ' ' && *q != NULLCHAR) q++;\r
709         p = q;\r
710         while (p > first.program && *(p-1) != '/') p--;\r
711         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
712                                         + strlen(PATCHLEVEL) + (q - p));\r
713         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
714         strncat(programVersion, p, q - p);\r
715     }\r
716 \r
717     if (!appData.icsActive) {\r
718       char buf[MSG_SIZ];\r
719       /* Check for variants that are supported only in ICS mode,\r
720          or not at all.  Some that are accepted here nevertheless\r
721          have bugs; see comments below.\r
722       */\r
723       VariantClass variant = StringToVariant(appData.variant);\r
724       switch (variant) {\r
725       case VariantBughouse:     /* need four players and two boards */\r
726       case VariantKriegspiel:   /* need to hide pieces and move details */\r
727       /* case VariantFischeRandom: (Fabien: moved below) */\r
728         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
729         DisplayFatalError(buf, 0, 2);\r
730         return;\r
731 \r
732       case VariantUnknown:\r
733       case VariantLoadable:\r
734       case Variant29:\r
735       case Variant30:\r
736 #ifndef FAIRY\r
737       case Variant31:\r
738       case Variant32:\r
739       case Variant33:\r
740       case Variant34:\r
741       case Variant35:\r
742       case Variant36:\r
743 #endif\r
744       default:\r
745         sprintf(buf, "Unknown variant name %s", appData.variant);\r
746         DisplayFatalError(buf, 0, 2);\r
747         return;\r
748 \r
749       case VariantNormal:     /* definitely works! */\r
750       case VariantWildCastle: /* pieces not automatically shuffled */\r
751       case VariantNoCastle:   /* pieces not automatically shuffled */\r
752       case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */\r
753       case VariantCrazyhouse: /* holdings not shown,\r
754                                  offboard interposition not understood */\r
755       case VariantLosers:     /* should work except for win condition,\r
756                                  and doesn't know captures are mandatory */\r
757       case VariantSuicide:    /* should work except for win condition,\r
758                                  and doesn't know captures are mandatory */\r
759       case VariantGiveaway:   /* should work except for win condition,\r
760                                  and doesn't know captures are mandatory */\r
761       case VariantTwoKings:   /* should work */\r
762       case VariantAtomic:     /* should work except for win condition */\r
763       case Variant3Check:     /* should work except for win condition */\r
764       case VariantShatranj:   /* might work if TestLegality is off */\r
765 #ifdef FAIRY\r
766       case VariantShogi:\r
767       case VariantXiangqi:\r
768       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
769       case VariantGothic:\r
770       case VariantCapablanca:\r
771       case VariantCourier:\r
772 #endif\r
773         break;\r
774       }\r
775     }\r
776 }\r
777 \r
778 int NextIntegerFromString( char ** str, long * value )\r
779 {\r
780     int result = -1;\r
781     char * s = *str;\r
782 \r
783     while( *s == ' ' || *s == '\t' ) {\r
784         s++;\r
785     }\r
786 \r
787     *value = 0;\r
788 \r
789     if( *s >= '0' && *s <= '9' ) {\r
790         while( *s >= '0' && *s <= '9' ) {\r
791             *value = *value * 10 + (*s - '0');\r
792             s++;\r
793         }\r
794 \r
795         result = 0;\r
796     }\r
797 \r
798     *str = s;\r
799 \r
800     return result;\r
801 }\r
802 \r
803 int NextTimeControlFromString( char ** str, long * value )\r
804 {\r
805     long temp;\r
806     int result = NextIntegerFromString( str, &temp );\r
807 \r
808     if( result == 0 ) {\r
809         *value = temp * 60; /* Minutes */\r
810         if( **str == ':' ) {\r
811             (*str)++;\r
812             result = NextIntegerFromString( str, &temp );\r
813             *value += temp; /* Seconds */\r
814         }\r
815     }\r
816 \r
817     return result;\r
818 }\r
819 \r
820 int GetTimeControlForWhite()\r
821 {\r
822     int result = timeControl;\r
823 \r
824     return result;\r
825 }\r
826 \r
827 int GetTimeControlForBlack()\r
828 {\r
829     int result = timeControl;\r
830 \r
831     if( timeControl_2 > 0 ) {\r
832         result = timeControl_2;\r
833     }\r
834 \r
835     return result;\r
836 }\r
837 \r
838 int\r
839 ParseTimeControl(tc, ti, mps)\r
840      char *tc;\r
841      int ti;\r
842      int mps;\r
843 {\r
844 #if 0\r
845     int matched, min, sec;\r
846 \r
847     matched = sscanf(tc, "%d:%d", &min, &sec);\r
848     if (matched == 1) {\r
849         timeControl = min * 60 * 1000;\r
850     } else if (matched == 2) {\r
851         timeControl = (min * 60 + sec) * 1000;\r
852     } else {\r
853         return FALSE;\r
854     }\r
855 #else\r
856     long tc1;\r
857     long tc2;\r
858 \r
859     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
860         return FALSE;\r
861     }\r
862 \r
863     if( *tc == '/' ) {\r
864         /* Parse second time control */\r
865         tc++;\r
866 \r
867         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
868             return FALSE;\r
869         }\r
870 \r
871         if( tc2 == 0 ) {\r
872             return FALSE;\r
873         }\r
874 \r
875         timeControl_2 = tc2 * 1000;\r
876     }\r
877     else {\r
878         timeControl_2 = 0;\r
879     }\r
880 \r
881     if( tc1 == 0 ) {\r
882         return FALSE;\r
883     }\r
884 \r
885     timeControl = tc1 * 1000;\r
886 #endif\r
887 \r
888     if (ti >= 0) {\r
889         timeIncrement = ti * 1000;  /* convert to ms */\r
890         movesPerSession = 0;\r
891     } else {\r
892         timeIncrement = 0;\r
893         movesPerSession = mps;\r
894     }\r
895     return TRUE;\r
896 }\r
897 \r
898 void\r
899 InitBackEnd2()\r
900 {\r
901     if (appData.debugMode) {\r
902         fprintf(debugFP, "%s\n", programVersion);\r
903     }\r
904 \r
905     if (appData.matchGames > 0) {\r
906         appData.matchMode = TRUE;\r
907     } else if (appData.matchMode) {\r
908         appData.matchGames = 1;\r
909     }\r
910     Reset(TRUE, FALSE);\r
911     if (appData.noChessProgram || first.protocolVersion == 1) {\r
912       InitBackEnd3();\r
913     } else {\r
914       /* kludge: allow timeout for initial "feature" commands */\r
915       FreezeUI();\r
916       DisplayMessage("", "Starting chess program");\r
917       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
918     }\r
919 }\r
920 \r
921 void\r
922 InitBackEnd3 P((void))\r
923 {\r
924     GameMode initialMode;\r
925     char buf[MSG_SIZ];\r
926     int err;\r
927 \r
928     InitChessProgram(&first);\r
929 \r
930     if (appData.icsActive) {\r
931         err = establish();\r
932         if (err != 0) {\r
933             if (*appData.icsCommPort != NULLCHAR) {\r
934                 sprintf(buf, "Could not open comm port %s",  \r
935                         appData.icsCommPort);\r
936             } else {\r
937                 sprintf(buf, "Could not connect to host %s, port %s",  \r
938                         appData.icsHost, appData.icsPort);\r
939             }\r
940             DisplayFatalError(buf, err, 1);\r
941             return;\r
942         }\r
943         SetICSMode();\r
944         telnetISR =\r
945           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
946         fromUserISR =\r
947           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
948     } else if (appData.noChessProgram) {\r
949         SetNCPMode();\r
950     } else {\r
951         SetGNUMode();\r
952     }\r
953 \r
954     if (*appData.cmailGameName != NULLCHAR) {\r
955         SetCmailMode();\r
956         OpenLoopback(&cmailPR);\r
957         cmailISR =\r
958           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
959     }\r
960     \r
961     ThawUI();\r
962     DisplayMessage("", "");\r
963     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
964       initialMode = BeginningOfGame;\r
965     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
966       initialMode = TwoMachinesPlay;\r
967     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
968       initialMode = AnalyzeFile; \r
969     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
970       initialMode = AnalyzeMode;\r
971     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
972       initialMode = MachinePlaysWhite;\r
973     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
974       initialMode = MachinePlaysBlack;\r
975     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
976       initialMode = EditGame;\r
977     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
978       initialMode = EditPosition;\r
979     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
980       initialMode = Training;\r
981     } else {\r
982       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
983       DisplayFatalError(buf, 0, 2);\r
984       return;\r
985     }\r
986 \r
987     if (appData.matchMode) {\r
988         /* Set up machine vs. machine match */\r
989         if (appData.noChessProgram) {\r
990             DisplayFatalError("Can't have a match with no chess programs",\r
991                               0, 2);\r
992             return;\r
993         }\r
994         matchMode = TRUE;\r
995         matchGame = 1;\r
996         if (*appData.loadGameFile != NULLCHAR) {\r
997             if (!LoadGameFromFile(appData.loadGameFile,\r
998                                   appData.loadGameIndex,\r
999                                   appData.loadGameFile, FALSE)) {\r
1000                 DisplayFatalError("Bad game file", 0, 1);\r
1001                 return;\r
1002             }\r
1003         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1004             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1005                                       appData.loadPositionIndex,\r
1006                                       appData.loadPositionFile)) {\r
1007                 DisplayFatalError("Bad position file", 0, 1);\r
1008                 return;\r
1009             }\r
1010         }\r
1011         TwoMachinesEvent();\r
1012     } else if (*appData.cmailGameName != NULLCHAR) {\r
1013         /* Set up cmail mode */\r
1014         ReloadCmailMsgEvent(TRUE);\r
1015     } else {\r
1016         /* Set up other modes */\r
1017         if (initialMode == AnalyzeFile) {\r
1018           if (*appData.loadGameFile == NULLCHAR) {\r
1019             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1020             return;\r
1021           }\r
1022         }\r
1023         if (*appData.loadGameFile != NULLCHAR) {\r
1024             (void) LoadGameFromFile(appData.loadGameFile,\r
1025                                     appData.loadGameIndex,\r
1026                                     appData.loadGameFile, TRUE);\r
1027         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1028             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1029                                         appData.loadPositionIndex,\r
1030                                         appData.loadPositionFile);\r
1031         }\r
1032         if (initialMode == AnalyzeMode) {\r
1033           if (appData.noChessProgram) {\r
1034             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1035             return;\r
1036           }\r
1037           if (appData.icsActive) {\r
1038             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1039             return;\r
1040           }\r
1041           AnalyzeModeEvent();\r
1042         } else if (initialMode == AnalyzeFile) {\r
1043           ShowThinkingEvent(TRUE);\r
1044           AnalyzeFileEvent();\r
1045           AnalysisPeriodicEvent(1);\r
1046         } else if (initialMode == MachinePlaysWhite) {\r
1047           if (appData.noChessProgram) {\r
1048             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1049                               0, 2);\r
1050             return;\r
1051           }\r
1052           if (appData.icsActive) {\r
1053             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1054                               0, 2);\r
1055             return;\r
1056           }\r
1057           MachineWhiteEvent();\r
1058         } else if (initialMode == MachinePlaysBlack) {\r
1059           if (appData.noChessProgram) {\r
1060             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1061                               0, 2);\r
1062             return;\r
1063           }\r
1064           if (appData.icsActive) {\r
1065             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1066                               0, 2);\r
1067             return;\r
1068           }\r
1069           MachineBlackEvent();\r
1070         } else if (initialMode == TwoMachinesPlay) {\r
1071           if (appData.noChessProgram) {\r
1072             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1073                               0, 2);\r
1074             return;\r
1075           }\r
1076           if (appData.icsActive) {\r
1077             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1078                               0, 2);\r
1079             return;\r
1080           }\r
1081           TwoMachinesEvent();\r
1082         } else if (initialMode == EditGame) {\r
1083           EditGameEvent();\r
1084         } else if (initialMode == EditPosition) {\r
1085           EditPositionEvent();\r
1086         } else if (initialMode == Training) {\r
1087           if (*appData.loadGameFile == NULLCHAR) {\r
1088             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1089             return;\r
1090           }\r
1091           TrainingEvent();\r
1092         }\r
1093     }\r
1094 }\r
1095 \r
1096 /*\r
1097  * Establish will establish a contact to a remote host.port.\r
1098  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1099  *  used to talk to the host.\r
1100  * Returns 0 if okay, error code if not.\r
1101  */\r
1102 int\r
1103 establish()\r
1104 {\r
1105     char buf[MSG_SIZ];\r
1106 \r
1107     if (*appData.icsCommPort != NULLCHAR) {\r
1108         /* Talk to the host through a serial comm port */\r
1109         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1110 \r
1111     } else if (*appData.gateway != NULLCHAR) {\r
1112         if (*appData.remoteShell == NULLCHAR) {\r
1113             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1114             sprintf(buf, "%s %s %s",\r
1115                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1116             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1117 \r
1118         } else {\r
1119             /* Use the rsh program to run telnet program on a gateway host */\r
1120             if (*appData.remoteUser == NULLCHAR) {\r
1121                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1122                         appData.gateway, appData.telnetProgram,\r
1123                         appData.icsHost, appData.icsPort);\r
1124             } else {\r
1125                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1126                         appData.remoteShell, appData.gateway, \r
1127                         appData.remoteUser, appData.telnetProgram,\r
1128                         appData.icsHost, appData.icsPort);\r
1129             }\r
1130             return StartChildProcess(buf, "", &icsPR);\r
1131 \r
1132         }\r
1133     } else if (appData.useTelnet) {\r
1134         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1135 \r
1136     } else {\r
1137         /* TCP socket interface differs somewhat between\r
1138            Unix and NT; handle details in the front end.\r
1139            */\r
1140         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1141     }\r
1142 }\r
1143 \r
1144 void\r
1145 show_bytes(fp, buf, count)\r
1146      FILE *fp;\r
1147      char *buf;\r
1148      int count;\r
1149 {\r
1150     while (count--) {\r
1151         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1152             fprintf(fp, "\\%03o", *buf & 0xff);\r
1153         } else {\r
1154             putc(*buf, fp);\r
1155         }\r
1156         buf++;\r
1157     }\r
1158     fflush(fp);\r
1159 }\r
1160 \r
1161 /* Returns an errno value */\r
1162 int\r
1163 OutputMaybeTelnet(pr, message, count, outError)\r
1164      ProcRef pr;\r
1165      char *message;\r
1166      int count;\r
1167      int *outError;\r
1168 {\r
1169     char buf[8192], *p, *q, *buflim;\r
1170     int left, newcount, outcount;\r
1171 \r
1172     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1173         *appData.gateway != NULLCHAR) {\r
1174         if (appData.debugMode) {\r
1175             fprintf(debugFP, ">ICS: ");\r
1176             show_bytes(debugFP, message, count);\r
1177             fprintf(debugFP, "\n");\r
1178         }\r
1179         return OutputToProcess(pr, message, count, outError);\r
1180     }\r
1181 \r
1182     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1183     p = message;\r
1184     q = buf;\r
1185     left = count;\r
1186     newcount = 0;\r
1187     while (left) {\r
1188         if (q >= buflim) {\r
1189             if (appData.debugMode) {\r
1190                 fprintf(debugFP, ">ICS: ");\r
1191                 show_bytes(debugFP, buf, newcount);\r
1192                 fprintf(debugFP, "\n");\r
1193             }\r
1194             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1195             if (outcount < newcount) return -1; /* to be sure */\r
1196             q = buf;\r
1197             newcount = 0;\r
1198         }\r
1199         if (*p == '\n') {\r
1200             *q++ = '\r';\r
1201             newcount++;\r
1202         } else if (((unsigned char) *p) == TN_IAC) {\r
1203             *q++ = (char) TN_IAC;\r
1204             newcount ++;\r
1205         }\r
1206         *q++ = *p++;\r
1207         newcount++;\r
1208         left--;\r
1209     }\r
1210     if (appData.debugMode) {\r
1211         fprintf(debugFP, ">ICS: ");\r
1212         show_bytes(debugFP, buf, newcount);\r
1213         fprintf(debugFP, "\n");\r
1214     }\r
1215     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1216     if (outcount < newcount) return -1; /* to be sure */\r
1217     return count;\r
1218 }\r
1219 \r
1220 void\r
1221 read_from_player(isr, closure, message, count, error)\r
1222      InputSourceRef isr;\r
1223      VOIDSTAR closure;\r
1224      char *message;\r
1225      int count;\r
1226      int error;\r
1227 {\r
1228     int outError, outCount;\r
1229     static int gotEof = 0;\r
1230 \r
1231     /* Pass data read from player on to ICS */\r
1232     if (count > 0) {\r
1233         gotEof = 0;\r
1234         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1235         if (outCount < count) {\r
1236             DisplayFatalError("Error writing to ICS", outError, 1);\r
1237         }\r
1238     } else if (count < 0) {\r
1239         RemoveInputSource(isr);\r
1240         DisplayFatalError("Error reading from keyboard", error, 1);\r
1241     } else if (gotEof++ > 0) {\r
1242         RemoveInputSource(isr);\r
1243         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1244     }\r
1245 }\r
1246 \r
1247 void\r
1248 SendToICS(s)\r
1249      char *s;\r
1250 {\r
1251     int count, outCount, outError;\r
1252 \r
1253     if (icsPR == NULL) return;\r
1254 \r
1255     count = strlen(s);\r
1256     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1257     if (outCount < count) {\r
1258         DisplayFatalError("Error writing to ICS", outError, 1);\r
1259     }\r
1260 }\r
1261 \r
1262 /* This is used for sending logon scripts to the ICS. Sending\r
1263    without a delay causes problems when using timestamp on ICC\r
1264    (at least on my machine). */\r
1265 void\r
1266 SendToICSDelayed(s,msdelay)\r
1267      char *s;\r
1268      long msdelay;\r
1269 {\r
1270     int count, outCount, outError;\r
1271 \r
1272     if (icsPR == NULL) return;\r
1273 \r
1274     count = strlen(s);\r
1275     if (appData.debugMode) {\r
1276         fprintf(debugFP, ">ICS: ");\r
1277         show_bytes(debugFP, s, count);\r
1278         fprintf(debugFP, "\n");\r
1279     }\r
1280     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1281                                       msdelay);\r
1282     if (outCount < count) {\r
1283         DisplayFatalError("Error writing to ICS", outError, 1);\r
1284     }\r
1285 }\r
1286 \r
1287 \r
1288 /* Remove all highlighting escape sequences in s\r
1289    Also deletes any suffix starting with '(' \r
1290    */\r
1291 char *\r
1292 StripHighlightAndTitle(s)\r
1293      char *s;\r
1294 {\r
1295     static char retbuf[MSG_SIZ];\r
1296     char *p = retbuf;\r
1297 \r
1298     while (*s != NULLCHAR) {\r
1299         while (*s == '\033') {\r
1300             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1301             if (*s != NULLCHAR) s++;\r
1302         }\r
1303         while (*s != NULLCHAR && *s != '\033') {\r
1304             if (*s == '(' || *s == '[') {\r
1305                 *p = NULLCHAR;\r
1306                 return retbuf;\r
1307             }\r
1308             *p++ = *s++;\r
1309         }\r
1310     }\r
1311     *p = NULLCHAR;\r
1312     return retbuf;\r
1313 }\r
1314 \r
1315 /* Remove all highlighting escape sequences in s */\r
1316 char *\r
1317 StripHighlight(s)\r
1318      char *s;\r
1319 {\r
1320     static char retbuf[MSG_SIZ];\r
1321     char *p = retbuf;\r
1322 \r
1323     while (*s != NULLCHAR) {\r
1324         while (*s == '\033') {\r
1325             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1326             if (*s != NULLCHAR) s++;\r
1327         }\r
1328         while (*s != NULLCHAR && *s != '\033') {\r
1329             *p++ = *s++;\r
1330         }\r
1331     }\r
1332     *p = NULLCHAR;\r
1333     return retbuf;\r
1334 }\r
1335 \r
1336 char *variantNames[] = VARIANT_NAMES;\r
1337 char *\r
1338 VariantName(v)\r
1339      VariantClass v;\r
1340 {\r
1341     return variantNames[v];\r
1342 }\r
1343 \r
1344 \r
1345 /* Identify a variant from the strings the chess servers use or the\r
1346    PGN Variant tag names we use. */\r
1347 VariantClass\r
1348 StringToVariant(e)\r
1349      char *e;\r
1350 {\r
1351     char *p;\r
1352     int wnum = -1;\r
1353     VariantClass v = VariantNormal;\r
1354     int i, found = FALSE;\r
1355     char buf[MSG_SIZ];\r
1356 \r
1357     if (!e) return v;\r
1358     \r
1359     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1360       if (StrCaseStr(e, variantNames[i])) {\r
1361         v = (VariantClass) i;\r
1362         found = TRUE;\r
1363         break;\r
1364       }\r
1365     }\r
1366 \r
1367     if (!found) {\r
1368       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1369           || StrCaseStr(e, "wild/fr")) {\r
1370         v = VariantFischeRandom;\r
1371       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1372                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1373         p += i;\r
1374         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1375         if (isdigit(*p)) {\r
1376           wnum = atoi(p);\r
1377         } else {\r
1378           wnum = -1;\r
1379         }\r
1380         switch (wnum) {\r
1381         case 0: /* FICS only, actually */\r
1382         case 1:\r
1383           /* Castling legal even if K starts on d-file */\r
1384           v = VariantWildCastle;\r
1385           break;\r
1386         case 2:\r
1387         case 3:\r
1388         case 4:\r
1389           /* Castling illegal even if K & R happen to start in\r
1390              normal positions. */\r
1391           v = VariantNoCastle;\r
1392           break;\r
1393         case 5:\r
1394         case 7:\r
1395         case 8:\r
1396         case 10:\r
1397         case 11:\r
1398         case 12:\r
1399         case 13:\r
1400         case 14:\r
1401         case 15:\r
1402         case 18:\r
1403         case 19:\r
1404           /* Castling legal iff K & R start in normal positions */\r
1405           v = VariantNormal;\r
1406           break;\r
1407         case 6:\r
1408         case 20:\r
1409         case 21:\r
1410           /* Special wilds for position setup; unclear what to do here */\r
1411           v = VariantLoadable;\r
1412           break;\r
1413         case 9:\r
1414           /* Bizarre ICC game */\r
1415           v = VariantTwoKings;\r
1416           break;\r
1417         case 16:\r
1418           v = VariantKriegspiel;\r
1419           break;\r
1420         case 17:\r
1421           v = VariantLosers;\r
1422           break;\r
1423         case 22:\r
1424           v = VariantFischeRandom;\r
1425           break;\r
1426         case 23:\r
1427           v = VariantCrazyhouse;\r
1428           break;\r
1429         case 24:\r
1430           v = VariantBughouse;\r
1431           break;\r
1432         case 25:\r
1433           v = Variant3Check;\r
1434           break;\r
1435         case 26:\r
1436           /* Not quite the same as FICS suicide! */\r
1437           v = VariantGiveaway;\r
1438           break;\r
1439         case 27:\r
1440           v = VariantAtomic;\r
1441           break;\r
1442         case 28:\r
1443           v = VariantShatranj;\r
1444           break;\r
1445 \r
1446         /* Temporary names for future ICC types.  The name *will* change in \r
1447            the next xboard/WinBoard release after ICC defines it. */\r
1448         case 29:\r
1449           v = Variant29;\r
1450           break;\r
1451         case 30:\r
1452           v = Variant30;\r
1453           break;\r
1454         case 31:\r
1455 #ifdef FAIRY\r
1456           v = VariantShogi;\r
1457 #else\r
1458           v = Variant31;\r
1459 #endif\r
1460           break;\r
1461         case 32:\r
1462 #ifdef FAIRY\r
1463           v = VariantXiangqi;\r
1464 #else\r
1465           v = Variant32;\r
1466 #endif\r
1467           break;\r
1468         case 33:\r
1469 #ifdef FAIRY\r
1470           v = VariantCourier;\r
1471 #else\r
1472           v = Variant33;\r
1473 #endif\r
1474           break;\r
1475         case 34:\r
1476 #ifdef FAIRY\r
1477           v = VariantGothic;\r
1478 #else\r
1479           v = Variant34;\r
1480 #endif\r
1481           break;\r
1482         case 35:\r
1483 #ifdef FAIRY\r
1484           v = VariantCapablanca;\r
1485 #else\r
1486           v = Variant35;\r
1487 #endif\r
1488           break;\r
1489         case 36:\r
1490 #ifdef FAIRY\r
1491           v = VariantFairy;\r
1492 #else\r
1493           v = Variant36;\r
1494 #endif\r
1495           break;\r
1496 \r
1497         case -1:\r
1498           /* Found "wild" or "w" in the string but no number;\r
1499              must assume it's normal chess. */\r
1500           v = VariantNormal;\r
1501           break;\r
1502         default:\r
1503           sprintf(buf, "Unknown wild type %d", wnum);\r
1504           DisplayError(buf, 0);\r
1505           v = VariantUnknown;\r
1506           break;\r
1507         }\r
1508       }\r
1509     }\r
1510     if (appData.debugMode) {\r
1511       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1512               e, wnum, VariantName(v));\r
1513     }\r
1514     return v;\r
1515 }\r
1516 \r
1517 static int leftover_start = 0, leftover_len = 0;\r
1518 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1519 \r
1520 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1521    advance *index beyond it, and set leftover_start to the new value of\r
1522    *index; else return FALSE.  If pattern contains the character '*', it\r
1523    matches any sequence of characters not containing '\r', '\n', or the\r
1524    character following the '*' (if any), and the matched sequence(s) are\r
1525    copied into star_match.\r
1526    */\r
1527 int\r
1528 looking_at(buf, index, pattern)\r
1529      char *buf;\r
1530      int *index;\r
1531      char *pattern;\r
1532 {\r
1533     char *bufp = &buf[*index], *patternp = pattern;\r
1534     int star_count = 0;\r
1535     char *matchp = star_match[0];\r
1536     \r
1537     for (;;) {\r
1538         if (*patternp == NULLCHAR) {\r
1539             *index = leftover_start = bufp - buf;\r
1540             *matchp = NULLCHAR;\r
1541             return TRUE;\r
1542         }\r
1543         if (*bufp == NULLCHAR) return FALSE;\r
1544         if (*patternp == '*') {\r
1545             if (*bufp == *(patternp + 1)) {\r
1546                 *matchp = NULLCHAR;\r
1547                 matchp = star_match[++star_count];\r
1548                 patternp += 2;\r
1549                 bufp++;\r
1550                 continue;\r
1551             } else if (*bufp == '\n' || *bufp == '\r') {\r
1552                 patternp++;\r
1553                 if (*patternp == NULLCHAR)\r
1554                   continue;\r
1555                 else\r
1556                   return FALSE;\r
1557             } else {\r
1558                 *matchp++ = *bufp++;\r
1559                 continue;\r
1560             }\r
1561         }\r
1562         if (*patternp != *bufp) return FALSE;\r
1563         patternp++;\r
1564         bufp++;\r
1565     }\r
1566 }\r
1567 \r
1568 void\r
1569 SendToPlayer(data, length)\r
1570      char *data;\r
1571      int length;\r
1572 {\r
1573     int error, outCount;\r
1574     outCount = OutputToProcess(NoProc, data, length, &error);\r
1575     if (outCount < length) {\r
1576         DisplayFatalError("Error writing to display", error, 1);\r
1577     }\r
1578 }\r
1579 \r
1580 void\r
1581 PackHolding(packed, holding)\r
1582      char packed[];\r
1583      char *holding;\r
1584 {\r
1585     char *p = holding;\r
1586     char *q = packed;\r
1587     int runlength = 0;\r
1588     int curr = 9999;\r
1589     do {\r
1590         if (*p == curr) {\r
1591             runlength++;\r
1592         } else {\r
1593             switch (runlength) {\r
1594               case 0:\r
1595                 break;\r
1596               case 1:\r
1597                 *q++ = curr;\r
1598                 break;\r
1599               case 2:\r
1600                 *q++ = curr;\r
1601                 *q++ = curr;\r
1602                 break;\r
1603               default:\r
1604                 sprintf(q, "%d", runlength);\r
1605                 while (*q) q++;\r
1606                 *q++ = curr;\r
1607                 break;\r
1608             }\r
1609             runlength = 1;\r
1610             curr = *p;\r
1611         }\r
1612     } while (*p++);\r
1613     *q = NULLCHAR;\r
1614 }\r
1615 \r
1616 /* Telnet protocol requests from the front end */\r
1617 void\r
1618 TelnetRequest(ddww, option)\r
1619      unsigned char ddww, option;\r
1620 {\r
1621     unsigned char msg[3];\r
1622     int outCount, outError;\r
1623 \r
1624     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1625 \r
1626     if (appData.debugMode) {\r
1627         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1628         switch (ddww) {\r
1629           case TN_DO:\r
1630             ddwwStr = "DO";\r
1631             break;\r
1632           case TN_DONT:\r
1633             ddwwStr = "DONT";\r
1634             break;\r
1635           case TN_WILL:\r
1636             ddwwStr = "WILL";\r
1637             break;\r
1638           case TN_WONT:\r
1639             ddwwStr = "WONT";\r
1640             break;\r
1641           default:\r
1642             ddwwStr = buf1;\r
1643             sprintf(buf1, "%d", ddww);\r
1644             break;\r
1645         }\r
1646         switch (option) {\r
1647           case TN_ECHO:\r
1648             optionStr = "ECHO";\r
1649             break;\r
1650           default:\r
1651             optionStr = buf2;\r
1652             sprintf(buf2, "%d", option);\r
1653             break;\r
1654         }\r
1655         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1656     }\r
1657     msg[0] = TN_IAC;\r
1658     msg[1] = ddww;\r
1659     msg[2] = option;\r
1660     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1661     if (outCount < 3) {\r
1662         DisplayFatalError("Error writing to ICS", outError, 1);\r
1663     }\r
1664 }\r
1665 \r
1666 void\r
1667 DoEcho()\r
1668 {\r
1669     if (!appData.icsActive) return;\r
1670     TelnetRequest(TN_DO, TN_ECHO);\r
1671 }\r
1672 \r
1673 void\r
1674 DontEcho()\r
1675 {\r
1676     if (!appData.icsActive) return;\r
1677     TelnetRequest(TN_DONT, TN_ECHO);\r
1678 }\r
1679 \r
1680 static int loggedOn = FALSE;\r
1681 \r
1682 /*-- Game start info cache: --*/\r
1683 int gs_gamenum;\r
1684 char gs_kind[MSG_SIZ];\r
1685 static char player1Name[128] = "";\r
1686 static char player2Name[128] = "";\r
1687 static int player1Rating = -1;\r
1688 static int player2Rating = -1;\r
1689 /*----------------------------*/\r
1690 \r
1691 ColorClass curColor = ColorNormal;\r
1692 \r
1693 void\r
1694 read_from_ics(isr, closure, data, count, error)\r
1695      InputSourceRef isr;\r
1696      VOIDSTAR closure;\r
1697      char *data;\r
1698      int count;\r
1699      int error;\r
1700 {\r
1701 #define BUF_SIZE 8192\r
1702 #define STARTED_NONE 0\r
1703 #define STARTED_MOVES 1\r
1704 #define STARTED_BOARD 2\r
1705 #define STARTED_OBSERVE 3\r
1706 #define STARTED_HOLDINGS 4\r
1707 #define STARTED_CHATTER 5\r
1708 #define STARTED_COMMENT 6\r
1709 #define STARTED_MOVES_NOHIDE 7\r
1710     \r
1711     static int started = STARTED_NONE;\r
1712     static char parse[20000];\r
1713     static int parse_pos = 0;\r
1714     static char buf[BUF_SIZE + 1];\r
1715     static int firstTime = TRUE, intfSet = FALSE;\r
1716     static ColorClass prevColor = ColorNormal;\r
1717     static int savingComment = FALSE;\r
1718     char str[500];\r
1719     int i, oldi;\r
1720     int buf_len;\r
1721     int next_out;\r
1722     int tkind;\r
1723     char *p;\r
1724 \r
1725 #ifdef WIN32\r
1726     if (appData.debugMode) {\r
1727       if (!error) {\r
1728         fprintf(debugFP, "<ICS: ");\r
1729         show_bytes(debugFP, data, count);\r
1730         fprintf(debugFP, "\n");\r
1731       }\r
1732     }\r
1733 #endif\r
1734 \r
1735     if (count > 0) {\r
1736         /* If last read ended with a partial line that we couldn't parse,\r
1737            prepend it to the new read and try again. */\r
1738         if (leftover_len > 0) {\r
1739             for (i=0; i<leftover_len; i++)\r
1740               buf[i] = buf[leftover_start + i];\r
1741         }\r
1742 \r
1743         /* Copy in new characters, removing nulls and \r's */\r
1744         buf_len = leftover_len;\r
1745         for (i = 0; i < count; i++) {\r
1746             if (data[i] != NULLCHAR && data[i] != '\r')\r
1747               buf[buf_len++] = data[i];\r
1748         }\r
1749 \r
1750         buf[buf_len] = NULLCHAR;\r
1751         next_out = leftover_len;\r
1752         leftover_start = 0;\r
1753         \r
1754         i = 0;\r
1755         while (i < buf_len) {\r
1756             /* Deal with part of the TELNET option negotiation\r
1757                protocol.  We refuse to do anything beyond the\r
1758                defaults, except that we allow the WILL ECHO option,\r
1759                which ICS uses to turn off password echoing when we are\r
1760                directly connected to it.  We reject this option\r
1761                if localLineEditing mode is on (always on in xboard)\r
1762                and we are talking to port 23, which might be a real\r
1763                telnet server that will try to keep WILL ECHO on permanently.\r
1764              */\r
1765             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
1766                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
1767                 unsigned char option;\r
1768                 oldi = i;\r
1769                 switch ((unsigned char) buf[++i]) {\r
1770                   case TN_WILL:\r
1771                     if (appData.debugMode)\r
1772                       fprintf(debugFP, "\n<WILL ");\r
1773                     switch (option = (unsigned char) buf[++i]) {\r
1774                       case TN_ECHO:\r
1775                         if (appData.debugMode)\r
1776                           fprintf(debugFP, "ECHO ");\r
1777                         /* Reply only if this is a change, according\r
1778                            to the protocol rules. */\r
1779                         if (remoteEchoOption) break;\r
1780                         if (appData.localLineEditing &&\r
1781                             atoi(appData.icsPort) == TN_PORT) {\r
1782                             TelnetRequest(TN_DONT, TN_ECHO);\r
1783                         } else {\r
1784                             EchoOff();\r
1785                             TelnetRequest(TN_DO, TN_ECHO);\r
1786                             remoteEchoOption = TRUE;\r
1787                         }\r
1788                         break;\r
1789                       default:\r
1790                         if (appData.debugMode)\r
1791                           fprintf(debugFP, "%d ", option);\r
1792                         /* Whatever this is, we don't want it. */\r
1793                         TelnetRequest(TN_DONT, option);\r
1794                         break;\r
1795                     }\r
1796                     break;\r
1797                   case TN_WONT:\r
1798                     if (appData.debugMode)\r
1799                       fprintf(debugFP, "\n<WONT ");\r
1800                     switch (option = (unsigned char) buf[++i]) {\r
1801                       case TN_ECHO:\r
1802                         if (appData.debugMode)\r
1803                           fprintf(debugFP, "ECHO ");\r
1804                         /* Reply only if this is a change, according\r
1805                            to the protocol rules. */\r
1806                         if (!remoteEchoOption) break;\r
1807                         EchoOn();\r
1808                         TelnetRequest(TN_DONT, TN_ECHO);\r
1809                         remoteEchoOption = FALSE;\r
1810                         break;\r
1811                       default:\r
1812                         if (appData.debugMode)\r
1813                           fprintf(debugFP, "%d ", (unsigned char) option);\r
1814                         /* Whatever this is, it must already be turned\r
1815                            off, because we never agree to turn on\r
1816                            anything non-default, so according to the\r
1817                            protocol rules, we don't reply. */\r
1818                         break;\r
1819                     }\r
1820                     break;\r
1821                   case TN_DO:\r
1822                     if (appData.debugMode)\r
1823                       fprintf(debugFP, "\n<DO ");\r
1824                     switch (option = (unsigned char) buf[++i]) {\r
1825                       default:\r
1826                         /* Whatever this is, we refuse to do it. */\r
1827                         if (appData.debugMode)\r
1828                           fprintf(debugFP, "%d ", option);\r
1829                         TelnetRequest(TN_WONT, option);\r
1830                         break;\r
1831                     }\r
1832                     break;\r
1833                   case TN_DONT:\r
1834                     if (appData.debugMode)\r
1835                       fprintf(debugFP, "\n<DONT ");\r
1836                     switch (option = (unsigned char) buf[++i]) {\r
1837                       default:\r
1838                         if (appData.debugMode)\r
1839                           fprintf(debugFP, "%d ", option);\r
1840                         /* Whatever this is, we are already not doing\r
1841                            it, because we never agree to do anything\r
1842                            non-default, so according to the protocol\r
1843                            rules, we don't reply. */\r
1844                         break;\r
1845                     }\r
1846                     break;\r
1847                   case TN_IAC:\r
1848                     if (appData.debugMode)\r
1849                       fprintf(debugFP, "\n<IAC ");\r
1850                     /* Doubled IAC; pass it through */\r
1851                     i--;\r
1852                     break;\r
1853                   default:\r
1854                     if (appData.debugMode)\r
1855                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
1856                     /* Drop all other telnet commands on the floor */\r
1857                     break;\r
1858                 }\r
1859                 if (oldi > next_out)\r
1860                   SendToPlayer(&buf[next_out], oldi - next_out);\r
1861                 if (++i > next_out)\r
1862                   next_out = i;\r
1863                 continue;\r
1864             }\r
1865                 \r
1866             /* OK, this at least will *usually* work */\r
1867             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
1868                 loggedOn = TRUE;\r
1869             }\r
1870             \r
1871             if (loggedOn && !intfSet) {\r
1872                 if (ics_type == ICS_ICC) {\r
1873                   sprintf(str,\r
1874                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
1875                           programVersion);\r
1876 \r
1877                 } else if (ics_type == ICS_CHESSNET) {\r
1878                   sprintf(str, "/style 12\n");\r
1879                 } else {\r
1880                   strcpy(str, "alias $ @\n$set interface ");\r
1881                   strcat(str, programVersion);\r
1882                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
1883 #ifdef WIN32\r
1884                   strcat(str, "$iset nohighlight 1\n");\r
1885 #endif\r
1886                   strcat(str, "$iset lock 1\n$style 12\n");\r
1887                 }\r
1888                 SendToICS(str);\r
1889                 intfSet = TRUE;\r
1890             }\r
1891 \r
1892             if (started == STARTED_COMMENT) {\r
1893                 /* Accumulate characters in comment */\r
1894                 parse[parse_pos++] = buf[i];\r
1895                 if (buf[i] == '\n') {\r
1896                     parse[parse_pos] = NULLCHAR;\r
1897                     AppendComment(forwardMostMove, StripHighlight(parse));\r
1898                     started = STARTED_NONE;\r
1899                 } else {\r
1900                     /* Don't match patterns against characters in chatter */\r
1901                     i++;\r
1902                     continue;\r
1903                 }\r
1904             }\r
1905             if (started == STARTED_CHATTER) {\r
1906                 if (buf[i] != '\n') {\r
1907                     /* Don't match patterns against characters in chatter */\r
1908                     i++;\r
1909                     continue;\r
1910                 }\r
1911                 started = STARTED_NONE;\r
1912             }\r
1913 \r
1914             /* Kludge to deal with rcmd protocol */\r
1915             if (firstTime && looking_at(buf, &i, "\001*")) {\r
1916                 DisplayFatalError(&buf[1], 0, 1);\r
1917                 continue;\r
1918             } else {\r
1919                 firstTime = FALSE;\r
1920             }\r
1921 \r
1922             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
1923                 ics_type = ICS_ICC;\r
1924                 ics_prefix = "/";\r
1925                 if (appData.debugMode)\r
1926                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1927                 continue;\r
1928             }\r
1929             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
1930                 ics_type = ICS_FICS;\r
1931                 ics_prefix = "$";\r
1932                 if (appData.debugMode)\r
1933                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1934                 continue;\r
1935             }\r
1936             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
1937                 ics_type = ICS_CHESSNET;\r
1938                 ics_prefix = "/";\r
1939                 if (appData.debugMode)\r
1940                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1941                 continue;\r
1942             }\r
1943 \r
1944             if (!loggedOn &&\r
1945                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
1946                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
1947                  looking_at(buf, &i, "will be \"*\""))) {\r
1948               strcpy(ics_handle, star_match[0]);\r
1949               continue;\r
1950             }\r
1951 \r
1952             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
1953               char buf[MSG_SIZ];\r
1954               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
1955               DisplayIcsInteractionTitle(buf);\r
1956               have_set_title = TRUE;\r
1957             }\r
1958 \r
1959             /* skip finger notes */\r
1960             if (started == STARTED_NONE &&\r
1961                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
1962                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
1963                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
1964               started = STARTED_CHATTER;\r
1965               i += 3;\r
1966               continue;\r
1967             }\r
1968 \r
1969             /* skip formula vars */\r
1970             if (started == STARTED_NONE &&\r
1971                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
1972               started = STARTED_CHATTER;\r
1973               i += 3;\r
1974               continue;\r
1975             }\r
1976 \r
1977             oldi = i;\r
1978             if (appData.zippyTalk || appData.zippyPlay) {\r
1979 #if ZIPPY\r
1980                 if (ZippyControl(buf, &i) ||\r
1981                     ZippyConverse(buf, &i) ||\r
1982                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
1983                     loggedOn = TRUE;\r
1984                     continue;\r
1985                 }\r
1986 #endif\r
1987             } else {\r
1988                 if (/* Don't color "message" or "messages" output */\r
1989                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
1990                     looking_at(buf, &i, "*. * at *:*: ") ||\r
1991                     looking_at(buf, &i, "--* (*:*): ") ||\r
1992                     /* Regular tells and says */\r
1993                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
1994                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
1995                     looking_at(buf, &i, "* says: ") ||\r
1996                     /* Message notifications (same color as tells) */\r
1997                     looking_at(buf, &i, "* has left a message ") ||\r
1998                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
1999                     /* Whispers and kibitzes */\r
2000                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2001                     looking_at(buf, &i, "* kibitzes: ") ||\r
2002                     /* Channel tells */\r
2003                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2004 \r
2005                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2006                       /* Avoid "tells you:" spoofs in channels */\r
2007                      tkind = 3;\r
2008                   }\r
2009                   if (star_match[0][0] == NULLCHAR ||\r
2010                       strchr(star_match[0], ' ') ||\r
2011                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2012                     /* Reject bogus matches */\r
2013                     i = oldi;\r
2014                   } else {\r
2015                     if (appData.colorize) {\r
2016                       if (oldi > next_out) {\r
2017                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2018                         next_out = oldi;\r
2019                       }\r
2020                       switch (tkind) {\r
2021                       case 1:\r
2022                         Colorize(ColorTell, FALSE);\r
2023                         curColor = ColorTell;\r
2024                         break;\r
2025                       case 2:\r
2026                         Colorize(ColorKibitz, FALSE);\r
2027                         curColor = ColorKibitz;\r
2028                         break;\r
2029                       case 3:\r
2030                         p = strrchr(star_match[1], '(');\r
2031                         if (p == NULL) {\r
2032                           p = star_match[1];\r
2033                         } else {\r
2034                           p++;\r
2035                         }\r
2036                         if (atoi(p) == 1) {\r
2037                           Colorize(ColorChannel1, FALSE);\r
2038                           curColor = ColorChannel1;\r
2039                         } else {\r
2040                           Colorize(ColorChannel, FALSE);\r
2041                           curColor = ColorChannel;\r
2042                         }\r
2043                         break;\r
2044                       case 5:\r
2045                         curColor = ColorNormal;\r
2046                         break;\r
2047                       }\r
2048                     }\r
2049                     if (started == STARTED_NONE && appData.autoComment &&\r
2050                         (gameMode == IcsObserving ||\r
2051                          gameMode == IcsPlayingWhite ||\r
2052                          gameMode == IcsPlayingBlack)) {\r
2053                       parse_pos = i - oldi;\r
2054                       memcpy(parse, &buf[oldi], parse_pos);\r
2055                       parse[parse_pos] = NULLCHAR;\r
2056                       started = STARTED_COMMENT;\r
2057                       savingComment = TRUE;\r
2058                     } else {\r
2059                       started = STARTED_CHATTER;\r
2060                       savingComment = FALSE;\r
2061                     }\r
2062                     loggedOn = TRUE;\r
2063                     continue;\r
2064                   }\r
2065                 }\r
2066 \r
2067                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2068                     looking_at(buf, &i, "* c-shouts: ")) {\r
2069                     if (appData.colorize) {\r
2070                         if (oldi > next_out) {\r
2071                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2072                             next_out = oldi;\r
2073                         }\r
2074                         Colorize(ColorSShout, FALSE);\r
2075                         curColor = ColorSShout;\r
2076                     }\r
2077                     loggedOn = TRUE;\r
2078                     started = STARTED_CHATTER;\r
2079                     continue;\r
2080                 }\r
2081 \r
2082                 if (looking_at(buf, &i, "--->")) {\r
2083                     loggedOn = TRUE;\r
2084                     continue;\r
2085                 }\r
2086 \r
2087                 if (looking_at(buf, &i, "* shouts: ") ||\r
2088                     looking_at(buf, &i, "--> ")) {\r
2089                     if (appData.colorize) {\r
2090                         if (oldi > next_out) {\r
2091                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2092                             next_out = oldi;\r
2093                         }\r
2094                         Colorize(ColorShout, FALSE);\r
2095                         curColor = ColorShout;\r
2096                     }\r
2097                     loggedOn = TRUE;\r
2098                     started = STARTED_CHATTER;\r
2099                     continue;\r
2100                 }\r
2101 \r
2102                 if (looking_at( buf, &i, "Challenge:")) {\r
2103                     if (appData.colorize) {\r
2104                         if (oldi > next_out) {\r
2105                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2106                             next_out = oldi;\r
2107                         }\r
2108                         Colorize(ColorChallenge, FALSE);\r
2109                         curColor = ColorChallenge;\r
2110                     }\r
2111                     loggedOn = TRUE;\r
2112                     continue;\r
2113                 }\r
2114 \r
2115                 if (looking_at(buf, &i, "* offers you") ||\r
2116                     looking_at(buf, &i, "* offers to be") ||\r
2117                     looking_at(buf, &i, "* would like to") ||\r
2118                     looking_at(buf, &i, "* requests to") ||\r
2119                     looking_at(buf, &i, "Your opponent offers") ||\r
2120                     looking_at(buf, &i, "Your opponent requests")) {\r
2121 \r
2122                     if (appData.colorize) {\r
2123                         if (oldi > next_out) {\r
2124                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2125                             next_out = oldi;\r
2126                         }\r
2127                         Colorize(ColorRequest, FALSE);\r
2128                         curColor = ColorRequest;\r
2129                     }\r
2130                     continue;\r
2131                 }\r
2132 \r
2133                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2134                     if (appData.colorize) {\r
2135                         if (oldi > next_out) {\r
2136                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2137                             next_out = oldi;\r
2138                         }\r
2139                         Colorize(ColorSeek, FALSE);\r
2140                         curColor = ColorSeek;\r
2141                     }\r
2142                     continue;\r
2143                 }\r
2144             }\r
2145 \r
2146             if (looking_at(buf, &i, "\\   ")) {\r
2147                 if (prevColor != ColorNormal) {\r
2148                     if (oldi > next_out) {\r
2149                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2150                         next_out = oldi;\r
2151                     }\r
2152                     Colorize(prevColor, TRUE);\r
2153                     curColor = prevColor;\r
2154                 }\r
2155                 if (savingComment) {\r
2156                     parse_pos = i - oldi;\r
2157                     memcpy(parse, &buf[oldi], parse_pos);\r
2158                     parse[parse_pos] = NULLCHAR;\r
2159                     started = STARTED_COMMENT;\r
2160                 } else {\r
2161                     started = STARTED_CHATTER;\r
2162                 }\r
2163                 continue;\r
2164             }\r
2165 \r
2166             if (looking_at(buf, &i, "Black Strength :") ||\r
2167                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2168                 looking_at(buf, &i, "<10>") ||\r
2169                 looking_at(buf, &i, "#@#")) {\r
2170                 /* Wrong board style */\r
2171                 loggedOn = TRUE;\r
2172                 SendToICS(ics_prefix);\r
2173                 SendToICS("set style 12\n");\r
2174                 SendToICS(ics_prefix);\r
2175                 SendToICS("refresh\n");\r
2176                 continue;\r
2177             }\r
2178             \r
2179             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2180                 ICSInitScript();\r
2181                 have_sent_ICS_logon = 1;\r
2182                 continue;\r
2183             }\r
2184               \r
2185             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2186                 (looking_at(buf, &i, "\n<12> ") ||\r
2187                  looking_at(buf, &i, "<12> "))) {\r
2188                 loggedOn = TRUE;\r
2189                 if (oldi > next_out) {\r
2190                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2191                 }\r
2192                 next_out = i;\r
2193                 started = STARTED_BOARD;\r
2194                 parse_pos = 0;\r
2195                 continue;\r
2196             }\r
2197 \r
2198             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2199                 looking_at(buf, &i, "<b1> ")) {\r
2200                 if (oldi > next_out) {\r
2201                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2202                 }\r
2203                 next_out = i;\r
2204                 started = STARTED_HOLDINGS;\r
2205                 parse_pos = 0;\r
2206                 continue;\r
2207             }\r
2208 \r
2209             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2210                 loggedOn = TRUE;\r
2211                 /* Header for a move list -- first line */\r
2212 \r
2213                 switch (ics_getting_history) {\r
2214                   case H_FALSE:\r
2215                     switch (gameMode) {\r
2216                       case IcsIdle:\r
2217                       case BeginningOfGame:\r
2218                         /* User typed "moves" or "oldmoves" while we\r
2219                            were idle.  Pretend we asked for these\r
2220                            moves and soak them up so user can step\r
2221                            through them and/or save them.\r
2222                            */\r
2223                         Reset(FALSE, TRUE);\r
2224                         gameMode = IcsObserving;\r
2225                         ModeHighlight();\r
2226                         ics_gamenum = -1;\r
2227                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2228                         break;\r
2229                       case EditGame: /*?*/\r
2230                       case EditPosition: /*?*/\r
2231                         /* Should above feature work in these modes too? */\r
2232                         /* For now it doesn't */\r
2233                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2234                         break;\r
2235                       default:\r
2236                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2237                         break;\r
2238                     }\r
2239                     break;\r
2240                   case H_REQUESTED:\r
2241                     /* Is this the right one? */\r
2242                     if (gameInfo.white && gameInfo.black &&\r
2243                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2244                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2245                         /* All is well */\r
2246                         ics_getting_history = H_GOT_REQ_HEADER;\r
2247                     }\r
2248                     break;\r
2249                   case H_GOT_REQ_HEADER:\r
2250                   case H_GOT_UNREQ_HEADER:\r
2251                   case H_GOT_UNWANTED_HEADER:\r
2252                   case H_GETTING_MOVES:\r
2253                     /* Should not happen */\r
2254                     DisplayError("Error gathering move list: two headers", 0);\r
2255                     ics_getting_history = H_FALSE;\r
2256                     break;\r
2257                 }\r
2258 \r
2259                 /* Save player ratings into gameInfo if needed */\r
2260                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2261                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2262                     (gameInfo.whiteRating == -1 ||\r
2263                      gameInfo.blackRating == -1)) {\r
2264 \r
2265                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2266                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2267                     if (appData.debugMode)\r
2268                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2269                               gameInfo.whiteRating, gameInfo.blackRating);\r
2270                 }\r
2271                 continue;\r
2272             }\r
2273 \r
2274             if (looking_at(buf, &i,\r
2275               "* * match, initial time: * minute*, increment: * second")) {\r
2276                 /* Header for a move list -- second line */\r
2277                 /* Initial board will follow if this is a wild game */\r
2278 \r
2279                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2280                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2281                 gameInfo.event = StrSave(str);\r
2282                 gameInfo.variant = StringToVariant(gameInfo.event);\r
2283                 continue;\r
2284             }\r
2285 \r
2286             if (looking_at(buf, &i, "Move  ")) {\r
2287                 /* Beginning of a move list */\r
2288                 switch (ics_getting_history) {\r
2289                   case H_FALSE:\r
2290                     /* Normally should not happen */\r
2291                     /* Maybe user hit reset while we were parsing */\r
2292                     break;\r
2293                   case H_REQUESTED:\r
2294                     /* Happens if we are ignoring a move list that is not\r
2295                      * the one we just requested.  Common if the user\r
2296                      * tries to observe two games without turning off\r
2297                      * getMoveList */\r
2298                     break;\r
2299                   case H_GETTING_MOVES:\r
2300                     /* Should not happen */\r
2301                     DisplayError("Error gathering move list: nested", 0);\r
2302                     ics_getting_history = H_FALSE;\r
2303                     break;\r
2304                   case H_GOT_REQ_HEADER:\r
2305                     ics_getting_history = H_GETTING_MOVES;\r
2306                     started = STARTED_MOVES;\r
2307                     parse_pos = 0;\r
2308                     if (oldi > next_out) {\r
2309                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2310                     }\r
2311                     break;\r
2312                   case H_GOT_UNREQ_HEADER:\r
2313                     ics_getting_history = H_GETTING_MOVES;\r
2314                     started = STARTED_MOVES_NOHIDE;\r
2315                     parse_pos = 0;\r
2316                     break;\r
2317                   case H_GOT_UNWANTED_HEADER:\r
2318                     ics_getting_history = H_FALSE;\r
2319                     break;\r
2320                 }\r
2321                 continue;\r
2322             }                           \r
2323             \r
2324             if (looking_at(buf, &i, "% ") ||\r
2325                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2326                  && looking_at(buf, &i, "}*"))) {\r
2327                 savingComment = FALSE;\r
2328                 switch (started) {\r
2329                   case STARTED_MOVES:\r
2330                   case STARTED_MOVES_NOHIDE:\r
2331                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2332                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2333                     ParseGameHistory(parse);\r
2334 #if ZIPPY\r
2335                     if (appData.zippyPlay && first.initDone) {\r
2336                         FeedMovesToProgram(&first, forwardMostMove);\r
2337                         if (gameMode == IcsPlayingWhite) {\r
2338                             if (WhiteOnMove(forwardMostMove)) {\r
2339                                 if (first.sendTime) {\r
2340                                   if (first.useColors) {\r
2341                                     SendToProgram("black\n", &first); \r
2342                                   }\r
2343                                   SendTimeRemaining(&first, TRUE);\r
2344                                 }\r
2345                                 if (first.useColors) {\r
2346                                   SendToProgram("white\ngo\n", &first);\r
2347                                 } else {\r
2348                                   SendToProgram("go\n", &first);\r
2349                                 }\r
2350                                 first.maybeThinking = TRUE;\r
2351                             } else {\r
2352                                 if (first.usePlayother) {\r
2353                                   if (first.sendTime) {\r
2354                                     SendTimeRemaining(&first, TRUE);\r
2355                                   }\r
2356                                   SendToProgram("playother\n", &first);\r
2357                                   firstMove = FALSE;\r
2358                                 } else {\r
2359                                   firstMove = TRUE;\r
2360                                 }\r
2361                             }\r
2362                         } else if (gameMode == IcsPlayingBlack) {\r
2363                             if (!WhiteOnMove(forwardMostMove)) {\r
2364                                 if (first.sendTime) {\r
2365                                   if (first.useColors) {\r
2366                                     SendToProgram("white\n", &first);\r
2367                                   }\r
2368                                   SendTimeRemaining(&first, FALSE);\r
2369                                 }\r
2370                                 if (first.useColors) {\r
2371                                   SendToProgram("black\ngo\n", &first);\r
2372                                 } else {\r
2373                                   SendToProgram("go\n", &first);\r
2374                                 }\r
2375                                 first.maybeThinking = TRUE;\r
2376                             } else {\r
2377                                 if (first.usePlayother) {\r
2378                                   if (first.sendTime) {\r
2379                                     SendTimeRemaining(&first, FALSE);\r
2380                                   }\r
2381                                   SendToProgram("playother\n", &first);\r
2382                                   firstMove = FALSE;\r
2383                                 } else {\r
2384                                   firstMove = TRUE;\r
2385                                 }\r
2386                             }\r
2387                         }                       \r
2388                     }\r
2389 #endif\r
2390                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2391                         /* Moves came from oldmoves or moves command\r
2392                            while we weren't doing anything else.\r
2393                            */\r
2394                         currentMove = forwardMostMove;\r
2395                         ClearHighlights();/*!!could figure this out*/\r
2396                         flipView = appData.flipView;\r
2397                         DrawPosition(FALSE, boards[currentMove]);\r
2398                         DisplayBothClocks();\r
2399                         sprintf(str, "%s vs. %s",\r
2400                                 gameInfo.white, gameInfo.black);\r
2401                         DisplayTitle(str);\r
2402                         gameMode = IcsIdle;\r
2403                     } else {\r
2404                         /* Moves were history of an active game */\r
2405                         if (gameInfo.resultDetails != NULL) {\r
2406                             free(gameInfo.resultDetails);\r
2407                             gameInfo.resultDetails = NULL;\r
2408                         }\r
2409                     }\r
2410                     HistorySet(parseList, backwardMostMove,\r
2411                                forwardMostMove, currentMove-1);\r
2412                     DisplayMove(currentMove - 1);\r
2413                     if (started == STARTED_MOVES) next_out = i;\r
2414                     started = STARTED_NONE;\r
2415                     ics_getting_history = H_FALSE;\r
2416                     break;\r
2417 \r
2418                   case STARTED_OBSERVE:\r
2419                     started = STARTED_NONE;\r
2420                     SendToICS(ics_prefix);\r
2421                     SendToICS("refresh\n");\r
2422                     break;\r
2423 \r
2424                   default:\r
2425                     break;\r
2426                 }\r
2427                 continue;\r
2428             }\r
2429             \r
2430             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2431                  started == STARTED_HOLDINGS ||\r
2432                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2433                 /* Accumulate characters in move list or board */\r
2434                 parse[parse_pos++] = buf[i];\r
2435             }\r
2436             \r
2437             /* Start of game messages.  Mostly we detect start of game\r
2438                when the first board image arrives.  On some versions\r
2439                of the ICS, though, we need to do a "refresh" after starting\r
2440                to observe in order to get the current board right away. */\r
2441             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2442                 started = STARTED_OBSERVE;\r
2443                 continue;\r
2444             }\r
2445 \r
2446             /* Handle auto-observe */\r
2447             if (appData.autoObserve &&\r
2448                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2449                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2450                 char *player;\r
2451                 /* Choose the player that was highlighted, if any. */\r
2452                 if (star_match[0][0] == '\033' ||\r
2453                     star_match[1][0] != '\033') {\r
2454                     player = star_match[0];\r
2455                 } else {\r
2456                     player = star_match[2];\r
2457                 }\r
2458                 sprintf(str, "%sobserve %s\n",\r
2459                         ics_prefix, StripHighlightAndTitle(player));\r
2460                 SendToICS(str);\r
2461 \r
2462                 /* Save ratings from notify string */\r
2463                 strcpy(player1Name, star_match[0]);\r
2464                 player1Rating = string_to_rating(star_match[1]);\r
2465                 strcpy(player2Name, star_match[2]);\r
2466                 player2Rating = string_to_rating(star_match[3]);\r
2467 \r
2468                 if (appData.debugMode)\r
2469                   fprintf(debugFP, \r
2470                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2471                           player1Name, player1Rating,\r
2472                           player2Name, player2Rating);\r
2473 \r
2474                 continue;\r
2475             }\r
2476 \r
2477             /* Deal with automatic examine mode after a game,\r
2478                and with IcsObserving -> IcsExamining transition */\r
2479             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2480                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2481 \r
2482                 int gamenum = atoi(star_match[0]);\r
2483                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2484                     gamenum == ics_gamenum) {\r
2485                     /* We were already playing or observing this game;\r
2486                        no need to refetch history */\r
2487                     gameMode = IcsExamining;\r
2488                     if (pausing) {\r
2489                         pauseExamForwardMostMove = forwardMostMove;\r
2490                     } else if (currentMove < forwardMostMove) {\r
2491                         ForwardInner(forwardMostMove);\r
2492                     }\r
2493                 } else {\r
2494                     /* I don't think this case really can happen */\r
2495                     SendToICS(ics_prefix);\r
2496                     SendToICS("refresh\n");\r
2497                 }\r
2498                 continue;\r
2499             }    \r
2500             \r
2501             /* Error messages */\r
2502             if (ics_user_moved) {\r
2503                 if (looking_at(buf, &i, "Illegal move") ||\r
2504                     looking_at(buf, &i, "Not a legal move") ||\r
2505                     looking_at(buf, &i, "Your king is in check") ||\r
2506                     looking_at(buf, &i, "It isn't your turn") ||\r
2507                     looking_at(buf, &i, "It is not your move")) {\r
2508                     /* Illegal move */\r
2509                     ics_user_moved = 0;\r
2510                     if (forwardMostMove > backwardMostMove) {\r
2511                         currentMove = --forwardMostMove;\r
2512                         DisplayMove(currentMove - 1); /* before DMError */\r
2513                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2514                         DrawPosition(FALSE, boards[currentMove]);\r
2515                         SwitchClocks();\r
2516                         DisplayBothClocks();\r
2517                     }\r
2518                     continue;\r
2519                 }\r
2520             }\r
2521 \r
2522             if (looking_at(buf, &i, "still have time") ||\r
2523                 looking_at(buf, &i, "not out of time") ||\r
2524                 looking_at(buf, &i, "either player is out of time") ||\r
2525                 looking_at(buf, &i, "has timeseal; checking")) {\r
2526                 /* We must have called his flag a little too soon */\r
2527                 whiteFlag = blackFlag = FALSE;\r
2528                 continue;\r
2529             }\r
2530 \r
2531             if (looking_at(buf, &i, "added * seconds to") ||\r
2532                 looking_at(buf, &i, "seconds were added to")) {\r
2533                 /* Update the clocks */\r
2534                 SendToICS(ics_prefix);\r
2535                 SendToICS("refresh\n");\r
2536                 continue;\r
2537             }\r
2538 \r
2539             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2540                 ics_clock_paused = TRUE;\r
2541                 StopClocks();\r
2542                 continue;\r
2543             }\r
2544 \r
2545             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2546                 ics_clock_paused = FALSE;\r
2547                 StartClocks();\r
2548                 continue;\r
2549             }\r
2550 \r
2551             /* Grab player ratings from the Creating: message.\r
2552                Note we have to check for the special case when\r
2553                the ICS inserts things like [white] or [black]. */\r
2554             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2555                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2556                 /* star_matches:\r
2557                    0    player 1 name (not necessarily white)\r
2558                    1    player 1 rating\r
2559                    2    empty, white, or black (IGNORED)\r
2560                    3    player 2 name (not necessarily black)\r
2561                    4    player 2 rating\r
2562                    \r
2563                    The names/ratings are sorted out when the game\r
2564                    actually starts (below).\r
2565                 */\r
2566                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2567                 player1Rating = string_to_rating(star_match[1]);\r
2568                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2569                 player2Rating = string_to_rating(star_match[4]);\r
2570 \r
2571                 if (appData.debugMode)\r
2572                   fprintf(debugFP, \r
2573                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2574                           player1Name, player1Rating,\r
2575                           player2Name, player2Rating);\r
2576 \r
2577                 continue;\r
2578             }\r
2579             \r
2580             /* Improved generic start/end-of-game messages */\r
2581             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2582                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2583                 /* If tkind == 0: */\r
2584                 /* star_match[0] is the game number */\r
2585                 /*           [1] is the white player's name */\r
2586                 /*           [2] is the black player's name */\r
2587                 /* For end-of-game: */\r
2588                 /*           [3] is the reason for the game end */\r
2589                 /*           [4] is a PGN end game-token, preceded by " " */\r
2590                 /* For start-of-game: */\r
2591                 /*           [3] begins with "Creating" or "Continuing" */\r
2592                 /*           [4] is " *" or empty (don't care). */\r
2593                 int gamenum = atoi(star_match[0]);\r
2594                 char *whitename, *blackname, *why, *endtoken;\r
2595                 ChessMove endtype = (ChessMove) 0;\r
2596 \r
2597                 if (tkind == 0) {\r
2598                   whitename = star_match[1];\r
2599                   blackname = star_match[2];\r
2600                   why = star_match[3];\r
2601                   endtoken = star_match[4];\r
2602                 } else {\r
2603                   whitename = star_match[1];\r
2604                   blackname = star_match[3];\r
2605                   why = star_match[5];\r
2606                   endtoken = star_match[6];\r
2607                 }\r
2608 \r
2609                 /* Game start messages */\r
2610                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2611                     strncmp(why, "Continuing ", 11) == 0) {\r
2612                     gs_gamenum = gamenum;\r
2613                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2614 #if ZIPPY\r
2615                     if (appData.zippyPlay) {\r
2616                         ZippyGameStart(whitename, blackname);\r
2617                     }\r
2618 #endif /*ZIPPY*/\r
2619                     continue;\r
2620                 }\r
2621 \r
2622                 /* Game end messages */\r
2623                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2624                     ics_gamenum != gamenum) {\r
2625                     continue;\r
2626                 }\r
2627                 while (endtoken[0] == ' ') endtoken++;\r
2628                 switch (endtoken[0]) {\r
2629                   case '*':\r
2630                   default:\r
2631                     endtype = GameUnfinished;\r
2632                     break;\r
2633                   case '0':\r
2634                     endtype = BlackWins;\r
2635                     break;\r
2636                   case '1':\r
2637                     if (endtoken[1] == '/')\r
2638                       endtype = GameIsDrawn;\r
2639                     else\r
2640                       endtype = WhiteWins;\r
2641                     break;\r
2642                 }\r
2643                 GameEnds(endtype, why, GE_ICS);\r
2644 #if ZIPPY\r
2645                 if (appData.zippyPlay && first.initDone) {\r
2646                     ZippyGameEnd(endtype, why);\r
2647                     if (first.pr == NULL) {\r
2648                       /* Start the next process early so that we'll\r
2649                          be ready for the next challenge */\r
2650                       StartChessProgram(&first);\r
2651                     }\r
2652                     /* Send "new" early, in case this command takes\r
2653                        a long time to finish, so that we'll be ready\r
2654                        for the next challenge. */\r
2655                     Reset(TRUE, TRUE);\r
2656                 }\r
2657 #endif /*ZIPPY*/\r
2658                 continue;\r
2659             }\r
2660 \r
2661             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2662                 looking_at(buf, &i, "no longer observing game *") ||\r
2663                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2664                 if (gameMode == IcsObserving &&\r
2665                     atoi(star_match[0]) == ics_gamenum)\r
2666                   {\r
2667                       StopClocks();\r
2668                       gameMode = IcsIdle;\r
2669                       ics_gamenum = -1;\r
2670                       ics_user_moved = FALSE;\r
2671                   }\r
2672                 continue;\r
2673             }\r
2674 \r
2675             if (looking_at(buf, &i, "no longer examining game *")) {\r
2676                 if (gameMode == IcsExamining &&\r
2677                     atoi(star_match[0]) == ics_gamenum)\r
2678                   {\r
2679                       gameMode = IcsIdle;\r
2680                       ics_gamenum = -1;\r
2681                       ics_user_moved = FALSE;\r
2682                   }\r
2683                 continue;\r
2684             }\r
2685 \r
2686             /* Advance leftover_start past any newlines we find,\r
2687                so only partial lines can get reparsed */\r
2688             if (looking_at(buf, &i, "\n")) {\r
2689                 prevColor = curColor;\r
2690                 if (curColor != ColorNormal) {\r
2691                     if (oldi > next_out) {\r
2692                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2693                         next_out = oldi;\r
2694                     }\r
2695                     Colorize(ColorNormal, FALSE);\r
2696                     curColor = ColorNormal;\r
2697                 }\r
2698                 if (started == STARTED_BOARD) {\r
2699                     started = STARTED_NONE;\r
2700                     parse[parse_pos] = NULLCHAR;\r
2701                     ParseBoard12(parse);\r
2702                     ics_user_moved = 0;\r
2703 \r
2704                     /* Send premove here */\r
2705                     if (appData.premove) {\r
2706                       char str[MSG_SIZ];\r
2707                       if (currentMove == 0 &&\r
2708                           gameMode == IcsPlayingWhite &&\r
2709                           appData.premoveWhite) {\r
2710                         sprintf(str, "%s%s\n", ics_prefix,\r
2711                                 appData.premoveWhiteText);\r
2712                         if (appData.debugMode)\r
2713                           fprintf(debugFP, "Sending premove:\n");\r
2714                         SendToICS(str);\r
2715                       } else if (currentMove == 1 &&\r
2716                                  gameMode == IcsPlayingBlack &&\r
2717                                  appData.premoveBlack) {\r
2718                         sprintf(str, "%s%s\n", ics_prefix,\r
2719                                 appData.premoveBlackText);\r
2720                         if (appData.debugMode)\r
2721                           fprintf(debugFP, "Sending premove:\n");\r
2722                         SendToICS(str);\r
2723                       } else if (gotPremove) {\r
2724                         gotPremove = 0;\r
2725                         ClearPremoveHighlights();\r
2726                         if (appData.debugMode)\r
2727                           fprintf(debugFP, "Sending premove:\n");\r
2728                           UserMoveEvent(premoveFromX, premoveFromY, \r
2729                                         premoveToX, premoveToY, \r
2730                                         premovePromoChar);\r
2731                       }\r
2732                     }\r
2733 \r
2734                     /* Usually suppress following prompt */\r
2735                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2736                         if (looking_at(buf, &i, "*% ")) {\r
2737                             savingComment = FALSE;\r
2738                         }\r
2739                     }\r
2740                     next_out = i;\r
2741                 } else if (started == STARTED_HOLDINGS) {\r
2742                     int gamenum;\r
2743                     char new_piece[MSG_SIZ];\r
2744                     started = STARTED_NONE;\r
2745                     parse[parse_pos] = NULLCHAR;\r
2746                     if (appData.debugMode)\r
2747                       fprintf(debugFP, "Parsing holdings: %s\n", parse);\r
2748                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
2749                         gamenum == ics_gamenum) {\r
2750                         if (gameInfo.variant == VariantNormal) {\r
2751                           gameInfo.variant = VariantCrazyhouse; /*temp guess*/\r
2752                           /* Get a move list just to see the header, which\r
2753                              will tell us whether this is really bug or zh */\r
2754                           if (ics_getting_history == H_FALSE) {\r
2755                             ics_getting_history = H_REQUESTED;\r
2756                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2757                             SendToICS(str);\r
2758                           }\r
2759                         }\r
2760                         new_piece[0] = NULLCHAR;\r
2761                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
2762                                &gamenum, white_holding, black_holding,\r
2763                                new_piece);\r
2764                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
2765                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
2766 #if ZIPPY\r
2767                         if (appData.zippyPlay && first.initDone) {\r
2768                             ZippyHoldings(white_holding, black_holding,\r
2769                                           new_piece);\r
2770                         }\r
2771 #endif /*ZIPPY*/\r
2772                         if (tinyLayout || smallLayout) {\r
2773                             char wh[16], bh[16];\r
2774                             PackHolding(wh, white_holding);\r
2775                             PackHolding(bh, black_holding);\r
2776                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
2777                                     gameInfo.white, gameInfo.black);\r
2778                         } else {\r
2779                             sprintf(str, "%s [%s] vs. %s [%s]",\r
2780                                     gameInfo.white, white_holding,\r
2781                                     gameInfo.black, black_holding);\r
2782                         }\r
2783                         DrawPosition(FALSE, NULL);\r
2784                         DisplayTitle(str);\r
2785                     }\r
2786                     /* Suppress following prompt */\r
2787                     if (looking_at(buf, &i, "*% ")) {\r
2788                         savingComment = FALSE;\r
2789                     }\r
2790                     next_out = i;\r
2791                 }\r
2792                 continue;\r
2793             }\r
2794 \r
2795             i++;                /* skip unparsed character and loop back */\r
2796         }\r
2797         \r
2798         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
2799             started != STARTED_HOLDINGS && i > next_out) {\r
2800             SendToPlayer(&buf[next_out], i - next_out);\r
2801             next_out = i;\r
2802         }\r
2803         \r
2804         leftover_len = buf_len - leftover_start;\r
2805         /* if buffer ends with something we couldn't parse,\r
2806            reparse it after appending the next read */\r
2807         \r
2808     } else if (count == 0) {\r
2809         RemoveInputSource(isr);\r
2810         DisplayFatalError("Connection closed by ICS", 0, 0);\r
2811     } else {\r
2812         DisplayFatalError("Error reading from ICS", error, 1);\r
2813     }\r
2814 }\r
2815 \r
2816 \r
2817 /* Board style 12 looks like this:\r
2818    \r
2819    <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 0\r
2820    \r
2821  * The "<12> " is stripped before it gets to this routine.  The two\r
2822  * trailing 0's (flip state and clock ticking) are later addition, and\r
2823  * some chess servers may not have them, or may have only the first.\r
2824  * Additional trailing fields may be added in the future.  \r
2825  */\r
2826 \r
2827 #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%d"\r
2828 \r
2829 #define RELATION_OBSERVING_PLAYED    0\r
2830 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
2831 #define RELATION_PLAYING_MYMOVE      1\r
2832 #define RELATION_PLAYING_NOTMYMOVE  -1\r
2833 #define RELATION_EXAMINING           2\r
2834 #define RELATION_ISOLATED_BOARD     -3\r
2835 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
2836 \r
2837 void\r
2838 ParseBoard12(string)\r
2839      char *string;\r
2840\r
2841     GameMode newGameMode;\r
2842     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
2843     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
2844     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
2845     char to_play, board_chars[72];\r
2846     char move_str[500], str[500], elapsed_time[500];\r
2847     char black[32], white[32];\r
2848     Board board;\r
2849     int prevMove = currentMove;\r
2850     int ticking = 2;\r
2851     ChessMove moveType;\r
2852     int fromX, fromY, toX, toY;\r
2853     char promoChar;\r
2854 \r
2855     fromX = fromY = toX = toY = -1;\r
2856     \r
2857     newGame = FALSE;\r
2858 \r
2859     if (appData.debugMode)\r
2860       fprintf(debugFP, "Parsing board: %s\n", string);\r
2861 \r
2862     move_str[0] = NULLCHAR;\r
2863     elapsed_time[0] = NULLCHAR;\r
2864     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
2865                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
2866                &gamenum, white, black, &relation, &basetime, &increment,\r
2867                &white_stren, &black_stren, &white_time, &black_time,\r
2868                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
2869                &ticking);\r
2870 \r
2871     if (n < 22) {\r
2872         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
2873         DisplayError(str, 0);\r
2874         return;\r
2875     }\r
2876 \r
2877     /* Convert the move number to internal form */\r
2878     moveNum = (moveNum - 1) * 2;\r
2879     if (to_play == 'B') moveNum++;\r
2880     if (moveNum >= MAX_MOVES) {\r
2881       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
2882                         0, 1);\r
2883       return;\r
2884     }\r
2885     \r
2886     switch (relation) {\r
2887       case RELATION_OBSERVING_PLAYED:\r
2888       case RELATION_OBSERVING_STATIC:\r
2889         if (gamenum == -1) {\r
2890             /* Old ICC buglet */\r
2891             relation = RELATION_OBSERVING_STATIC;\r
2892         }\r
2893         newGameMode = IcsObserving;\r
2894         break;\r
2895       case RELATION_PLAYING_MYMOVE:\r
2896       case RELATION_PLAYING_NOTMYMOVE:\r
2897         newGameMode =\r
2898           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
2899             IcsPlayingWhite : IcsPlayingBlack;\r
2900         break;\r
2901       case RELATION_EXAMINING:\r
2902         newGameMode = IcsExamining;\r
2903         break;\r
2904       case RELATION_ISOLATED_BOARD:\r
2905       default:\r
2906         /* Just display this board.  If user was doing something else,\r
2907            we will forget about it until the next board comes. */ \r
2908         newGameMode = IcsIdle;\r
2909         break;\r
2910       case RELATION_STARTING_POSITION:\r
2911         newGameMode = gameMode;\r
2912         break;\r
2913     }\r
2914     \r
2915     /* Modify behavior for initial board display on move listing\r
2916        of wild games.\r
2917        */\r
2918     switch (ics_getting_history) {\r
2919       case H_FALSE:\r
2920       case H_REQUESTED:\r
2921         break;\r
2922       case H_GOT_REQ_HEADER:\r
2923       case H_GOT_UNREQ_HEADER:\r
2924         /* This is the initial position of the current game */\r
2925         gamenum = ics_gamenum;\r
2926         moveNum = 0;            /* old ICS bug workaround */\r
2927         if (to_play == 'B') {\r
2928           startedFromSetupPosition = TRUE;\r
2929           blackPlaysFirst = TRUE;\r
2930           moveNum = 1;\r
2931           if (forwardMostMove == 0) forwardMostMove = 1;\r
2932           if (backwardMostMove == 0) backwardMostMove = 1;\r
2933           if (currentMove == 0) currentMove = 1;\r
2934         }\r
2935         newGameMode = gameMode;\r
2936         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
2937         break;\r
2938       case H_GOT_UNWANTED_HEADER:\r
2939         /* This is an initial board that we don't want */\r
2940         return;\r
2941       case H_GETTING_MOVES:\r
2942         /* Should not happen */\r
2943         DisplayError("Error gathering move list: extra board", 0);\r
2944         ics_getting_history = H_FALSE;\r
2945         return;\r
2946     }\r
2947     \r
2948     /* Take action if this is the first board of a new game, or of a\r
2949        different game than is currently being displayed.  */\r
2950     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
2951         relation == RELATION_ISOLATED_BOARD) {\r
2952         \r
2953         /* Forget the old game and get the history (if any) of the new one */\r
2954         if (gameMode != BeginningOfGame) {\r
2955           Reset(FALSE, TRUE);\r
2956         }\r
2957         newGame = TRUE;\r
2958         if (appData.autoRaiseBoard) BoardToTop();\r
2959         prevMove = -3;\r
2960         if (gamenum == -1) {\r
2961             newGameMode = IcsIdle;\r
2962         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
2963                    appData.getMoveList) {\r
2964             /* Need to get game history */\r
2965             ics_getting_history = H_REQUESTED;\r
2966             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2967             SendToICS(str);\r
2968         }\r
2969         \r
2970         /* Initially flip the board to have black on the bottom if playing\r
2971            black or if the ICS flip flag is set, but let the user change\r
2972            it with the Flip View button. */\r
2973         flipView = appData.autoFlipView ? \r
2974           (newGameMode == IcsPlayingBlack) || ics_flip :\r
2975           appData.flipView;\r
2976         \r
2977         /* Done with values from previous mode; copy in new ones */\r
2978         gameMode = newGameMode;\r
2979         ModeHighlight();\r
2980         ics_gamenum = gamenum;\r
2981         if (gamenum == gs_gamenum) {\r
2982             int klen = strlen(gs_kind);\r
2983             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
2984             sprintf(str, "ICS %s", gs_kind);\r
2985             gameInfo.event = StrSave(str);\r
2986         } else {\r
2987             gameInfo.event = StrSave("ICS game");\r
2988         }\r
2989         gameInfo.site = StrSave(appData.icsHost);\r
2990         gameInfo.date = PGNDate();\r
2991         gameInfo.round = StrSave("-");\r
2992         gameInfo.white = StrSave(white);\r
2993         gameInfo.black = StrSave(black);\r
2994         timeControl = basetime * 60 * 1000;\r
2995         timeControl_2 = 0;\r
2996         timeIncrement = increment * 1000;\r
2997         movesPerSession = 0;\r
2998         gameInfo.timeControl = TimeControlTagValue();\r
2999         gameInfo.variant = StringToVariant(gameInfo.event);\r
3000         gameInfo.outOfBook = NULL;\r
3001         \r
3002         /* Do we have the ratings? */\r
3003         if (strcmp(player1Name, white) == 0 &&\r
3004             strcmp(player2Name, black) == 0) {\r
3005             if (appData.debugMode)\r
3006               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3007                       player1Rating, player2Rating);\r
3008             gameInfo.whiteRating = player1Rating;\r
3009             gameInfo.blackRating = player2Rating;\r
3010         } else if (strcmp(player2Name, white) == 0 &&\r
3011                    strcmp(player1Name, black) == 0) {\r
3012             if (appData.debugMode)\r
3013               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3014                       player2Rating, player1Rating);\r
3015             gameInfo.whiteRating = player2Rating;\r
3016             gameInfo.blackRating = player1Rating;\r
3017         }\r
3018         player1Name[0] = player2Name[0] = NULLCHAR;\r
3019 \r
3020         /* Silence shouts if requested */\r
3021         if (appData.quietPlay &&\r
3022             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3023             SendToICS(ics_prefix);\r
3024             SendToICS("set shout 0\n");\r
3025         }\r
3026     }\r
3027     \r
3028     /* Deal with midgame name changes */\r
3029     if (!newGame) {\r
3030         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3031             if (gameInfo.white) free(gameInfo.white);\r
3032             gameInfo.white = StrSave(white);\r
3033         }\r
3034         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3035             if (gameInfo.black) free(gameInfo.black);\r
3036             gameInfo.black = StrSave(black);\r
3037         }\r
3038     }\r
3039     \r
3040     /* Throw away game result if anything actually changes in examine mode */\r
3041     if (gameMode == IcsExamining && !newGame) {\r
3042         gameInfo.result = GameUnfinished;\r
3043         if (gameInfo.resultDetails != NULL) {\r
3044             free(gameInfo.resultDetails);\r
3045             gameInfo.resultDetails = NULL;\r
3046         }\r
3047     }\r
3048     \r
3049     /* In pausing && IcsExamining mode, we ignore boards coming\r
3050        in if they are in a different variation than we are. */\r
3051     if (pauseExamInvalid) return;\r
3052     if (pausing && gameMode == IcsExamining) {\r
3053         if (moveNum <= pauseExamForwardMostMove) {\r
3054             pauseExamInvalid = TRUE;\r
3055             forwardMostMove = pauseExamForwardMostMove;\r
3056             return;\r
3057         }\r
3058     }\r
3059     \r
3060     /* Parse the board */\r
3061     for (k = 0; k < 8; k++)\r
3062       for (j = 0; j < 8; j++)\r
3063         board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3064     CopyBoard(boards[moveNum], board);\r
3065     if (moveNum == 0) {\r
3066         startedFromSetupPosition =\r
3067           !CompareBoards(board, initialPosition);\r
3068     }\r
3069     \r
3070     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3071         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3072         /* This was an initial position from a move list, not\r
3073            the current position */\r
3074         return;\r
3075     }\r
3076     \r
3077     /* Update currentMove and known move number limits */\r
3078     newMove = newGame || moveNum > forwardMostMove;\r
3079     if (newGame) {\r
3080         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3081         if (gameMode == IcsExamining && moveNum == 0) {\r
3082           /* Workaround for ICS limitation: we are not told the wild\r
3083              type when starting to examine a game.  But if we ask for\r
3084              the move list, the move list header will tell us */\r
3085             ics_getting_history = H_REQUESTED;\r
3086             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3087             SendToICS(str);\r
3088         }\r
3089     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3090                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3091         forwardMostMove = moveNum;\r
3092         if (!pausing || currentMove > forwardMostMove)\r
3093           currentMove = forwardMostMove;\r
3094     } else {\r
3095         /* New part of history that is not contiguous with old part */ \r
3096         if (pausing && gameMode == IcsExamining) {\r
3097             pauseExamInvalid = TRUE;\r
3098             forwardMostMove = pauseExamForwardMostMove;\r
3099             return;\r
3100         }\r
3101         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3102         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3103             ics_getting_history = H_REQUESTED;\r
3104             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3105             SendToICS(str);\r
3106         }\r
3107     }\r
3108     \r
3109     /* Update the clocks */\r
3110     if (strchr(elapsed_time, '.')) {\r
3111       /* Time is in ms */\r
3112       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3113       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3114     } else {\r
3115       /* Time is in seconds */\r
3116       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3117       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3118     }\r
3119       \r
3120 \r
3121 #if ZIPPY\r
3122     if (appData.zippyPlay && newGame &&\r
3123         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3124         gameMode != IcsExamining)\r
3125       ZippyFirstBoard(moveNum, basetime, increment);\r
3126 #endif\r
3127     \r
3128     /* Put the move on the move list, first converting\r
3129        to canonical algebraic form. */\r
3130     if (moveNum > 0) {\r
3131         if (moveNum <= backwardMostMove) {\r
3132             /* We don't know what the board looked like before\r
3133                this move.  Punt. */\r
3134             strcpy(parseList[moveNum - 1], move_str);\r
3135             strcat(parseList[moveNum - 1], " ");\r
3136             strcat(parseList[moveNum - 1], elapsed_time);\r
3137             moveList[moveNum - 1][0] = NULLCHAR;\r
3138         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3139                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3140             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3141                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3142                                      fromY, fromX, toY, toX, promoChar,\r
3143                                      parseList[moveNum-1]);\r
3144             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3145                              castlingRights[moveNum]) ) {\r
3146               case MT_NONE:\r
3147               case MT_STALEMATE:\r
3148               default:\r
3149                 break;\r
3150               case MT_CHECK:\r
3151                 strcat(parseList[moveNum - 1], "+");\r
3152                 break;\r
3153               case MT_CHECKMATE:\r
3154                 strcat(parseList[moveNum - 1], "#");\r
3155                 break;\r
3156             }\r
3157             strcat(parseList[moveNum - 1], " ");\r
3158             strcat(parseList[moveNum - 1], elapsed_time);\r
3159             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3160             strcpy(moveList[moveNum - 1], currentMoveString);\r
3161             strcat(moveList[moveNum - 1], "\n");\r
3162         } else if (strcmp(move_str, "none") == 0) {\r
3163             /* Again, we don't know what the board looked like;\r
3164                this is really the start of the game. */\r
3165             parseList[moveNum - 1][0] = NULLCHAR;\r
3166             moveList[moveNum - 1][0] = NULLCHAR;\r
3167             backwardMostMove = moveNum;\r
3168             startedFromSetupPosition = TRUE;\r
3169             fromX = fromY = toX = toY = -1;\r
3170         } else {\r
3171             /* Move from ICS was illegal!?  Punt. */\r
3172 #if 0\r
3173             if (appData.testLegality && appData.debugMode) {\r
3174                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3175                 DisplayError(str, 0);\r
3176             }\r
3177 #endif\r
3178             strcpy(parseList[moveNum - 1], move_str);\r
3179             strcat(parseList[moveNum - 1], " ");\r
3180             strcat(parseList[moveNum - 1], elapsed_time);\r
3181             moveList[moveNum - 1][0] = NULLCHAR;\r
3182             fromX = fromY = toX = toY = -1;\r
3183         }\r
3184 \r
3185 #if ZIPPY\r
3186         /* Send move to chess program (BEFORE animating it). */\r
3187         if (appData.zippyPlay && !newGame && newMove && \r
3188            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3189 \r
3190             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3191                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3192                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3193                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3194                             move_str);\r
3195                     DisplayError(str, 0);\r
3196                 } else {\r
3197                     if (first.sendTime) {\r
3198                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3199                     }\r
3200                     SendMoveToProgram(moveNum - 1, &first);\r
3201                     if (firstMove) {\r
3202                         firstMove = FALSE;\r
3203                         if (first.useColors) {\r
3204                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3205                                         "white\ngo\n" :\r
3206                                         "black\ngo\n", &first);\r
3207                         } else {\r
3208                           SendToProgram("go\n", &first);\r
3209                         }\r
3210                         first.maybeThinking = TRUE;\r
3211                     }\r
3212                 }\r
3213             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3214               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3215                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3216                 DisplayError(str, 0);\r
3217               } else {\r
3218                 SendMoveToProgram(moveNum - 1, &first);\r
3219               }\r
3220             }\r
3221         }\r
3222 #endif\r
3223     }\r
3224 \r
3225     if (moveNum > 0 && !gotPremove) {\r
3226         /* If move comes from a remote source, animate it.  If it\r
3227            isn't remote, it will have already been animated. */\r
3228         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3229             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3230         }\r
3231         if (!pausing && appData.highlightLastMove) {\r
3232             SetHighlights(fromX, fromY, toX, toY);\r
3233         }\r
3234     }\r
3235     \r
3236     /* Start the clocks */\r
3237     whiteFlag = blackFlag = FALSE;\r
3238     appData.clockMode = !(basetime == 0 && increment == 0);\r
3239     if (ticking == 0) {\r
3240       ics_clock_paused = TRUE;\r
3241       StopClocks();\r
3242     } else if (ticking == 1) {\r
3243       ics_clock_paused = FALSE;\r
3244     }\r
3245     if (gameMode == IcsIdle ||\r
3246         relation == RELATION_OBSERVING_STATIC ||\r
3247         relation == RELATION_EXAMINING ||\r
3248         ics_clock_paused)\r
3249       DisplayBothClocks();\r
3250     else\r
3251       StartClocks();\r
3252     \r
3253     /* Display opponents and material strengths */\r
3254     if (gameInfo.variant != VariantBughouse &&\r
3255         gameInfo.variant != VariantCrazyhouse) {\r
3256         if (tinyLayout || smallLayout) {\r
3257             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3258                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3259                     basetime, increment);\r
3260         } else {\r
3261             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3262                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3263                     basetime, increment);\r
3264         }\r
3265         DisplayTitle(str);\r
3266     }\r
3267 \r
3268    \r
3269     /* Display the board */\r
3270     if (!pausing) {\r
3271       \r
3272       if (appData.premove)\r
3273           if (!gotPremove || \r
3274              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3275              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3276               ClearPremoveHighlights();\r
3277 \r
3278       DrawPosition(FALSE, boards[currentMove]);\r
3279       DisplayMove(moveNum - 1);\r
3280       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3281         RingBell();\r
3282     }\r
3283 \r
3284     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3285 }\r
3286 \r
3287 void\r
3288 GetMoveListEvent()\r
3289 {\r
3290     char buf[MSG_SIZ];\r
3291     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3292         ics_getting_history = H_REQUESTED;\r
3293         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3294         SendToICS(buf);\r
3295     }\r
3296 }\r
3297 \r
3298 void\r
3299 AnalysisPeriodicEvent(force)\r
3300      int force;\r
3301 {\r
3302     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3303          && !force) || !appData.periodicUpdates)\r
3304       return;\r
3305 \r
3306     /* Send . command to Crafty to collect stats */\r
3307     SendToProgram(".\n", &first);\r
3308 \r
3309     /* Don't send another until we get a response (this makes\r
3310        us stop sending to old Crafty's which don't understand\r
3311        the "." command (sending illegal cmds resets node count & time,\r
3312        which looks bad)) */\r
3313     programStats.ok_to_send = 0;\r
3314 }\r
3315 \r
3316 void\r
3317 SendMoveToProgram(moveNum, cps)\r
3318      int moveNum;\r
3319      ChessProgramState *cps;\r
3320 {\r
3321     char buf[MSG_SIZ];\r
3322     if (cps->useUsermove) {\r
3323       SendToProgram("usermove ", cps);\r
3324     }\r
3325     if (cps->useSAN) {\r
3326       char *space;\r
3327       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3328         int len = space - parseList[moveNum];\r
3329         memcpy(buf, parseList[moveNum], len);\r
3330         buf[len++] = '\n';\r
3331         buf[len] = NULLCHAR;\r
3332       } else {\r
3333         sprintf(buf, "%s\n", parseList[moveNum]);\r
3334       }\r
3335       /* [HGM] decrement all digits to code ranks starting from 0 */\r
3336       if(BOARD_HEIGHT>8) {\r
3337           char *p = buf;\r
3338           while(*p) { if(*p < 'A') (*p)--; p++; }\r
3339       }\r
3340       SendToProgram(buf, cps);\r
3341     } else {\r
3342       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3343        * the engine. It would be nice to have a better way to identify castle \r
3344        * moves here. */\r
3345       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3346         int fromX = moveList[moveNum][0] - 'a'; \r
3347         int fromY = moveList[moveNum][1] - ONE;\r
3348         int toX = moveList[moveNum][2] - 'a'; \r
3349         int toY = moveList[moveNum][3] - ONE;\r
3350         if((boards[currentMove][fromY][fromX] == WhiteKing \r
3351             && boards[currentMove][toY][toX] == WhiteRook)\r
3352            || (boards[currentMove][fromY][fromX] == BlackKing \r
3353                && boards[currentMove][toY][toX] == BlackRook)) {\r
3354           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3355           else SendToProgram("O-O-O\n", cps);\r
3356         }\r
3357         else SendToProgram(moveList[moveNum], cps);\r
3358       }\r
3359       else SendToProgram(moveList[moveNum], cps);\r
3360       /* End of additions by Tord */\r
3361     }\r
3362 }\r
3363 \r
3364 void\r
3365 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3366      ChessMove moveType;\r
3367      int fromX, fromY, toX, toY;\r
3368 {\r
3369     char user_move[MSG_SIZ];\r
3370 \r
3371     switch (moveType) {\r
3372       default:\r
3373         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3374                 (int)moveType, fromX, fromY, toX, toY);\r
3375         DisplayError(user_move + strlen("say "), 0);\r
3376         break;\r
3377       case WhiteKingSideCastle:\r
3378       case BlackKingSideCastle:\r
3379       case WhiteQueenSideCastleWild:\r
3380       case BlackQueenSideCastleWild:\r
3381       /* PUSH Fabien */\r
3382       case WhiteHSideCastleFR:\r
3383       case BlackHSideCastleFR:\r
3384       /* POP Fabien */\r
3385         sprintf(user_move, "o-o\n");\r
3386         break;\r
3387       case WhiteQueenSideCastle:\r
3388       case BlackQueenSideCastle:\r
3389       case WhiteKingSideCastleWild:\r
3390       case BlackKingSideCastleWild:\r
3391       /* PUSH Fabien */\r
3392       case WhiteASideCastleFR:\r
3393       case BlackASideCastleFR:\r
3394       /* POP Fabien */\r
3395         sprintf(user_move, "o-o-o\n");\r
3396         break;\r
3397       case WhitePromotionQueen:\r
3398       case BlackPromotionQueen:\r
3399       case WhitePromotionRook:\r
3400       case BlackPromotionRook:\r
3401       case WhitePromotionBishop:\r
3402       case BlackPromotionBishop:\r
3403       case WhitePromotionKnight:\r
3404       case BlackPromotionKnight:\r
3405       case WhitePromotionKing:\r
3406       case BlackPromotionKing:\r
3407 #ifdef FAIRY\r
3408       case WhitePromotionChancellor:\r
3409       case BlackPromotionChancellor:\r
3410       case WhitePromotionArchbishop:\r
3411       case BlackPromotionArchbishop:\r
3412 #endif\r
3413         sprintf(user_move, "%c%c%c%c=%c\n",\r
3414                 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY,\r
3415                 PieceToChar(PromoPiece(moveType)));\r
3416         break;\r
3417       case WhiteDrop:\r
3418       case BlackDrop:\r
3419         sprintf(user_move, "%c@%c%c\n",\r
3420                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3421                 'a' + toX, ONE + toY);\r
3422         break;\r
3423       case NormalMove:\r
3424       case WhiteCapturesEnPassant:\r
3425       case BlackCapturesEnPassant:\r
3426       case IllegalMove:  /* could be a variant we don't quite understand */\r
3427         sprintf(user_move, "%c%c%c%c\n",\r
3428                 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY);\r
3429         break;\r
3430     }\r
3431     SendToICS(user_move);\r
3432 }\r
3433 \r
3434 void\r
3435 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3436      int rf, ff, rt, ft;\r
3437      char promoChar;\r
3438      char move[7];\r
3439 {\r
3440     if (rf == DROP_RANK) {\r
3441         sprintf(move, "%c@%c%c\n",\r
3442                 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, ONE + rt);\r
3443     } else {\r
3444         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3445             sprintf(move, "%c%c%c%c\n",\r
3446                     'a' + ff, ONE + rf, 'a' + ft, ONE + rt);\r
3447         } else {\r
3448             sprintf(move, "%c%c%c%c%c\n",\r
3449                     'a' + ff, ONE + rf, 'a' + ft, ONE + rt, promoChar);\r
3450         }\r
3451     }\r
3452 }\r
3453 \r
3454 void\r
3455 ProcessICSInitScript(f)\r
3456      FILE *f;\r
3457 {\r
3458     char buf[MSG_SIZ];\r
3459 \r
3460     while (fgets(buf, MSG_SIZ, f)) {\r
3461         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3462     }\r
3463 \r
3464     fclose(f);\r
3465 }\r
3466 \r
3467 \r
3468 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3469 Boolean\r
3470 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3471      char *move;\r
3472      int moveNum;\r
3473      ChessMove *moveType;\r
3474      int *fromX, *fromY, *toX, *toY;\r
3475      char *promoChar;\r
3476 {       \r
3477     *moveType = yylexstr(moveNum, move);\r
3478 \r
3479     switch (*moveType) {\r
3480 #ifdef FAIRY\r
3481       case WhitePromotionChancellor:\r
3482       case BlackPromotionChancellor:\r
3483       case WhitePromotionArchbishop:\r
3484       case BlackPromotionArchbishop:\r
3485 #endif\r
3486       case WhitePromotionQueen:\r
3487       case BlackPromotionQueen:\r
3488       case WhitePromotionRook:\r
3489       case BlackPromotionRook:\r
3490       case WhitePromotionBishop:\r
3491       case BlackPromotionBishop:\r
3492       case WhitePromotionKnight:\r
3493       case BlackPromotionKnight:\r
3494       case WhitePromotionKing:\r
3495       case BlackPromotionKing:\r
3496       case NormalMove:\r
3497       case WhiteCapturesEnPassant:\r
3498       case BlackCapturesEnPassant:\r
3499       case WhiteKingSideCastle:\r
3500       case WhiteQueenSideCastle:\r
3501       case BlackKingSideCastle:\r
3502       case BlackQueenSideCastle:\r
3503       case WhiteKingSideCastleWild:\r
3504       case WhiteQueenSideCastleWild:\r
3505       case BlackKingSideCastleWild:\r
3506       case BlackQueenSideCastleWild:\r
3507       /* Code added by Tord: */\r
3508       case WhiteHSideCastleFR:\r
3509       case WhiteASideCastleFR:\r
3510       case BlackHSideCastleFR:\r
3511       case BlackASideCastleFR:\r
3512       /* End of code added by Tord */\r
3513       case IllegalMove:         /* bug or odd chess variant */\r
3514         *fromX = currentMoveString[0] - 'a';\r
3515         *fromY = currentMoveString[1] - ONE;\r
3516         *toX = currentMoveString[2] - 'a';\r
3517         *toY = currentMoveString[3] - ONE;\r
3518         *promoChar = currentMoveString[4];\r
3519         if (*fromX < 0 || *fromX >= BOARD_WIDTH || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3520             *toX < 0 || *toX >= BOARD_WIDTH || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3521             *fromX = *fromY = *toX = *toY = 0;\r
3522             return FALSE;\r
3523         }\r
3524         if (appData.testLegality) {\r
3525           return (*moveType != IllegalMove);\r
3526         } else {\r
3527           return !(fromX == fromY && toX == toY);\r
3528         }\r
3529 \r
3530       case WhiteDrop:\r
3531       case BlackDrop:\r
3532         *fromX = *moveType == WhiteDrop ?\r
3533           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3534         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3535         *fromY = DROP_RANK;\r
3536         *toX = currentMoveString[2] - 'a';\r
3537         *toY = currentMoveString[3] - ONE;\r
3538         *promoChar = NULLCHAR;\r
3539         return TRUE;\r
3540 \r
3541       case AmbiguousMove:\r
3542       case ImpossibleMove:\r
3543       case (ChessMove) 0:       /* end of file */\r
3544       case ElapsedTime:\r
3545       case Comment:\r
3546       case PGNTag:\r
3547       case NAG:\r
3548       case WhiteWins:\r
3549       case BlackWins:\r
3550       case GameIsDrawn:\r
3551       default:\r
3552         /* bug? */\r
3553         *fromX = *fromY = *toX = *toY = 0;\r
3554         *promoChar = NULLCHAR;\r
3555         return FALSE;\r
3556     }\r
3557 }\r
3558 \r
3559 /* [AS] FRC game initialization */\r
3560 static int FindEmptySquare( Board board, int n )\r
3561 {\r
3562     int i = 0;\r
3563 \r
3564     while( 1 ) {\r
3565         while( board[0][i] != EmptySquare ) i++;\r
3566         if( n == 0 )\r
3567             break;\r
3568         n--;\r
3569         i++;\r
3570     }\r
3571 \r
3572     return i;\r
3573 }\r
3574 \r
3575 static void ShuffleFRC( Board board )\r
3576 {\r
3577     int i;\r
3578 \r
3579     srand( time(0) );\r
3580     \r
3581     for( i=0; i<8; i++ ) {\r
3582         board[0][i] = EmptySquare;\r
3583     }\r
3584 \r
3585     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
3586     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
3587     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
3588     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
3589     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
3590     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3591     board[0][FindEmptySquare(board, 0)] = WhiteKing;\r
3592     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3593 \r
3594     for( i=0; i<BOARD_WIDTH; i++ ) {\r
3595         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3596     }\r
3597 }\r
3598 \r
3599 static unsigned char FRC_KnightTable[10] = {\r
3600     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
3601 };\r
3602 \r
3603 static void SetupFRC( Board board, int pos_index )\r
3604 {\r
3605     int i;\r
3606     unsigned char knights;\r
3607 \r
3608     /* Bring the position index into a safe range (just in case...) */\r
3609     if( pos_index < 0 ) pos_index = 0;\r
3610 \r
3611     pos_index %= 960;\r
3612 \r
3613     /* Clear the board */\r
3614     for( i=0; i<8; i++ ) {\r
3615         board[0][i] = EmptySquare;\r
3616     }\r
3617 \r
3618     /* Place bishops and queen */\r
3619     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
3620     pos_index /= 4;\r
3621     \r
3622     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
3623     pos_index /= 4;\r
3624 \r
3625     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
3626     pos_index /= 6;\r
3627 \r
3628     /* Place knigths */\r
3629     knights = FRC_KnightTable[ pos_index ];\r
3630 \r
3631     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
3632     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
3633 \r
3634     /* Place rooks and king */\r
3635     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3636     board[0][ FindEmptySquare(board, 0) ] = WhiteKing;\r
3637     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3638 \r
3639     /* Mirror piece placement for black */\r
3640     for( i=0; i<BOARD_WIDTH; i++ ) {\r
3641         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3642     }\r
3643 }\r
3644 \r
3645 void\r
3646 InitPosition(redraw)\r
3647      int redraw;\r
3648 {\r
3649     ChessSquare (* pieces)[BOARD_SIZE];\r
3650     int i, j;\r
3651 \r
3652     currentMove = forwardMostMove = backwardMostMove = 0;\r
3653 \r
3654     /* [AS] Initialize pv info list [HGM] and game status */\r
3655     {\r
3656         for( i=0; i<MAX_MOVES; i++ ) {\r
3657             pvInfoList[i].depth = 0;\r
3658             epStatus[i]=EP_NONE;\r
3659             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
3660         }\r
3661 \r
3662         /* [HGM] Build normal castling rights */\r
3663         for( j=0; j<BOARD_SIZE; j++ ) initialRights[j] = -1;\r
3664         nrCastlingRights = 6;\r
3665         castlingRights[0][0] = initialRights[0] = BOARD_WIDTH-1;\r
3666         castlingRights[0][1] = initialRights[1] = 0;\r
3667         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
3668         castlingRights[0][3] = initialRights[3] = BOARD_WIDTH-1;\r
3669         castlingRights[0][4] = initialRights[4] = 0;\r
3670         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
3671 \r
3672         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
3673         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
3674 \r
3675         initialRulePlies = 0; /* 50-move counter start */\r
3676     }\r
3677 \r
3678     \r
3679     /* [HGM] logic here is completely changed. In stead of full positions */\r
3680     /* the initialized data only consist of the two backranks. The switch */\r
3681     /* selects which one we will use, which is than copied to the Board   */\r
3682     /* initialPosition, which for the rest is initialized by Pawns and    */\r
3683     /* empty squares. This initial position is then copied to boards[0],  */\r
3684     /* possibly after shuffling, so that it remains available.            */\r
3685 \r
3686     switch (gameInfo.variant) {\r
3687     default:\r
3688       pieces = BOARD_WIDTH <= 8  ? FIDEArray :\r
3689                BOARD_WIDTH <= 10 ? CapablancaArray : CourierArray;\r
3690       break;\r
3691     case VariantShatranj:\r
3692       pieces = ShatranjArray;\r
3693       CharToPiece('E'); /* associate PGN/FEN letter with internal piece type */\r
3694       CharToPiece('e');\r
3695       nrCastlingRights = 0;\r
3696       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3697       break;\r
3698     case VariantTwoKings:\r
3699       pieces = twoKingsArray;\r
3700       nrCastlingRights = 8;                 /* add rights for second King */\r
3701       castlingRights[0][6] = initialRights[2] = 5;\r
3702       castlingRights[0][7] = initialRights[5] = 5;\r
3703       castlingRank[6] = 0;\r
3704       castlingRank[6] = BOARD_HEIGHT-1;\r
3705       startedFromSetupPosition = TRUE;\r
3706       break;\r
3707 #ifdef FAIRY\r
3708     case VariantCapablanca:\r
3709       pieces = CapablancaArray;\r
3710       break;\r
3711     case VariantGothic:\r
3712       pieces = GothicArray;\r
3713       break;\r
3714     case VariantXiangqi:\r
3715       pieces = XiangqiArray;\r
3716       break;\r
3717     case VariantCourier:\r
3718       pieces = CourierArray;\r
3719       nrCastlingRights = 0;\r
3720       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3721       break;\r
3722     case VariantFairy:\r
3723       pieces = fairyArray;\r
3724       startedFromSetupPosition = TRUE;\r
3725       break;\r
3726 #endif\r
3727     case VariantWildCastle:\r
3728       pieces = FIDEArray;\r
3729       /* !!?shuffle with kings guaranteed to be on d or e file */\r
3730       break;\r
3731     case VariantNoCastle:\r
3732       pieces = FIDEArray;\r
3733       nrCastlingRights = 0;\r
3734       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3735       /* !!?unconstrained back-rank shuffle */\r
3736       break;\r
3737     }\r
3738 \r
3739     for( j=0; j<BOARD_WIDTH; j++ ) {\r
3740         for( i=0; i<BOARD_HEIGHT; i++ )\r
3741             initialPosition[i][j] = EmptySquare;\r
3742         initialPosition[0][j] = pieces[0][j];\r
3743         if(gameInfo.variant == VariantXiangqi) {\r
3744             if(j&1) {\r
3745                 initialPosition[3][j] = \r
3746                 initialPosition[BOARD_HEIGHT-4][j] = EmptySquare;\r
3747                 if(j==1 || j>=BOARD_WIDTH-2) {\r
3748                     initialPosition[2][j] = WhiteFairyMarshall;\r
3749                     initialPosition[BOARD_HEIGHT-3][j] = BlackFairyMarshall;\r
3750                 }\r
3751             } else {\r
3752                 initialPosition[3][j] = WhitePawn;\r
3753                 initialPosition[BOARD_HEIGHT-4][j] = BlackPawn;\r
3754             }\r
3755         } else {\r
3756             initialPosition[1][j] = WhitePawn;\r
3757             initialPosition[BOARD_HEIGHT-2][j] = BlackPawn;\r
3758         }\r
3759         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j];\r
3760     }\r
3761 \r
3762     if(gameInfo.variant == VariantFischeRandom) {\r
3763       if( appData.defaultFrcPosition < 0 ) {\r
3764         ShuffleFRC( initialPosition );\r
3765       }\r
3766       else {\r
3767         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
3768       }\r
3769     }\r
3770 \r
3771     CopyBoard(boards[0], initialPosition);\r
3772 \r
3773     if (redraw)\r
3774       DrawPosition(TRUE, boards[currentMove]);\r
3775 }\r
3776 \r
3777 void\r
3778 SendBoard(cps, moveNum)\r
3779      ChessProgramState *cps;\r
3780      int moveNum;\r
3781 {\r
3782     char message[MSG_SIZ];\r
3783     \r
3784     if (cps->useSetboard) {\r
3785       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
3786       sprintf(message, "setboard %s\n", fen);\r
3787       SendToProgram(message, cps);\r
3788       free(fen);\r
3789 \r
3790     } else {\r
3791       ChessSquare *bp;\r
3792       int i, j;\r
3793       /* Kludge to set black to move, avoiding the troublesome and now\r
3794        * deprecated "black" command.\r
3795        */\r
3796       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
3797 \r
3798       SendToProgram("edit\n", cps);\r
3799       SendToProgram("#\n", cps);\r
3800       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
3801         bp = &boards[moveNum][i][0];\r
3802         for (j = 0; j < BOARD_WIDTH; j++, bp++) {\r
3803           if ((int) *bp < (int) BlackPawn) {\r
3804             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
3805                     'a' + j, ONE + i);\r
3806             SendToProgram(message, cps);\r
3807           }\r
3808         }\r
3809       }\r
3810     \r
3811       SendToProgram("c\n", cps);\r
3812       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
3813         bp = &boards[moveNum][i][0];\r
3814         for (j = 0; j < BOARD_WIDTH; j++, bp++) {\r
3815           if (((int) *bp != (int) EmptySquare)\r
3816               && ((int) *bp >= (int) BlackPawn)) {\r
3817             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
3818                     'a' + j, ONE + i);\r
3819             SendToProgram(message, cps);\r
3820           }\r
3821         }\r
3822       }\r
3823     \r
3824       SendToProgram(".\n", cps);\r
3825     }\r
3826 }\r
3827 \r
3828 int\r
3829 IsPromotion(fromX, fromY, toX, toY)\r
3830      int fromX, fromY, toX, toY;\r
3831 {\r
3832     return gameMode != EditPosition && gameInfo.variant != VariantXiangqi &&\r
3833       fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&\r
3834         ((boards[currentMove][fromY][fromX] == WhitePawn && toY == BOARD_HEIGHT-1) ||\r
3835          (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));\r
3836 }\r
3837 \r
3838 \r
3839 int\r
3840 PieceForSquare (x, y)\r
3841      int x;\r
3842      int y;\r
3843 {\r
3844   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
3845      return -1;\r
3846   else\r
3847      return boards[currentMove][y][x];\r
3848 }\r
3849 \r
3850 int\r
3851 OKToStartUserMove(x, y)\r
3852      int x, y;\r
3853 {\r
3854     ChessSquare from_piece;\r
3855     int white_piece;\r
3856 \r
3857     if (matchMode) return FALSE;\r
3858     if (gameMode == EditPosition) return TRUE;\r
3859 \r
3860     if (x >= 0 && y >= 0)\r
3861       from_piece = boards[currentMove][y][x];\r
3862     else\r
3863       from_piece = EmptySquare;\r
3864 \r
3865     if (from_piece == EmptySquare) return FALSE;\r
3866 \r
3867     white_piece = (int)from_piece >= (int)WhitePawn &&\r
3868       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
3869 \r
3870     switch (gameMode) {\r
3871       case PlayFromGameFile:\r
3872       case AnalyzeFile:\r
3873       case TwoMachinesPlay:\r
3874       case EndOfGame:\r
3875         return FALSE;\r
3876 \r
3877       case IcsObserving:\r
3878       case IcsIdle:\r
3879         return FALSE;\r
3880 \r
3881       case MachinePlaysWhite:\r
3882       case IcsPlayingBlack:\r
3883         if (appData.zippyPlay) return FALSE;\r
3884         if (white_piece) {\r
3885             DisplayMoveError("You are playing Black");\r
3886             return FALSE;\r
3887         }\r
3888         break;\r
3889 \r
3890       case MachinePlaysBlack:\r
3891       case IcsPlayingWhite:\r
3892         if (appData.zippyPlay) return FALSE;\r
3893         if (!white_piece) {\r
3894             DisplayMoveError("You are playing White");\r
3895             return FALSE;\r
3896         }\r
3897         break;\r
3898 \r
3899       case EditGame:\r
3900         if (!white_piece && WhiteOnMove(currentMove)) {\r
3901             DisplayMoveError("It is White's turn");\r
3902             return FALSE;\r
3903         }           \r
3904         if (white_piece && !WhiteOnMove(currentMove)) {\r
3905             DisplayMoveError("It is Black's turn");\r
3906             return FALSE;\r
3907         }           \r
3908         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
3909             /* Editing correspondence game history */\r
3910             /* Could disallow this or prompt for confirmation */\r
3911             cmailOldMove = -1;\r
3912         }\r
3913         if (currentMove < forwardMostMove) {\r
3914             /* Discarding moves */\r
3915             /* Could prompt for confirmation here,\r
3916                but I don't think that's such a good idea */\r
3917             forwardMostMove = currentMove;\r
3918         }\r
3919         break;\r
3920 \r
3921       case BeginningOfGame:\r
3922         if (appData.icsActive) return FALSE;\r
3923         if (!appData.noChessProgram) {\r
3924             if (!white_piece) {\r
3925                 DisplayMoveError("You are playing White");\r
3926                 return FALSE;\r
3927             }\r
3928         }\r
3929         break;\r
3930         \r
3931       case Training:\r
3932         if (!white_piece && WhiteOnMove(currentMove)) {\r
3933             DisplayMoveError("It is White's turn");\r
3934             return FALSE;\r
3935         }           \r
3936         if (white_piece && !WhiteOnMove(currentMove)) {\r
3937             DisplayMoveError("It is Black's turn");\r
3938             return FALSE;\r
3939         }           \r
3940         break;\r
3941 \r
3942       default:\r
3943       case IcsExamining:\r
3944         break;\r
3945     }\r
3946     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
3947         && gameMode != AnalyzeFile && gameMode != Training) {\r
3948         DisplayMoveError("Displayed position is not current");\r
3949         return FALSE;\r
3950     }\r
3951     return TRUE;\r
3952 }\r
3953 \r
3954 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
3955 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
3956 int lastLoadGameUseList = FALSE;\r
3957 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
3958 ChessMove lastLoadGameStart = (ChessMove) 0;\r
3959 \r
3960 \r
3961 void\r
3962 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
3963      int fromX, fromY, toX, toY;\r
3964      int promoChar;\r
3965 {\r
3966     ChessMove moveType;\r
3967 \r
3968     if (fromX < 0 || fromY < 0) return;\r
3969     if ((fromX == toX) && (fromY == toY)) {\r
3970         return;\r
3971     }\r
3972         \r
3973     /* Check if the user is playing in turn.  This is complicated because we\r
3974        let the user "pick up" a piece before it is his turn.  So the piece he\r
3975        tried to pick up may have been captured by the time he puts it down!\r
3976        Therefore we use the color the user is supposed to be playing in this\r
3977        test, not the color of the piece that is currently on the starting\r
3978        square---except in EditGame mode, where the user is playing both\r
3979        sides; fortunately there the capture race can't happen.  (It can\r
3980        now happen in IcsExamining mode, but that's just too bad.  The user\r
3981        will get a somewhat confusing message in that case.)\r
3982        */\r
3983 \r
3984     switch (gameMode) {\r
3985       case PlayFromGameFile:\r
3986       case AnalyzeFile:\r
3987       case TwoMachinesPlay:\r
3988       case EndOfGame:\r
3989       case IcsObserving:\r
3990       case IcsIdle:\r
3991         /* We switched into a game mode where moves are not accepted,\r
3992            perhaps while the mouse button was down. */\r
3993         return;\r
3994 \r
3995       case MachinePlaysWhite:\r
3996         /* User is moving for Black */\r
3997         if (WhiteOnMove(currentMove)) {\r
3998             DisplayMoveError("It is White's turn");\r
3999             return;\r
4000         }\r
4001         break;\r
4002 \r
4003       case MachinePlaysBlack:\r
4004         /* User is moving for White */\r
4005         if (!WhiteOnMove(currentMove)) {\r
4006             DisplayMoveError("It is Black's turn");\r
4007             return;\r
4008         }\r
4009         break;\r
4010 \r
4011       case EditGame:\r
4012       case IcsExamining:\r
4013       case BeginningOfGame:\r
4014       case AnalyzeMode:\r
4015       case Training:\r
4016         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4017             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4018             /* User is moving for Black */\r
4019             if (WhiteOnMove(currentMove)) {\r
4020                 DisplayMoveError("It is White's turn");\r
4021                 return;\r
4022             }\r
4023         } else {\r
4024             /* User is moving for White */\r
4025             if (!WhiteOnMove(currentMove)) {\r
4026                 DisplayMoveError("It is Black's turn");\r
4027                 return;\r
4028             }\r
4029         }\r
4030         break;\r
4031 \r
4032       case IcsPlayingBlack:\r
4033         /* User is moving for Black */\r
4034         if (WhiteOnMove(currentMove)) {\r
4035             if (!appData.premove) {\r
4036                 DisplayMoveError("It is White's turn");\r
4037             } else if (toX >= 0 && toY >= 0) {\r
4038                 premoveToX = toX;\r
4039                 premoveToY = toY;\r
4040                 premoveFromX = fromX;\r
4041                 premoveFromY = fromY;\r
4042                 premovePromoChar = promoChar;\r
4043                 gotPremove = 1;\r
4044                 if (appData.debugMode) \r
4045                     fprintf(debugFP, "Got premove: fromX %d,"\r
4046                             "fromY %d, toX %d, toY %d\n",\r
4047                             fromX, fromY, toX, toY);\r
4048             }\r
4049             return;\r
4050         }\r
4051         break;\r
4052 \r
4053       case IcsPlayingWhite:\r
4054         /* User is moving for White */\r
4055         if (!WhiteOnMove(currentMove)) {\r
4056             if (!appData.premove) {\r
4057                 DisplayMoveError("It is Black's turn");\r
4058             } else if (toX >= 0 && toY >= 0) {\r
4059                 premoveToX = toX;\r
4060                 premoveToY = toY;\r
4061                 premoveFromX = fromX;\r
4062                 premoveFromY = fromY;\r
4063                 premovePromoChar = promoChar;\r
4064                 gotPremove = 1;\r
4065                 if (appData.debugMode) \r
4066                     fprintf(debugFP, "Got premove: fromX %d,"\r
4067                             "fromY %d, toX %d, toY %d\n",\r
4068                             fromX, fromY, toX, toY);\r
4069             }\r
4070             return;\r
4071         }\r
4072         break;\r
4073 \r
4074       default:\r
4075         break;\r
4076 \r
4077       case EditPosition:\r
4078         if (toX == -2 || toY == -2) {\r
4079             boards[0][fromY][fromX] = EmptySquare;\r
4080             DrawPosition(FALSE, boards[currentMove]);\r
4081         } else if (toX >= 0 && toY >= 0) {\r
4082             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4083             boards[0][fromY][fromX] = EmptySquare;\r
4084             DrawPosition(FALSE, boards[currentMove]);\r
4085         }\r
4086         return;\r
4087     }\r
4088 \r
4089     if (toX < 0 || toY < 0) return;\r
4090     userOfferedDraw = FALSE;\r
4091         \r
4092     if (appData.testLegality) {\r
4093         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4094                                 EP_UNKNOWN, castlingRights[currentMove],\r
4095                                          fromY, fromX, toY, toX, promoChar);\r
4096         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4097             DisplayMoveError("Illegal move");\r
4098             return;\r
4099         }\r
4100     } else {\r
4101         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4102     }\r
4103 \r
4104     if (gameMode == Training) {\r
4105       /* compare the move played on the board to the next move in the\r
4106        * game. If they match, display the move and the opponent's response. \r
4107        * If they don't match, display an error message.\r
4108        */\r
4109       int saveAnimate;\r
4110       Board testBoard;\r
4111       CopyBoard(testBoard, boards[currentMove]);\r
4112       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4113 \r
4114       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4115         ForwardInner(currentMove+1);\r
4116 \r
4117         /* Autoplay the opponent's response.\r
4118          * if appData.animate was TRUE when Training mode was entered,\r
4119          * the response will be animated.\r
4120          */\r
4121         saveAnimate = appData.animate;\r
4122         appData.animate = animateTraining;\r
4123         ForwardInner(currentMove+1);\r
4124         appData.animate = saveAnimate;\r
4125 \r
4126         /* check for the end of the game */\r
4127         if (currentMove >= forwardMostMove) {\r
4128           gameMode = PlayFromGameFile;\r
4129           ModeHighlight();\r
4130           SetTrainingModeOff();\r
4131           DisplayInformation("End of game");\r
4132         }\r
4133       } else {\r
4134         DisplayError("Incorrect move", 0);\r
4135       }\r
4136       return;\r
4137     }\r
4138 \r
4139     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4140 }\r
4141 \r
4142 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4143 void\r
4144 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4145      ChessMove moveType;\r
4146      int fromX, fromY, toX, toY;\r
4147      /*char*/int promoChar;\r
4148 {\r
4149   /* Ok, now we know that the move is good, so we can kill\r
4150      the previous line in Analysis Mode */\r
4151   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4152     forwardMostMove = currentMove;\r
4153   }\r
4154 \r
4155   /* If we need the chess program but it's dead, restart it */\r
4156   ResurrectChessProgram();\r
4157 \r
4158   /* A user move restarts a paused game*/\r
4159   if (pausing)\r
4160     PauseEvent();\r
4161 \r
4162   thinkOutput[0] = NULLCHAR;\r
4163 \r
4164   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4165 \r
4166   if (gameMode == BeginningOfGame) {\r
4167     if (appData.noChessProgram) {\r
4168       gameMode = EditGame;\r
4169       SetGameInfo();\r
4170     } else {\r
4171       char buf[MSG_SIZ];\r
4172       gameMode = MachinePlaysBlack;\r
4173       SetGameInfo();\r
4174       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4175       DisplayTitle(buf);\r
4176       if (first.sendName) {\r
4177         sprintf(buf, "name %s\n", gameInfo.white);\r
4178         SendToProgram(buf, &first);\r
4179       }\r
4180     }\r
4181     ModeHighlight();\r
4182   }\r
4183 \r
4184   /* Relay move to ICS or chess engine */\r
4185   if (appData.icsActive) {\r
4186     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4187         gameMode == IcsExamining) {\r
4188       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4189       ics_user_moved = 1;\r
4190     }\r
4191   } else {\r
4192     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4193                            gameMode == MachinePlaysWhite ||\r
4194                            gameMode == MachinePlaysBlack)) {\r
4195       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4196     }\r
4197     SendMoveToProgram(forwardMostMove-1, &first);\r
4198     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4199       first.maybeThinking = TRUE;\r
4200     }\r
4201     if (currentMove == cmailOldMove + 1) {\r
4202       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4203     }\r
4204   }\r
4205 \r
4206   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4207 \r
4208   switch (gameMode) {\r
4209   case EditGame:\r
4210     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4211                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4212     case MT_NONE:\r
4213     case MT_CHECK:\r
4214       break;\r
4215     case MT_CHECKMATE:\r
4216       if (WhiteOnMove(currentMove)) {\r
4217         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4218       } else {\r
4219         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4220       }\r
4221       break;\r
4222     case MT_STALEMATE:\r
4223       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4224       break;\r
4225     }\r
4226     break;\r
4227     \r
4228   case MachinePlaysBlack:\r
4229   case MachinePlaysWhite:\r
4230     /* disable certain menu options while machine is thinking */\r
4231     SetMachineThinkingEnables();\r
4232     break;\r
4233 \r
4234   default:\r
4235     break;\r
4236   }\r
4237 }\r
4238 \r
4239 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4240 {\r
4241     char * hint = lastHint;\r
4242     FrontEndProgramStats stats;\r
4243 \r
4244     stats.which = cps == &first ? 0 : 1;\r
4245     stats.depth = cpstats->depth;\r
4246     stats.nodes = cpstats->nodes;\r
4247     stats.score = cpstats->score;\r
4248     stats.time = cpstats->time;\r
4249     stats.pv = cpstats->movelist;\r
4250     stats.hint = lastHint;\r
4251     stats.an_move_index = 0;\r
4252     stats.an_move_count = 0;\r
4253 \r
4254     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4255         stats.hint = cpstats->move_name;\r
4256         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4257         stats.an_move_count = cpstats->nr_moves;\r
4258     }\r
4259 \r
4260     SetProgramStats( &stats );\r
4261 }\r
4262 \r
4263 void\r
4264 HandleMachineMove(message, cps)\r
4265      char *message;\r
4266      ChessProgramState *cps;\r
4267 {\r
4268     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4269     char realname[MSG_SIZ];\r
4270     int fromX, fromY, toX, toY;\r
4271     ChessMove moveType;\r
4272     char promoChar;\r
4273     char *p;\r
4274     int machineWhite;\r
4275 \r
4276     /*\r
4277      * Kludge to ignore BEL characters\r
4278      */\r
4279     while (*message == '\007') message++;\r
4280 \r
4281     /*\r
4282      * Look for book output\r
4283      */\r
4284     if (cps == &first && bookRequested) {\r
4285         if (message[0] == '\t' || message[0] == ' ') {\r
4286             /* Part of the book output is here; append it */\r
4287             strcat(bookOutput, message);\r
4288             strcat(bookOutput, "  \n");\r
4289             return;\r
4290         } else if (bookOutput[0] != NULLCHAR) {\r
4291             /* All of book output has arrived; display it */\r
4292             char *p = bookOutput;\r
4293             while (*p != NULLCHAR) {\r
4294                 if (*p == '\t') *p = ' ';\r
4295                 p++;\r
4296             }\r
4297             DisplayInformation(bookOutput);\r
4298             bookRequested = FALSE;\r
4299             /* Fall through to parse the current output */\r
4300         }\r
4301     }\r
4302 \r
4303     /*\r
4304      * Look for machine move.\r
4305      */\r
4306     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4307         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4308     {\r
4309         /* This method is only useful on engines that support ping */\r
4310         if (cps->lastPing != cps->lastPong) {\r
4311           if (gameMode == BeginningOfGame) {\r
4312             /* Extra move from before last new; ignore */\r
4313             if (appData.debugMode) {\r
4314                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4315             }\r
4316           } else {\r
4317             if (appData.debugMode) {\r
4318                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4319                         cps->which, gameMode);\r
4320             }\r
4321 \r
4322             SendToProgram("undo\n", cps);\r
4323           }\r
4324           return;\r
4325         }\r
4326 \r
4327         switch (gameMode) {\r
4328           case BeginningOfGame:\r
4329             /* Extra move from before last reset; ignore */\r
4330             if (appData.debugMode) {\r
4331                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4332             }\r
4333             return;\r
4334 \r
4335           case EndOfGame:\r
4336           case IcsIdle:\r
4337           default:\r
4338             /* Extra move after we tried to stop.  The mode test is\r
4339                not a reliable way of detecting this problem, but it's\r
4340                the best we can do on engines that don't support ping.\r
4341             */\r
4342             if (appData.debugMode) {\r
4343                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4344                         cps->which, gameMode);\r
4345             }\r
4346             SendToProgram("undo\n", cps);\r
4347             return;\r
4348 \r
4349           case MachinePlaysWhite:\r
4350           case IcsPlayingWhite:\r
4351             machineWhite = TRUE;\r
4352             break;\r
4353 \r
4354           case MachinePlaysBlack:\r
4355           case IcsPlayingBlack:\r
4356             machineWhite = FALSE;\r
4357             break;\r
4358 \r
4359           case TwoMachinesPlay:\r
4360             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
4361             break;\r
4362         }\r
4363         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
4364             if (appData.debugMode) {\r
4365                 fprintf(debugFP,\r
4366                         "Ignoring move out of turn by %s, gameMode %d"\r
4367                         ", forwardMost %d\n",\r
4368                         cps->which, gameMode, forwardMostMove);\r
4369             }\r
4370             return;\r
4371         }\r
4372 \r
4373         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
4374                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4375             /* Machine move could not be parsed; ignore it. */\r
4376             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
4377                     machineMove, cps->which);\r
4378             DisplayError(buf1, 0);\r
4379             if (gameMode == TwoMachinesPlay) {\r
4380               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4381                        "Forfeit due to illegal move", GE_XBOARD);\r
4382             }\r
4383             return;\r
4384         }\r
4385 \r
4386         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
4387         /* So we have to redo legality test with true e.p. status here,  */\r
4388         /* to make sure an illegal e.p. capture does not slip through,   */\r
4389         /* to cause a forfeit on a justified illegal-move complaint      */\r
4390         /* of the opponent.                                              */\r
4391         if(gameMode==TwoMachinesPlay && appData.testLegality &&\r
4392            LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
4393                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
4394                              fromY, fromX, toY, toX, promoChar) == IllegalMove)\r
4395            { static char buf[MSG_SIZ];\r
4396             if (appData.debugMode) {\r
4397                 int i;\r
4398                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
4399                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
4400                 fprintf(debugFP, "castling rights\n");\r
4401             }\r
4402               sprintf(buf, "Xboard: Forfeit due to illegal move %s (%c%c%c%c)%c",\r
4403                             machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0);\r
4404               GameEnds(machineWhite ? BlackWins : WhiteWins, buf, GE_XBOARD);\r
4405            }\r
4406         hintRequested = FALSE;\r
4407         lastHint[0] = NULLCHAR;\r
4408         bookRequested = FALSE;\r
4409         /* Program may be pondering now */\r
4410         cps->maybeThinking = TRUE;\r
4411         if (cps->sendTime == 2) cps->sendTime = 1;\r
4412         if (cps->offeredDraw) cps->offeredDraw--;\r
4413 \r
4414 #if ZIPPY\r
4415         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
4416             first.initDone) {\r
4417           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4418           ics_user_moved = 1;\r
4419         }\r
4420 #endif\r
4421         /* currentMoveString is set as a side-effect of ParseOneMove */\r
4422         strcpy(machineMove, currentMoveString);\r
4423         strcat(machineMove, "\n");\r
4424         strcpy(moveList[forwardMostMove], machineMove);\r
4425 \r
4426         /* [AS] Save move info and clear stats for next move */\r
4427         pvInfoList[ forwardMostMove ].score = programStats.score;\r
4428         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
4429         pvInfoList[ forwardMostMove ].time = -1;\r
4430         ClearProgramStats();\r
4431         thinkOutput[0] = NULLCHAR;\r
4432         hiddenThinkOutputState = 0;\r
4433 \r
4434         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
4435 \r
4436         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
4437         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
4438             int count = 0;\r
4439 \r
4440             while( count < adjudicateLossPlies ) {\r
4441                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
4442 \r
4443                 if( count & 1 ) {\r
4444                     score = -score; /* Flip score for winning side */\r
4445                 }\r
4446 \r
4447                 if( score > adjudicateLossThreshold ) {\r
4448                     break;\r
4449                 }\r
4450 \r
4451                 count++;\r
4452             }\r
4453 \r
4454             if( count >= adjudicateLossPlies ) {\r
4455                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4456 \r
4457                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
4458                     "Xboard adjudication", \r
4459                     GE_XBOARD );\r
4460 \r
4461                 return;\r
4462             }\r
4463         }\r
4464 \r
4465 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
4466 \r
4467         if( gameMode == TwoMachinesPlay ) {\r
4468             int count = 0, epFile = epStatus[forwardMostMove];\r
4469 \r
4470             if(appData.testLegality) // don't wait for engine to announce game end if we can judge ourselves\r
4471             switch (MateTest(boards[forwardMostMove],\r
4472                                  PosFlags(forwardMostMove), epFile,\r
4473                                        castlingRights[forwardMostMove]) ) {\r
4474               case MT_NONE:\r
4475               case MT_CHECK:\r
4476               default:\r
4477                 break;\r
4478               case MT_STALEMATE:\r
4479                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4480                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
4481                     GE_XBOARD );\r
4482                 break;\r
4483               case MT_CHECKMATE:\r
4484                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4485                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
4486                     "Xboard adjudication: Checkmate", \r
4487                     GE_XBOARD );\r
4488                 break;\r
4489             }\r
4490 \r
4491             if( appData.testLegality )\r
4492             {   /* [HGM] Some more adjudications for obstinate engines */\r
4493                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
4494                     NrWQ=0, NrBQ=0,\r
4495                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
4496                 static int moveCount;\r
4497 \r
4498                 /* First absolutely insufficient mating material. Count what is on board. */\r
4499                 for(i=0; i<BOARD_HEIGHT; i++) for(j=0; j<BOARD_WIDTH; j++)\r
4500                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
4501                     int m=i;\r
4502 \r
4503                     switch((int) p)\r
4504                     {   /* count B,N,R and other of each side */\r
4505                         case WhiteKnight:\r
4506                              NrWN++; break;\r
4507                         case WhiteBishop:\r
4508                              NrWB++; break;\r
4509                         case BlackKnight:\r
4510                              NrWN++; break;\r
4511                         case BlackBishop:\r
4512                              NrBB++; break;\r
4513                         case WhiteRook:\r
4514                              NrWR++; break;\r
4515                         case BlackRook:\r
4516                              NrBR++; break;\r
4517                         case WhiteQueen:\r
4518                              NrWR++; break;\r
4519                         case BlackQueen:\r
4520                              NrBR++; break;\r
4521                         case EmptySquare: \r
4522                              break;\r
4523                         case BlackPawn:\r
4524                              m = 7-i;\r
4525                         case WhitePawn:\r
4526                              PawnAdvance += m; NrPawns++;\r
4527                     }\r
4528                     NrPieces += (p != EmptySquare);\r
4529                 }\r
4530 \r
4531                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )\r
4532                 {    /* KBK, KNK or KK */\r
4533 \r
4534                      /* always flag draws, for judging claims */\r
4535                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
4536 \r
4537                      if(adjudicateLossThreshold != 0) {\r
4538                          /* but only adjudicate them if adjudication enabled */\r
4539                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4540                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
4541                          return;\r
4542                      }\r
4543                 }\r
4544 \r
4545                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
4546                 if(NrPieces == 4 && \r
4547                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
4548                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
4549                    || NrWN==2 || NrBN==2     /* KNNK */\r
4550                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
4551                   ) ) {\r
4552                      if(--moveCount < 0 && adjudicateLossThreshold != 0)\r
4553                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
4554                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4555                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
4556                           return;\r
4557                      }\r
4558                 } else moveCount = 6;\r
4559 \r
4560     if (appData.debugMode) { int i;\r
4561       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
4562               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
4563               appData.drawRepeats);\r
4564       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
4565            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
4566 \r
4567     }\r
4568                 /* Check for rep-draws */\r
4569                 count = 0;\r
4570                 for(k = forwardMostMove-2;\r
4571                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
4572                         epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
4573                     k-=2)\r
4574                 {   int rights=0;\r
4575     if (appData.debugMode) {\r
4576       fprintf(debugFP, " loop\n");\r
4577     }\r
4578                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
4579     if (appData.debugMode) {\r
4580       fprintf(debugFP, "match\n");\r
4581     }\r
4582                         /* compare castling rights */\r
4583                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
4584                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
4585                                 rights++; /* King lost rights, while rook still had them */\r
4586                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
4587                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
4588                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
4589                                    rights++; /* but at least one rook lost them */\r
4590                         }\r
4591                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
4592                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
4593                                 rights++; \r
4594                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
4595                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
4596                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
4597                                    rights++;\r
4598                         }\r
4599     if (appData.debugMode) {\r
4600       for(i=0; i<nrCastlingRights; i++)\r
4601       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
4602     }\r
4603 \r
4604     if (appData.debugMode) {\r
4605       fprintf(debugFP, " %d %d\n", rights, k);\r
4606     }\r
4607                         if( rights == 0 && ++count > appData.drawRepeats-2\r
4608                             && adjudicateLossThreshold != 0) {\r
4609                              /* adjudicate after user-specified nr of repeats */\r
4610                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4611                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
4612                              return;\r
4613                         }\r
4614                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
4615                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
4616                     }\r
4617                 }\r
4618 \r
4619                 /* Now we test for 50-move draws. Determine ply count */\r
4620                 count = forwardMostMove;\r
4621                 /* look for last irreversble move */\r
4622                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
4623                     count--;\r
4624                 /* if we hit starting position, add initial plies */\r
4625                 if( count == backwardMostMove )\r
4626                     count -= initialRulePlies;\r
4627                 count = forwardMostMove - count; \r
4628                 if( count >= 100)\r
4629                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
4630                          /* this is used to judge if draw claims are legal */\r
4631                 if(adjudicateLossThreshold != 0 && count >= 2*appData.ruleMoves) {\r
4632                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4633                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
4634                          return;\r
4635                  }\r
4636             }\r
4637 \r
4638 \r
4639         }\r
4640 #endif\r
4641         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
4642             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4643 \r
4644             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
4645 \r
4646             return;\r
4647         }\r
4648 \r
4649         if (gameMode == TwoMachinesPlay) {\r
4650             if (cps->other->sendTime) {\r
4651                 SendTimeRemaining(cps->other,\r
4652                                   cps->other->twoMachinesColor[0] == 'w');\r
4653             }\r
4654             SendMoveToProgram(forwardMostMove-1, cps->other);\r
4655             if (firstMove) {\r
4656                 firstMove = FALSE;\r
4657                 if (cps->other->useColors) {\r
4658                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
4659                 }\r
4660                 SendToProgram("go\n", cps->other);\r
4661             }\r
4662             cps->other->maybeThinking = TRUE;\r
4663         }\r
4664 \r
4665         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4666         \r
4667         if (!pausing && appData.ringBellAfterMoves) {\r
4668             RingBell();\r
4669         }\r
4670 \r
4671         /* \r
4672          * Reenable menu items that were disabled while\r
4673          * machine was thinking\r
4674          */\r
4675         if (gameMode != TwoMachinesPlay)\r
4676             SetUserThinkingEnables();\r
4677 \r
4678         return;\r
4679     }\r
4680 \r
4681     /* Set special modes for chess engines.  Later something general\r
4682      *  could be added here; for now there is just one kludge feature,\r
4683      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
4684      *  when "xboard" is given as an interactive command.\r
4685      */\r
4686     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
4687         cps->useSigint = FALSE;\r
4688         cps->useSigterm = FALSE;\r
4689     }\r
4690 \r
4691     /*\r
4692      * Look for communication commands\r
4693      */\r
4694     if (!strncmp(message, "telluser ", 9)) {\r
4695         DisplayNote(message + 9);\r
4696         return;\r
4697     }\r
4698     if (!strncmp(message, "tellusererror ", 14)) {\r
4699         DisplayError(message + 14, 0);\r
4700         return;\r
4701     }\r
4702     if (!strncmp(message, "tellopponent ", 13)) {\r
4703       if (appData.icsActive) {\r
4704         if (loggedOn) {\r
4705           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
4706           SendToICS(buf1);\r
4707         }\r
4708       } else {\r
4709         DisplayNote(message + 13);\r
4710       }\r
4711       return;\r
4712     }\r
4713     if (!strncmp(message, "tellothers ", 11)) {\r
4714       if (appData.icsActive) {\r
4715         if (loggedOn) {\r
4716           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
4717           SendToICS(buf1);\r
4718         }\r
4719       }\r
4720       return;\r
4721     }\r
4722     if (!strncmp(message, "tellall ", 8)) {\r
4723       if (appData.icsActive) {\r
4724         if (loggedOn) {\r
4725           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
4726           SendToICS(buf1);\r
4727         }\r
4728       } else {\r
4729         DisplayNote(message + 8);\r
4730       }\r
4731       return;\r
4732     }\r
4733     if (strncmp(message, "warning", 7) == 0) {\r
4734         /* Undocumented feature, use tellusererror in new code */\r
4735         DisplayError(message, 0);\r
4736         return;\r
4737     }\r
4738     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
4739         strcpy(realname, cps->tidy);\r
4740         strcat(realname, " query");\r
4741         AskQuestion(realname, buf2, buf1, cps->pr);\r
4742         return;\r
4743     }\r
4744     /* Commands from the engine directly to ICS.  We don't allow these to be \r
4745      *  sent until we are logged on. Crafty kibitzes have been known to \r
4746      *  interfere with the login process.\r
4747      */\r
4748     if (loggedOn) {\r
4749         if (!strncmp(message, "tellics ", 8)) {\r
4750             SendToICS(message + 8);\r
4751             SendToICS("\n");\r
4752             return;\r
4753         }\r
4754         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
4755             SendToICS(ics_prefix);\r
4756             SendToICS(message + 15);\r
4757             SendToICS("\n");\r
4758             return;\r
4759         }\r
4760         /* The following are for backward compatibility only */\r
4761         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
4762             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
4763             SendToICS(ics_prefix);\r
4764             SendToICS(message);\r
4765             SendToICS("\n");\r
4766             return;\r
4767         }\r
4768     }\r
4769     if (strncmp(message, "feature ", 8) == 0) {\r
4770       ParseFeatures(message+8, cps);\r
4771     }\r
4772     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
4773       return;\r
4774     }\r
4775     /*\r
4776      * If the move is illegal, cancel it and redraw the board.\r
4777      * Also deal with other error cases.  Matching is rather loose\r
4778      * here to accommodate engines written before the spec.\r
4779      */\r
4780     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
4781         strncmp(message, "Error", 5) == 0) {\r
4782         if (StrStr(message, "name") || \r
4783             StrStr(message, "rating") || StrStr(message, "?") ||\r
4784             StrStr(message, "result") || StrStr(message, "board") ||\r
4785             StrStr(message, "bk") || StrStr(message, "computer") ||\r
4786             StrStr(message, "variant") || StrStr(message, "hint") ||\r
4787             StrStr(message, "random") || StrStr(message, "depth") ||\r
4788             StrStr(message, "accepted")) {\r
4789             return;\r
4790         }\r
4791         if (StrStr(message, "protover")) {\r
4792           /* Program is responding to input, so it's apparently done\r
4793              initializing, and this error message indicates it is\r
4794              protocol version 1.  So we don't need to wait any longer\r
4795              for it to initialize and send feature commands. */\r
4796           FeatureDone(cps, 1);\r
4797           cps->protocolVersion = 1;\r
4798           return;\r
4799         }\r
4800         cps->maybeThinking = FALSE;\r
4801 \r
4802         if (StrStr(message, "draw")) {\r
4803             /* Program doesn't have "draw" command */\r
4804             cps->sendDrawOffers = 0;\r
4805             return;\r
4806         }\r
4807         if (cps->sendTime != 1 &&\r
4808             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
4809           /* Program apparently doesn't have "time" or "otim" command */\r
4810           cps->sendTime = 0;\r
4811           return;\r
4812         }\r
4813         if (StrStr(message, "analyze")) {\r
4814             cps->analysisSupport = FALSE;\r
4815             cps->analyzing = FALSE;\r
4816             Reset(FALSE, TRUE);\r
4817             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
4818             DisplayError(buf2, 0);\r
4819             return;\r
4820         }\r
4821         if (StrStr(message, "(no matching move)st")) {\r
4822           /* Special kludge for GNU Chess 4 only */\r
4823           cps->stKludge = TRUE;\r
4824           SendTimeControl(cps, movesPerSession, timeControl,\r
4825                           timeIncrement, appData.searchDepth,\r
4826                           searchTime);\r
4827           return;\r
4828         }\r
4829         if (StrStr(message, "(no matching move)sd")) {\r
4830           /* Special kludge for GNU Chess 4 only */\r
4831           cps->sdKludge = TRUE;\r
4832           SendTimeControl(cps, movesPerSession, timeControl,\r
4833                           timeIncrement, appData.searchDepth,\r
4834                           searchTime);\r
4835           return;\r
4836         }\r
4837         if (!StrStr(message, "llegal")) {\r
4838             return;\r
4839         }\r
4840         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
4841             gameMode == IcsIdle) return;\r
4842         if (forwardMostMove <= backwardMostMove) return;\r
4843 #if 0\r
4844         /* Following removed: it caused a bug where a real illegal move\r
4845            message in analyze mored would be ignored. */\r
4846         if (cps == &first && programStats.ok_to_send == 0) {\r
4847             /* Bogus message from Crafty responding to "."  This filtering\r
4848                can miss some of the bad messages, but fortunately the bug \r
4849                is fixed in current Crafty versions, so it doesn't matter. */\r
4850             return;\r
4851         }\r
4852 #endif\r
4853         if (pausing) PauseEvent();\r
4854         if (gameMode == PlayFromGameFile) {\r
4855             /* Stop reading this game file */\r
4856             gameMode = EditGame;\r
4857             ModeHighlight();\r
4858         }\r
4859         currentMove = --forwardMostMove;\r
4860         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
4861         SwitchClocks();\r
4862         DisplayBothClocks();\r
4863         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
4864                 parseList[currentMove], cps->which);\r
4865         DisplayMoveError(buf1);\r
4866         DrawPosition(FALSE, boards[currentMove]);\r
4867 \r
4868         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
4869         /* only passes fully legal moves                            */\r
4870         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
4871             static char buf[MSG_SIZ];\r
4872             sprintf(buf, "False illegal-move claim on %s (%c%c%c%c)%c",\r
4873                     machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0);\r
4874             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
4875                                 buf, GE_XBOARD );\r
4876         }\r
4877         return;\r
4878     }\r
4879     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
4880         /* Program has a broken "time" command that\r
4881            outputs a string not ending in newline.\r
4882            Don't use it. */\r
4883         cps->sendTime = 0;\r
4884     }\r
4885     \r
4886     /*\r
4887      * If chess program startup fails, exit with an error message.\r
4888      * Attempts to recover here are futile.\r
4889      */\r
4890     if ((StrStr(message, "unknown host") != NULL)\r
4891         || (StrStr(message, "No remote directory") != NULL)\r
4892         || (StrStr(message, "not found") != NULL)\r
4893         || (StrStr(message, "No such file") != NULL)\r
4894         || (StrStr(message, "can't alloc") != NULL)\r
4895         || (StrStr(message, "Permission denied") != NULL)) {\r
4896 \r
4897         cps->maybeThinking = FALSE;\r
4898         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
4899                 cps->which, cps->program, cps->host, message);\r
4900         RemoveInputSource(cps->isr);\r
4901         DisplayFatalError(buf1, 0, 1);\r
4902         return;\r
4903     }\r
4904     \r
4905     /* \r
4906      * Look for hint output\r
4907      */\r
4908     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
4909         if (cps == &first && hintRequested) {\r
4910             hintRequested = FALSE;\r
4911             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
4912                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4913                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
4914                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
4915                                     fromY, fromX, toY, toX, promoChar, buf1);\r
4916                 sprintf(buf2, "Hint: %s", buf1);\r
4917                 DisplayInformation(buf2);\r
4918             } else {\r
4919                 /* Hint move could not be parsed!? */\r
4920                 sprintf(buf2,\r
4921                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
4922                         buf1, cps->which);\r
4923                 DisplayError(buf2, 0);\r
4924             }\r
4925         } else {\r
4926             strcpy(lastHint, buf1);\r
4927         }\r
4928         return;\r
4929     }\r
4930 \r
4931     /*\r
4932      * Ignore other messages if game is not in progress\r
4933      */\r
4934     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
4935         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
4936 \r
4937     /*\r
4938      * look for win, lose, draw, or draw offer\r
4939      */\r
4940     if (strncmp(message, "1-0", 3) == 0) {\r
4941         char *p, *q, *r = "";\r
4942         p = strchr(message, '{');\r
4943         if (p) {\r
4944             q = strchr(p, '}');\r
4945             if (q) {\r
4946                 *q = NULLCHAR;\r
4947                 r = p + 1;\r
4948             }\r
4949         }\r
4950         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
4951         return;\r
4952     } else if (strncmp(message, "0-1", 3) == 0) {\r
4953         char *p, *q, *r = "";\r
4954         p = strchr(message, '{');\r
4955         if (p) {\r
4956             q = strchr(p, '}');\r
4957             if (q) {\r
4958                 *q = NULLCHAR;\r
4959                 r = p + 1;\r
4960             }\r
4961         }\r
4962         /* Kludge for Arasan 4.1 bug */\r
4963         if (strcmp(r, "Black resigns") == 0) {\r
4964             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
4965             return;\r
4966         }\r
4967         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
4968         return;\r
4969     } else if (strncmp(message, "1/2", 3) == 0) {\r
4970         char *p, *q, *r = "";\r
4971         p = strchr(message, '{');\r
4972         if (p) {\r
4973             q = strchr(p, '}');\r
4974             if (q) {\r
4975                 *q = NULLCHAR;\r
4976                 r = p + 1;\r
4977             }\r
4978         }\r
4979             \r
4980         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
4981         return;\r
4982 \r
4983     } else if (strncmp(message, "White resign", 12) == 0) {\r
4984         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
4985         return;\r
4986     } else if (strncmp(message, "Black resign", 12) == 0) {\r
4987         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
4988         return;\r
4989     } else if (strncmp(message, "White", 5) == 0 &&\r
4990                message[5] != '(' &&\r
4991                StrStr(message, "Black") == NULL) {\r
4992         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
4993         return;\r
4994     } else if (strncmp(message, "Black", 5) == 0 &&\r
4995                message[5] != '(') {\r
4996         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
4997         return;\r
4998     } else if (strcmp(message, "resign") == 0 ||\r
4999                strcmp(message, "computer resigns") == 0) {\r
5000         switch (gameMode) {\r
5001           case MachinePlaysBlack:\r
5002           case IcsPlayingBlack:\r
5003             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5004             break;\r
5005           case MachinePlaysWhite:\r
5006           case IcsPlayingWhite:\r
5007             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5008             break;\r
5009           case TwoMachinesPlay:\r
5010             if (cps->twoMachinesColor[0] == 'w')\r
5011               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5012             else\r
5013               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5014             break;\r
5015           default:\r
5016             /* can't happen */\r
5017             break;\r
5018         }\r
5019         return;\r
5020     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5021         switch (gameMode) {\r
5022           case MachinePlaysBlack:\r
5023           case IcsPlayingBlack:\r
5024             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5025             break;\r
5026           case MachinePlaysWhite:\r
5027           case IcsPlayingWhite:\r
5028             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5029             break;\r
5030           case TwoMachinesPlay:\r
5031             if (cps->twoMachinesColor[0] == 'w')\r
5032               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5033             else\r
5034               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5035             break;\r
5036           default:\r
5037             /* can't happen */\r
5038             break;\r
5039         }\r
5040         return;\r
5041     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5042         switch (gameMode) {\r
5043           case MachinePlaysBlack:\r
5044           case IcsPlayingBlack:\r
5045             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5046             break;\r
5047           case MachinePlaysWhite:\r
5048           case IcsPlayingWhite:\r
5049             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5050             break;\r
5051           case TwoMachinesPlay:\r
5052             if (cps->twoMachinesColor[0] == 'w')\r
5053               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5054             else\r
5055               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5056             break;\r
5057           default:\r
5058             /* can't happen */\r
5059             break;\r
5060         }\r
5061         return;\r
5062     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5063         if (WhiteOnMove(forwardMostMove)) {\r
5064             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5065         } else {\r
5066             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5067         }\r
5068         return;\r
5069     } else if (strstr(message, "Draw") != NULL ||\r
5070                strstr(message, "game is a draw") != NULL) {\r
5071         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5072         return;\r
5073     } else if (strstr(message, "offer") != NULL &&\r
5074                strstr(message, "draw") != NULL) {\r
5075 #if ZIPPY\r
5076         if (appData.zippyPlay && first.initDone) {\r
5077             /* Relay offer to ICS */\r
5078             SendToICS(ics_prefix);\r
5079             SendToICS("draw\n");\r
5080         }\r
5081 #endif\r
5082         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5083         if (gameMode == TwoMachinesPlay) {\r
5084             if (cps->other->offeredDraw) {\r
5085                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5086             } else {\r
5087                 if (cps->other->sendDrawOffers) {\r
5088                     SendToProgram("draw\n", cps->other);\r
5089                 }\r
5090             }\r
5091         } else if (gameMode == MachinePlaysWhite ||\r
5092                    gameMode == MachinePlaysBlack) {\r
5093           if (userOfferedDraw) {\r
5094             DisplayInformation("Machine accepts your draw offer");\r
5095             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5096           } else {\r
5097             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5098           }\r
5099         }\r
5100     }\r
5101 \r
5102     \r
5103     /*\r
5104      * Look for thinking output\r
5105      */\r
5106     if ( appData.showThinking) {\r
5107         int plylev, mvleft, mvtot, curscore, time;\r
5108         char mvname[MOVE_LEN];\r
5109         unsigned long nodes;\r
5110         char plyext;\r
5111         int ignore = FALSE;\r
5112         int prefixHint = FALSE;\r
5113         mvname[0] = NULLCHAR;\r
5114 \r
5115         switch (gameMode) {\r
5116           case MachinePlaysBlack:\r
5117           case IcsPlayingBlack:\r
5118             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5119             break;\r
5120           case MachinePlaysWhite:\r
5121           case IcsPlayingWhite:\r
5122             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5123             break;\r
5124           case AnalyzeMode:\r
5125           case AnalyzeFile:\r
5126             break;\r
5127           case TwoMachinesPlay:\r
5128             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5129                 ignore = TRUE;\r
5130             }\r
5131             break;\r
5132           default:\r
5133             ignore = TRUE;\r
5134             break;\r
5135         }\r
5136 \r
5137         if (!ignore) {\r
5138             buf1[0] = NULLCHAR;\r
5139             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5140                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5141 \r
5142                 if (plyext != ' ' && plyext != '\t') {\r
5143                     time *= 100;\r
5144                 }\r
5145 \r
5146                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5147                 if( cps->scoreIsAbsolute && \r
5148                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5149                 {\r
5150                     curscore = -curscore;\r
5151                 }\r
5152 \r
5153 \r
5154                 programStats.depth = plylev;\r
5155                 programStats.nodes = nodes;\r
5156                 programStats.time = time;\r
5157                 programStats.score = curscore;\r
5158                 programStats.got_only_move = 0;\r
5159 \r
5160                 /* Buffer overflow protection */\r
5161                 if (buf1[0] != NULLCHAR) {\r
5162                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5163                         && appData.debugMode) {\r
5164                         fprintf(debugFP,\r
5165                                 "PV is too long; using the first %d bytes.\n",\r
5166                                 sizeof(programStats.movelist) - 1);\r
5167                     }\r
5168 \r
5169                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5170                 } else {\r
5171                     sprintf(programStats.movelist, " no PV\n");\r
5172                 }\r
5173 \r
5174                 if (programStats.seen_stat) {\r
5175                     programStats.ok_to_send = 1;\r
5176                 }\r
5177 \r
5178                 if (strchr(programStats.movelist, '(') != NULL) {\r
5179                     programStats.line_is_book = 1;\r
5180                     programStats.nr_moves = 0;\r
5181                     programStats.moves_left = 0;\r
5182                 } else {\r
5183                     programStats.line_is_book = 0;\r
5184                 }\r
5185 \r
5186                 SendProgramStatsToFrontend( cps, &programStats );\r
5187 \r
5188                 /* \r
5189                     [AS] Protect the thinkOutput buffer from overflow... this\r
5190                     is only useful if buf1 hasn't overflowed first!\r
5191                 */\r
5192                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5193                         plylev, \r
5194                         (gameMode == TwoMachinesPlay ?\r
5195                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5196                         ((double) curscore) / 100.0,\r
5197                         prefixHint ? lastHint : "",\r
5198                         prefixHint ? " " : "" );\r
5199 \r
5200                 if( buf1[0] != NULLCHAR ) {\r
5201                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5202 \r
5203                     if( strlen(buf1) > max_len ) {\r
5204                         if( appData.debugMode) {\r
5205                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5206                         }\r
5207                         buf1[max_len+1] = '\0';\r
5208                     }\r
5209 \r
5210                     strcat( thinkOutput, buf1 );\r
5211                 }\r
5212 \r
5213                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5214                     DisplayMove(currentMove - 1);\r
5215                     DisplayAnalysis();\r
5216                 }\r
5217                 return;\r
5218 \r
5219             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
5220                 /* crafty (9.25+) says "(only move) <move>"\r
5221                  * if there is only 1 legal move\r
5222                  */\r
5223                 sscanf(p, "(only move) %s", buf1);\r
5224                 sprintf(thinkOutput, "%s (only move)", buf1);\r
5225                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
5226                 programStats.depth = 1;\r
5227                 programStats.nr_moves = 1;\r
5228                 programStats.moves_left = 1;\r
5229                 programStats.nodes = 1;\r
5230                 programStats.time = 1;\r
5231                 programStats.got_only_move = 1;\r
5232 \r
5233                 /* Not really, but we also use this member to\r
5234                    mean "line isn't going to change" (Crafty\r
5235                    isn't searching, so stats won't change) */\r
5236                 programStats.line_is_book = 1;\r
5237 \r
5238                 SendProgramStatsToFrontend( cps, &programStats );\r
5239                 \r
5240                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5241                     DisplayMove(currentMove - 1);\r
5242                     DisplayAnalysis();\r
5243                 }\r
5244                 return;\r
5245             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
5246                               &time, &nodes, &plylev, &mvleft,\r
5247                               &mvtot, mvname) >= 5) {\r
5248                 /* The stat01: line is from Crafty (9.29+) in response\r
5249                    to the "." command */\r
5250                 programStats.seen_stat = 1;\r
5251                 cps->maybeThinking = TRUE;\r
5252 \r
5253                 if (programStats.got_only_move || !appData.periodicUpdates)\r
5254                   return;\r
5255 \r
5256                 programStats.depth = plylev;\r
5257                 programStats.time = time;\r
5258                 programStats.nodes = nodes;\r
5259                 programStats.moves_left = mvleft;\r
5260                 programStats.nr_moves = mvtot;\r
5261                 strcpy(programStats.move_name, mvname);\r
5262                 programStats.ok_to_send = 1;\r
5263                 programStats.movelist[0] = '\0';\r
5264 \r
5265                 SendProgramStatsToFrontend( cps, &programStats );\r
5266 \r
5267                 DisplayAnalysis();\r
5268                 return;\r
5269 \r
5270             } else if (strncmp(message,"++",2) == 0) {\r
5271                 /* Crafty 9.29+ outputs this */\r
5272                 programStats.got_fail = 2;\r
5273                 return;\r
5274 \r
5275             } else if (strncmp(message,"--",2) == 0) {\r
5276                 /* Crafty 9.29+ outputs this */\r
5277                 programStats.got_fail = 1;\r
5278                 return;\r
5279 \r
5280             } else if (thinkOutput[0] != NULLCHAR &&\r
5281                        strncmp(message, "    ", 4) == 0) {\r
5282                 unsigned message_len;\r
5283 \r
5284                 p = message;\r
5285                 while (*p && *p == ' ') p++;\r
5286 \r
5287                 message_len = strlen( p );\r
5288 \r
5289                 /* [AS] Avoid buffer overflow */\r
5290                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
5291                     strcat(thinkOutput, " ");\r
5292                     strcat(thinkOutput, p);\r
5293                 }\r
5294 \r
5295                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
5296                     strcat(programStats.movelist, " ");\r
5297                     strcat(programStats.movelist, p);\r
5298                 }\r
5299 \r
5300                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5301                     DisplayMove(currentMove - 1);\r
5302                     DisplayAnalysis();\r
5303                 }\r
5304                 return;\r
5305             }\r
5306         }\r
5307         else {\r
5308             buf1[0] = NULLCHAR;\r
5309 \r
5310             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5311                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
5312             {\r
5313                 ChessProgramStats cpstats;\r
5314 \r
5315                 if (plyext != ' ' && plyext != '\t') {\r
5316                     time *= 100;\r
5317                 }\r
5318 \r
5319                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5320                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
5321                     curscore = -curscore;\r
5322                 }\r
5323 \r
5324                 cpstats.depth = plylev;\r
5325                 cpstats.nodes = nodes;\r
5326                 cpstats.time = time;\r
5327                 cpstats.score = curscore;\r
5328                 cpstats.got_only_move = 0;\r
5329                 cpstats.movelist[0] = '\0';\r
5330 \r
5331                 if (buf1[0] != NULLCHAR) {\r
5332                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
5333                 }\r
5334 \r
5335                 cpstats.ok_to_send = 0;\r
5336                 cpstats.line_is_book = 0;\r
5337                 cpstats.nr_moves = 0;\r
5338                 cpstats.moves_left = 0;\r
5339 \r
5340                 SendProgramStatsToFrontend( cps, &cpstats );\r
5341             }\r
5342         }\r
5343     }\r
5344 }\r
5345 \r
5346 \r
5347 /* Parse a game score from the character string "game", and\r
5348    record it as the history of the current game.  The game\r
5349    score is NOT assumed to start from the standard position. \r
5350    The display is not updated in any way.\r
5351    */\r
5352 void\r
5353 ParseGameHistory(game)\r
5354      char *game;\r
5355 {\r
5356     ChessMove moveType;\r
5357     int fromX, fromY, toX, toY, boardIndex;\r
5358     char promoChar;\r
5359     char *p, *q;\r
5360     char buf[MSG_SIZ];\r
5361 \r
5362     if (appData.debugMode)\r
5363       fprintf(debugFP, "Parsing game history: %s\n", game);\r
5364 \r
5365     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
5366     gameInfo.site = StrSave(appData.icsHost);\r
5367     gameInfo.date = PGNDate();\r
5368     gameInfo.round = StrSave("-");\r
5369 \r
5370     /* Parse out names of players */\r
5371     while (*game == ' ') game++;\r
5372     p = buf;\r
5373     while (*game != ' ') *p++ = *game++;\r
5374     *p = NULLCHAR;\r
5375     gameInfo.white = StrSave(buf);\r
5376     while (*game == ' ') game++;\r
5377     p = buf;\r
5378     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
5379     *p = NULLCHAR;\r
5380     gameInfo.black = StrSave(buf);\r
5381 \r
5382     /* Parse moves */\r
5383     boardIndex = blackPlaysFirst ? 1 : 0;\r
5384     yynewstr(game);\r
5385     for (;;) {\r
5386         yyboardindex = boardIndex;\r
5387         moveType = (ChessMove) yylex();\r
5388         switch (moveType) {\r
5389 #ifdef FAIRY\r
5390           case WhitePromotionChancellor:\r
5391           case BlackPromotionChancellor:\r
5392           case WhitePromotionArchbishop:\r
5393           case BlackPromotionArchbishop:\r
5394 #endif\r
5395           case WhitePromotionQueen:\r
5396           case BlackPromotionQueen:\r
5397           case WhitePromotionRook:\r
5398           case BlackPromotionRook:\r
5399           case WhitePromotionBishop:\r
5400           case BlackPromotionBishop:\r
5401           case WhitePromotionKnight:\r
5402           case BlackPromotionKnight:\r
5403           case WhitePromotionKing:\r
5404           case BlackPromotionKing:\r
5405           case NormalMove:\r
5406           case WhiteCapturesEnPassant:\r
5407           case BlackCapturesEnPassant:\r
5408           case WhiteKingSideCastle:\r
5409           case WhiteQueenSideCastle:\r
5410           case BlackKingSideCastle:\r
5411           case BlackQueenSideCastle:\r
5412           case WhiteKingSideCastleWild:\r
5413           case WhiteQueenSideCastleWild:\r
5414           case BlackKingSideCastleWild:\r
5415           case BlackQueenSideCastleWild:\r
5416           /* PUSH Fabien */\r
5417           case WhiteHSideCastleFR:\r
5418           case WhiteASideCastleFR:\r
5419           case BlackHSideCastleFR:\r
5420           case BlackASideCastleFR:\r
5421           /* POP Fabien */\r
5422           case IllegalMove:             /* maybe suicide chess, etc. */\r
5423             fromX = currentMoveString[0] - 'a';\r
5424             fromY = currentMoveString[1] - ONE;\r
5425             toX = currentMoveString[2] - 'a';\r
5426             toY = currentMoveString[3] - ONE;\r
5427             promoChar = currentMoveString[4];\r
5428             break;\r
5429           case WhiteDrop:\r
5430           case BlackDrop:\r
5431             fromX = moveType == WhiteDrop ?\r
5432               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
5433             (int) CharToPiece(ToLower(currentMoveString[0]));\r
5434             fromY = DROP_RANK;\r
5435             toX = currentMoveString[2] - 'a';\r
5436             toY = currentMoveString[3] - ONE;\r
5437             promoChar = NULLCHAR;\r
5438             break;\r
5439           case AmbiguousMove:\r
5440             /* bug? */\r
5441             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
5442             DisplayError(buf, 0);\r
5443             return;\r
5444           case ImpossibleMove:\r
5445             /* bug? */\r
5446             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
5447             DisplayError(buf, 0);\r
5448             return;\r
5449           case (ChessMove) 0:   /* end of file */\r
5450             if (boardIndex < backwardMostMove) {\r
5451                 /* Oops, gap.  How did that happen? */\r
5452                 DisplayError("Gap in move list", 0);\r
5453                 return;\r
5454             }\r
5455             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
5456             if (boardIndex > forwardMostMove) {\r
5457                 forwardMostMove = boardIndex;\r
5458             }\r
5459             return;\r
5460           case ElapsedTime:\r
5461             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
5462                 strcat(parseList[boardIndex-1], " ");\r
5463                 strcat(parseList[boardIndex-1], yy_text);\r
5464             }\r
5465             continue;\r
5466           case Comment:\r
5467           case PGNTag:\r
5468           case NAG:\r
5469           default:\r
5470             /* ignore */\r
5471             continue;\r
5472           case WhiteWins:\r
5473           case BlackWins:\r
5474           case GameIsDrawn:\r
5475           case GameUnfinished:\r
5476             if (gameMode == IcsExamining) {\r
5477                 if (boardIndex < backwardMostMove) {\r
5478                     /* Oops, gap.  How did that happen? */\r
5479                     return;\r
5480                 }\r
5481                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5482                 return;\r
5483             }\r
5484             gameInfo.result = moveType;\r
5485             p = strchr(yy_text, '{');\r
5486             if (p == NULL) p = strchr(yy_text, '(');\r
5487             if (p == NULL) {\r
5488                 p = yy_text;\r
5489                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
5490             } else {\r
5491                 q = strchr(p, *p == '{' ? '}' : ')');\r
5492                 if (q != NULL) *q = NULLCHAR;\r
5493                 p++;\r
5494             }\r
5495             gameInfo.resultDetails = StrSave(p);\r
5496             continue;\r
5497         }\r
5498         if (boardIndex >= forwardMostMove &&\r
5499             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
5500             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5501             return;\r
5502         }\r
5503         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
5504                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
5505                                  parseList[boardIndex]);\r
5506         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
5507         /* currentMoveString is set as a side-effect of yylex */\r
5508         strcpy(moveList[boardIndex], currentMoveString);\r
5509         strcat(moveList[boardIndex], "\n");\r
5510         boardIndex++;\r
5511         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
5512         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
5513                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
5514           case MT_NONE:\r
5515           case MT_STALEMATE:\r
5516           default:\r
5517             break;\r
5518           case MT_CHECK:\r
5519             strcat(parseList[boardIndex - 1], "+");\r
5520             break;\r
5521           case MT_CHECKMATE:\r
5522             strcat(parseList[boardIndex - 1], "#");\r
5523             break;\r
5524         }\r
5525     }\r
5526 }\r
5527 \r
5528 \r
5529 /* Apply a move to the given board  */\r
5530 void\r
5531 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
5532      int fromX, fromY, toX, toY;\r
5533      int promoChar;\r
5534      Board board;\r
5535 {\r
5536     /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
5537     if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
5538        && promoChar != 0) promoChar = 'f';\r
5539          \r
5540     ChessSquare captured = board[toY][toX];\r
5541     if (fromY == DROP_RANK) {\r
5542         /* must be first */\r
5543         board[toY][toX] = (ChessSquare) fromX;\r
5544     } else if (fromX == toX && fromY == toY) {\r
5545         return;\r
5546     }\r
5547 \r
5548     /* Code added by Tord: */\r
5549     /* FRC castling assumed when king captures friendly rook. */\r
5550     else if (board[fromY][fromX] == WhiteKing &&\r
5551              board[toY][toX] == WhiteRook) {\r
5552       board[fromY][fromX] = EmptySquare;\r
5553       board[toY][toX] = EmptySquare;\r
5554       if(toX > fromX) {\r
5555         board[0][BOARD_WIDTH-2] = WhiteKing; board[0][BOARD_WIDTH-3] = WhiteRook;\r
5556       } else {\r
5557         board[0][2] = WhiteKing; board[0][3] = WhiteRook;\r
5558       }\r
5559     } else if (board[fromY][fromX] == BlackKing &&\r
5560                board[toY][toX] == BlackRook) {\r
5561       board[fromY][fromX] = EmptySquare;\r
5562       board[toY][toX] = EmptySquare;\r
5563       if(toX > fromX) {\r
5564         board[BOARD_HEIGHT-1][BOARD_WIDTH-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_WIDTH-3] = BlackRook;\r
5565       } else {\r
5566         board[BOARD_HEIGHT-1][2] = BlackKing; board[BOARD_HEIGHT-1][3] = BlackRook;\r
5567       }\r
5568     /* End of code added by Tord */\r
5569 \r
5570     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5571         && board[fromY][fromX] == WhiteKing\r
5572         && toY == fromY && toX > fromX+1) {\r
5573         board[fromY][fromX] = EmptySquare;\r
5574         board[toY][toX] = WhiteKing;\r
5575         board[fromY][BOARD_WIDTH-1] = EmptySquare;\r
5576         board[toY][toX-1] = WhiteRook;\r
5577     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5578                && board[fromY][fromX] == WhiteKing\r
5579                && toY == fromY && toX < fromX-1) {\r
5580         board[fromY][fromX] = EmptySquare;\r
5581         board[toY][toX] = WhiteKing;\r
5582         board[fromY][0] = EmptySquare;\r
5583         board[toY][toX+1] = WhiteRook;\r
5584     } else if (fromY == 0 && fromX == 3\r
5585                && board[fromY][fromX] == WhiteKing\r
5586                && toY == 0 && toX == 5) {\r
5587         board[fromY][fromX] = EmptySquare;\r
5588         board[toY][toX] = WhiteKing;\r
5589         board[fromY][7] = EmptySquare;\r
5590         board[toY][4] = WhiteRook;\r
5591     } else if (fromY == 0 && fromX == 3\r
5592                && board[fromY][fromX] == WhiteKing\r
5593                && toY == 0 && toX == 1) {\r
5594         board[fromY][fromX] = EmptySquare;\r
5595         board[toY][toX] = WhiteKing;\r
5596         board[fromY][0] = EmptySquare;\r
5597         board[toY][2] = WhiteRook;\r
5598     } else if (board[fromY][fromX] == WhitePawn\r
5599                && toY == BOARD_HEIGHT-1\r
5600 #ifdef FAIRY\r
5601                && gameInfo.variant != VariantXiangqi\r
5602 #endif\r
5603                ) {\r
5604         /* white pawn promotion */\r
5605         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
5606         if (board[toY][toX] == EmptySquare) {\r
5607             board[toY][toX] = WhiteQueen;\r
5608         }\r
5609         board[fromY][fromX] = EmptySquare;\r
5610     } else if ((fromY == BOARD_HEIGHT-4)\r
5611                && (toX != fromX)\r
5612                && (board[fromY][fromX] == WhitePawn)\r
5613                && (board[toY][toX] == EmptySquare)) {\r
5614         board[fromY][fromX] = EmptySquare;\r
5615         board[toY][toX] = WhitePawn;\r
5616         captured = board[toY - 1][toX];\r
5617         board[toY - 1][toX] = EmptySquare;\r
5618     } else if (initialPosition[fromY][fromX] == BlackKing\r
5619                && board[fromY][fromX] == BlackKing\r
5620                && toY == fromY && toX > fromX+1) {\r
5621         board[fromY][fromX] = EmptySquare;\r
5622         board[toY][toX] = BlackKing;\r
5623         board[fromY][BOARD_WIDTH-1] = EmptySquare;\r
5624         board[toY][toX-1] = BlackRook;\r
5625     } else if (initialPosition[fromY][fromX] == BlackKing\r
5626                && board[fromY][fromX] == BlackKing\r
5627                && toY == fromY && toX < fromX-1) {\r
5628         board[fromY][fromX] = EmptySquare;\r
5629         board[toY][toX] = BlackKing;\r
5630         board[fromY][0] = EmptySquare;\r
5631         board[toY][toX+1] = BlackRook;\r
5632     } else if (fromY == 7 && fromX == 3\r
5633                && board[fromY][fromX] == BlackKing\r
5634                && toY == 7 && toX == 5) {\r
5635         board[fromY][fromX] = EmptySquare;\r
5636         board[toY][toX] = BlackKing;\r
5637         board[fromY][7] = EmptySquare;\r
5638         board[toY][4] = BlackRook;\r
5639     } else if (fromY == 7 && fromX == 3\r
5640                && board[fromY][fromX] == BlackKing\r
5641                && toY == 7 && toX == 1) {\r
5642         board[fromY][fromX] = EmptySquare;\r
5643         board[toY][toX] = BlackKing;\r
5644         board[fromY][0] = EmptySquare;\r
5645         board[toY][2] = BlackRook;\r
5646     } else if (board[fromY][fromX] == BlackPawn\r
5647                && toY == 0\r
5648 #ifdef FAIRY\r
5649                && gameInfo.variant != VariantXiangqi\r
5650 #endif\r
5651                ) {\r
5652         /* black pawn promotion */\r
5653         board[0][toX] = CharToPiece(ToLower(promoChar));\r
5654         if (board[0][toX] == EmptySquare) {\r
5655             board[0][toX] = BlackQueen;\r
5656         }\r
5657         board[fromY][fromX] = EmptySquare;\r
5658     } else if ((fromY == 3)\r
5659                && (toX != fromX)\r
5660                && (board[fromY][fromX] == BlackPawn)\r
5661                && (board[toY][toX] == EmptySquare)) {\r
5662         board[fromY][fromX] = EmptySquare;\r
5663         board[toY][toX] = BlackPawn;\r
5664         captured = board[toY + 1][toX];\r
5665         board[toY + 1][toX] = EmptySquare;\r
5666     } else {\r
5667         board[toY][toX] = board[fromY][fromX];\r
5668         board[fromY][fromX] = EmptySquare;\r
5669     }\r
5670     if (gameInfo.variant == VariantCrazyhouse) {\r
5671 #if 0\r
5672       /* !!A lot more code needs to be written to support holdings */\r
5673       if (fromY == DROP_RANK) {\r
5674         /* Delete from holdings */\r
5675         if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;\r
5676       }\r
5677       if (captured != EmptySquare) {\r
5678         /* Add to holdings */\r
5679         if (captured < BlackPawn) {\r
5680           holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;\r
5681         } else {\r
5682           holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;\r
5683         }\r
5684       }\r
5685 #endif\r
5686     } else if (gameInfo.variant == VariantAtomic) {\r
5687       if (captured != EmptySquare) {\r
5688         int y, x;\r
5689         for (y = toY-1; y <= toY+1; y++) {\r
5690           for (x = toX-1; x <= toX+1; x++) {\r
5691             if (y >= 0 && y < BOARD_WIDTH && x >= 0 && x < BOARD_WIDTH &&\r
5692                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
5693               board[y][x] = EmptySquare;\r
5694             }\r
5695           }\r
5696         }\r
5697         board[toY][toX] = EmptySquare;\r
5698       }\r
5699     }\r
5700 }\r
5701 \r
5702 /* Updates forwardMostMove */\r
5703 void\r
5704 MakeMove(fromX, fromY, toX, toY, promoChar)\r
5705      int fromX, fromY, toX, toY;\r
5706      int promoChar;\r
5707 {\r
5708     forwardMostMove++;\r
5709 \r
5710     if (forwardMostMove >= MAX_MOVES) {\r
5711       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
5712                         0, 1);\r
5713       return;\r
5714     }\r
5715     SwitchClocks();\r
5716     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
5717     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
5718     if (commentList[forwardMostMove] != NULL) {\r
5719         free(commentList[forwardMostMove]);\r
5720         commentList[forwardMostMove] = NULL;\r
5721     }\r
5722     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
5723     /* [HGM] compute & store e.p. status and castling rights for new position */\r
5724     { int i, j;\r
5725 \r
5726       epStatus[forwardMostMove] = EP_NONE;\r
5727 \r
5728       if( boards[forwardMostMove][toY][toX] != EmptySquare ) \r
5729            epStatus[forwardMostMove] = EP_CAPTURE;  \r
5730 \r
5731       if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {\r
5732            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
5733            if( toY-fromY==2 &&\r
5734                (toX>1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||\r
5735                 toX<BOARD_WIDTH-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )\r
5736               epStatus[forwardMostMove] = toX;\r
5737       } else \r
5738       if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {\r
5739            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
5740            if( toY-fromY== -2 &&\r
5741                (toX>1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||\r
5742                 toX<BOARD_WIDTH-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )\r
5743               epStatus[forwardMostMove] = toX;\r
5744        }\r
5745 \r
5746        for(i=0; i<nrCastlingRights; i++) {\r
5747            castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];\r
5748            if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||\r
5749               castlingRights[forwardMostMove][i] == toX   && castlingRank[i] == toY   \r
5750              ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece\r
5751 \r
5752        }\r
5753 \r
5754     }\r
5755     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
5756     gameInfo.result = GameUnfinished;\r
5757     if (gameInfo.resultDetails != NULL) {\r
5758         free(gameInfo.resultDetails);\r
5759         gameInfo.resultDetails = NULL;\r
5760     }\r
5761     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
5762                               moveList[forwardMostMove - 1]);\r
5763     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
5764                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
5765                              fromY, fromX, toY, toX, promoChar,\r
5766                              parseList[forwardMostMove - 1]);\r
5767     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5768                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
5769                             castlingRights[forwardMostMove]) ) {\r
5770       case MT_NONE:\r
5771       case MT_STALEMATE:\r
5772       default:\r
5773         break;\r
5774       case MT_CHECK:\r
5775         strcat(parseList[forwardMostMove - 1], "+");\r
5776         break;\r
5777       case MT_CHECKMATE:\r
5778         strcat(parseList[forwardMostMove - 1], "#");\r
5779         break;\r
5780     }\r
5781 }\r
5782 \r
5783 /* Updates currentMove if not pausing */\r
5784 void\r
5785 ShowMove(fromX, fromY, toX, toY)\r
5786 {\r
5787     int instant = (gameMode == PlayFromGameFile) ?\r
5788         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
5789     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
5790         if (!instant) {\r
5791             if (forwardMostMove == currentMove + 1) {\r
5792                 AnimateMove(boards[forwardMostMove - 1],\r
5793                             fromX, fromY, toX, toY);\r
5794             }\r
5795             if (appData.highlightLastMove) {\r
5796                 SetHighlights(fromX, fromY, toX, toY);\r
5797             }\r
5798         }\r
5799         currentMove = forwardMostMove;\r
5800     }\r
5801 \r
5802     if (instant) return;\r
5803 \r
5804     DisplayMove(currentMove - 1);\r
5805     DrawPosition(FALSE, boards[currentMove]);\r
5806     DisplayBothClocks();\r
5807     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
5808 }\r
5809 \r
5810 \r
5811 void\r
5812 InitChessProgram(cps)\r
5813      ChessProgramState *cps;\r
5814 {\r
5815     char buf[MSG_SIZ];\r
5816     if (appData.noChessProgram) return;\r
5817     hintRequested = FALSE;\r
5818     bookRequested = FALSE;\r
5819     SendToProgram(cps->initString, cps);\r
5820     if (gameInfo.variant != VariantNormal &&\r
5821         gameInfo.variant != VariantLoadable) {\r
5822       char *v = VariantName(gameInfo.variant);\r
5823       if (StrStr(cps->variants, v) == NULL) {\r
5824         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
5825         DisplayFatalError(buf, 0, 1);\r
5826         return;\r
5827       }\r
5828       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
5829       SendToProgram(buf, cps);\r
5830     }\r
5831     if (cps->sendICS) {\r
5832       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
5833       SendToProgram(buf, cps);\r
5834     }\r
5835     cps->maybeThinking = FALSE;\r
5836     cps->offeredDraw = 0;\r
5837     if (!appData.icsActive) {\r
5838         SendTimeControl(cps, movesPerSession, timeControl,\r
5839                         timeIncrement, appData.searchDepth,\r
5840                         searchTime);\r
5841     }\r
5842     if (appData.showThinking) {\r
5843         SendToProgram("post\n", cps);\r
5844     }\r
5845     SendToProgram("hard\n", cps);\r
5846     if (!appData.ponderNextMove) {\r
5847         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
5848            it without being sure what state we are in first.  "hard"\r
5849            is not a toggle, so that one is OK.\r
5850          */\r
5851         SendToProgram("easy\n", cps);\r
5852     }\r
5853     if (cps->usePing) {\r
5854       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
5855       SendToProgram(buf, cps);\r
5856     }\r
5857     cps->initDone = TRUE;\r
5858 }   \r
5859 \r
5860 \r
5861 void\r
5862 StartChessProgram(cps)\r
5863      ChessProgramState *cps;\r
5864 {\r
5865     char buf[MSG_SIZ];\r
5866     int err;\r
5867 \r
5868     if (appData.noChessProgram) return;\r
5869     cps->initDone = FALSE;\r
5870 \r
5871     if (strcmp(cps->host, "localhost") == 0) {\r
5872         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
5873     } else if (*appData.remoteShell == NULLCHAR) {\r
5874         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
5875     } else {\r
5876         if (*appData.remoteUser == NULLCHAR) {\r
5877             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
5878                     cps->program);\r
5879         } else {\r
5880             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
5881                     cps->host, appData.remoteUser, cps->program);\r
5882         }\r
5883         err = StartChildProcess(buf, "", &cps->pr);\r
5884     }\r
5885     \r
5886     if (err != 0) {\r
5887         sprintf(buf, "Startup failure on '%s'", cps->program);\r
5888         DisplayFatalError(buf, err, 1);\r
5889         cps->pr = NoProc;\r
5890         cps->isr = NULL;\r
5891         return;\r
5892     }\r
5893     \r
5894     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
5895     if (cps->protocolVersion > 1) {\r
5896       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
5897       SendToProgram(buf, cps);\r
5898     } else {\r
5899       SendToProgram("xboard\n", cps);\r
5900     }\r
5901 }\r
5902 \r
5903 \r
5904 void\r
5905 TwoMachinesEventIfReady P((void))\r
5906 {\r
5907   if (first.lastPing != first.lastPong) {\r
5908     DisplayMessage("", "Waiting for first chess program");\r
5909     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
5910     return;\r
5911   }\r
5912   if (second.lastPing != second.lastPong) {\r
5913     DisplayMessage("", "Waiting for second chess program");\r
5914     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
5915     return;\r
5916   }\r
5917   ThawUI();\r
5918   TwoMachinesEvent();\r
5919 }\r
5920 \r
5921 void\r
5922 NextMatchGame P((void))\r
5923 {\r
5924     Reset(FALSE, TRUE);\r
5925     if (*appData.loadGameFile != NULLCHAR) {\r
5926         LoadGameFromFile(appData.loadGameFile,\r
5927                          appData.loadGameIndex,\r
5928                          appData.loadGameFile, FALSE);\r
5929     } else if (*appData.loadPositionFile != NULLCHAR) {\r
5930         LoadPositionFromFile(appData.loadPositionFile,\r
5931                              appData.loadPositionIndex,\r
5932                              appData.loadPositionFile);\r
5933     }\r
5934     TwoMachinesEventIfReady();\r
5935 }\r
5936 \r
5937 void UserAdjudicationEvent( int result )\r
5938 {\r
5939     ChessMove gameResult = GameIsDrawn;\r
5940 \r
5941     if( result > 0 ) {\r
5942         gameResult = WhiteWins;\r
5943     }\r
5944     else if( result < 0 ) {\r
5945         gameResult = BlackWins;\r
5946     }\r
5947 \r
5948     if( gameMode == TwoMachinesPlay ) {\r
5949         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
5950     }\r
5951 }\r
5952 \r
5953 \r
5954 void\r
5955 GameEnds(result, resultDetails, whosays)\r
5956      ChessMove result;\r
5957      char *resultDetails;\r
5958      int whosays;\r
5959 {\r
5960     GameMode nextGameMode;\r
5961     int isIcsGame;\r
5962     char buf[MSG_SIZ];\r
5963 \r
5964     if (appData.debugMode) {\r
5965       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
5966               result, resultDetails ? resultDetails : "(null)", whosays);\r
5967     }\r
5968 \r
5969     if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {\r
5970         /* If we are playing on ICS, the server decides when the\r
5971            game is over, but the engine can offer to draw, claim \r
5972            a draw, or resign. \r
5973          */\r
5974 #if ZIPPY\r
5975         if (appData.zippyPlay && first.initDone) {\r
5976             if (result == GameIsDrawn) {\r
5977                 /* In case draw still needs to be claimed */\r
5978                 SendToICS(ics_prefix);\r
5979                 SendToICS("draw\n");\r
5980             } else if (StrCaseStr(resultDetails, "resign")) {\r
5981                 SendToICS(ics_prefix);\r
5982                 SendToICS("resign\n");\r
5983             }\r
5984         }\r
5985 #endif\r
5986         return;\r
5987     }\r
5988 \r
5989     /* If we're loading the game from a file, stop */\r
5990     if (whosays == GE_FILE) {\r
5991       (void) StopLoadGameTimer();\r
5992       gameFileFP = NULL;\r
5993     }\r
5994 \r
5995     /* Cancel draw offers */\r
5996    first.offeredDraw = second.offeredDraw = 0;\r
5997 \r
5998     /* If this is an ICS game, only ICS can really say it's done;\r
5999        if not, anyone can. */\r
6000     isIcsGame = (gameMode == IcsPlayingWhite || \r
6001                  gameMode == IcsPlayingBlack || \r
6002                  gameMode == IcsObserving    || \r
6003                  gameMode == IcsExamining);\r
6004 \r
6005     if (!isIcsGame || whosays == GE_ICS) {\r
6006         /* OK -- not an ICS game, or ICS said it was done */\r
6007         StopClocks();\r
6008     if (appData.debugMode) {\r
6009       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6010               result, resultDetails ? resultDetails : "(null)", whosays);\r
6011     }\r
6012         if (!isIcsGame && !appData.noChessProgram) \r
6013           SetUserThinkingEnables();\r
6014     \r
6015         /* [HGM] if a machine claims the game end we verify this claim */\r
6016         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6017             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6018                 char claimer;\r
6019 \r
6020     if (appData.debugMode) {\r
6021       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6022               result, resultDetails ? resultDetails : "(null)", whosays);\r
6023     }\r
6024                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6025                                             first.twoMachinesColor[0] :\r
6026                                             second.twoMachinesColor[0] ;\r
6027                 if( result == WhiteWins && claimer == 'w' ||\r
6028                     result == BlackWins && claimer == 'b' ) {\r
6029                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6030                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6031                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6032                       resultDetails = buf;\r
6033                 } else\r
6034                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {\r
6035                       /* Draw that was not flagged by Xboard is false */\r
6036                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6037                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6038                       resultDetails = buf;\r
6039                 }\r
6040                 /* (Claiming a loss is accepted no questions asked!) */\r
6041         }\r
6042 \r
6043     if (appData.debugMode) {\r
6044       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6045               result, resultDetails ? resultDetails : "(null)", whosays);\r
6046     }\r
6047         if (resultDetails != NULL) {\r
6048             gameInfo.result = result;\r
6049             gameInfo.resultDetails = StrSave(resultDetails);\r
6050 \r
6051             /* Tell program how game ended in case it is learning */\r
6052             if (gameMode == MachinePlaysWhite ||\r
6053                 gameMode == MachinePlaysBlack ||\r
6054                 gameMode == TwoMachinesPlay ||\r
6055                 gameMode == IcsPlayingWhite ||\r
6056                 gameMode == IcsPlayingBlack ||\r
6057                 gameMode == BeginningOfGame) {\r
6058                 char buf[MSG_SIZ];\r
6059                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
6060                         resultDetails);\r
6061                 if (first.pr != NoProc) {\r
6062                     SendToProgram(buf, &first);\r
6063                 }\r
6064                 if (second.pr != NoProc &&\r
6065                     gameMode == TwoMachinesPlay) {\r
6066                     SendToProgram(buf, &second);\r
6067                 }\r
6068             }\r
6069 \r
6070             /* display last move only if game was not loaded from file */\r
6071             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
6072                 DisplayMove(currentMove - 1);\r
6073     \r
6074             if (forwardMostMove != 0) {\r
6075                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
6076                     if (*appData.saveGameFile != NULLCHAR) {\r
6077                         SaveGameToFile(appData.saveGameFile, TRUE);\r
6078                     } else if (appData.autoSaveGames) {\r
6079                         AutoSaveGame();\r
6080                     }\r
6081                     if (*appData.savePositionFile != NULLCHAR) {\r
6082                         SavePositionToFile(appData.savePositionFile);\r
6083                     }\r
6084                 }\r
6085             }\r
6086         }\r
6087 \r
6088         if (appData.icsActive) {\r
6089             if (appData.quietPlay &&\r
6090                 (gameMode == IcsPlayingWhite ||\r
6091                  gameMode == IcsPlayingBlack)) {\r
6092                 SendToICS(ics_prefix);\r
6093                 SendToICS("set shout 1\n");\r
6094             }\r
6095             nextGameMode = IcsIdle;\r
6096             ics_user_moved = FALSE;\r
6097             /* clean up premove.  It's ugly when the game has ended and the\r
6098              * premove highlights are still on the board.\r
6099              */\r
6100             if (gotPremove) {\r
6101               gotPremove = FALSE;\r
6102               ClearPremoveHighlights();\r
6103               DrawPosition(FALSE, boards[currentMove]);\r
6104             }\r
6105             if (whosays == GE_ICS) {\r
6106                 switch (result) {\r
6107                 case WhiteWins:\r
6108                     if (gameMode == IcsPlayingWhite)\r
6109                         PlayIcsWinSound();\r
6110                     else if(gameMode == IcsPlayingBlack)\r
6111                         PlayIcsLossSound();\r
6112                     break;\r
6113                 case BlackWins:\r
6114                     if (gameMode == IcsPlayingBlack)\r
6115                         PlayIcsWinSound();\r
6116                     else if(gameMode == IcsPlayingWhite)\r
6117                         PlayIcsLossSound();\r
6118                     break;\r
6119                 case GameIsDrawn:\r
6120                     PlayIcsDrawSound();\r
6121                     break;\r
6122                 default:\r
6123                     PlayIcsUnfinishedSound();\r
6124                 }\r
6125             }\r
6126         } else if (gameMode == EditGame ||\r
6127                    gameMode == PlayFromGameFile || \r
6128                    gameMode == AnalyzeMode || \r
6129                    gameMode == AnalyzeFile) {\r
6130             nextGameMode = gameMode;\r
6131         } else {\r
6132             nextGameMode = EndOfGame;\r
6133         }\r
6134         pausing = FALSE;\r
6135         ModeHighlight();\r
6136     } else {\r
6137         nextGameMode = gameMode;\r
6138     }\r
6139 \r
6140     if (appData.noChessProgram) {\r
6141         gameMode = nextGameMode;\r
6142         ModeHighlight();\r
6143         return;\r
6144     }\r
6145 \r
6146     if (first.reuse) {\r
6147         /* Put first chess program into idle state */\r
6148         if (first.pr != NoProc &&\r
6149             (gameMode == MachinePlaysWhite ||\r
6150              gameMode == MachinePlaysBlack ||\r
6151              gameMode == TwoMachinesPlay ||\r
6152              gameMode == IcsPlayingWhite ||\r
6153              gameMode == IcsPlayingBlack ||\r
6154              gameMode == BeginningOfGame)) {\r
6155             SendToProgram("force\n", &first);\r
6156             if (first.usePing) {\r
6157               char buf[MSG_SIZ];\r
6158               sprintf(buf, "ping %d\n", ++first.lastPing);\r
6159               SendToProgram(buf, &first);\r
6160             }\r
6161         }\r
6162     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6163         /* Kill off first chess program */\r
6164         if (first.isr != NULL)\r
6165           RemoveInputSource(first.isr);\r
6166         first.isr = NULL;\r
6167     \r
6168         if (first.pr != NoProc) {\r
6169             ExitAnalyzeMode();\r
6170             DoSleep( appData.delayBeforeQuit );\r
6171             SendToProgram("quit\n", &first);\r
6172             DoSleep( appData.delayAfterQuit );\r
6173             DestroyChildProcess(first.pr, first.useSigterm);\r
6174         }\r
6175         first.pr = NoProc;\r
6176     }\r
6177     if (second.reuse) {\r
6178         /* Put second chess program into idle state */\r
6179         if (second.pr != NoProc &&\r
6180             gameMode == TwoMachinesPlay) {\r
6181             SendToProgram("force\n", &second);\r
6182             if (second.usePing) {\r
6183               char buf[MSG_SIZ];\r
6184               sprintf(buf, "ping %d\n", ++second.lastPing);\r
6185               SendToProgram(buf, &second);\r
6186             }\r
6187         }\r
6188     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6189         /* Kill off second chess program */\r
6190         if (second.isr != NULL)\r
6191           RemoveInputSource(second.isr);\r
6192         second.isr = NULL;\r
6193     \r
6194         if (second.pr != NoProc) {\r
6195             DoSleep( appData.delayBeforeQuit );\r
6196             SendToProgram("quit\n", &second);\r
6197             DoSleep( appData.delayAfterQuit );\r
6198             DestroyChildProcess(second.pr, second.useSigterm);\r
6199         }\r
6200         second.pr = NoProc;\r
6201     }\r
6202 \r
6203     if (matchMode && gameMode == TwoMachinesPlay) {\r
6204         switch (result) {\r
6205         case WhiteWins:\r
6206           if (first.twoMachinesColor[0] == 'w') {\r
6207             first.matchWins++;\r
6208           } else {\r
6209             second.matchWins++;\r
6210           }\r
6211           break;\r
6212         case BlackWins:\r
6213           if (first.twoMachinesColor[0] == 'b') {\r
6214             first.matchWins++;\r
6215           } else {\r
6216             second.matchWins++;\r
6217           }\r
6218           break;\r
6219         default:\r
6220           break;\r
6221         }\r
6222         if (matchGame < appData.matchGames) {\r
6223             char *tmp;\r
6224             tmp = first.twoMachinesColor;\r
6225             first.twoMachinesColor = second.twoMachinesColor;\r
6226             second.twoMachinesColor = tmp;\r
6227             gameMode = nextGameMode;\r
6228             matchGame++;\r
6229             if(appData.matchPause>10000 || appData.matchPause<10)\r
6230                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
6231             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
6232             return;\r
6233         } else {\r
6234             char buf[MSG_SIZ];\r
6235             gameMode = nextGameMode;\r
6236             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
6237                     first.tidy, second.tidy,\r
6238                     first.matchWins, second.matchWins,\r
6239                     appData.matchGames - (first.matchWins + second.matchWins));\r
6240             DisplayFatalError(buf, 0, 0);\r
6241         }\r
6242     }\r
6243     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6244         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
6245       ExitAnalyzeMode();\r
6246     gameMode = nextGameMode;\r
6247     ModeHighlight();\r
6248 }\r
6249 \r
6250 /* Assumes program was just initialized (initString sent).\r
6251    Leaves program in force mode. */\r
6252 void\r
6253 FeedMovesToProgram(cps, upto) \r
6254      ChessProgramState *cps;\r
6255      int upto;\r
6256 {\r
6257     int i;\r
6258     \r
6259     if (appData.debugMode)\r
6260       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
6261               startedFromSetupPosition ? "position and " : "",\r
6262               backwardMostMove, upto, cps->which);\r
6263     SendToProgram("force\n", cps);\r
6264     if (startedFromSetupPosition) {\r
6265         SendBoard(cps, backwardMostMove);\r
6266     }\r
6267     for (i = backwardMostMove; i < upto; i++) {\r
6268         SendMoveToProgram(i, cps);\r
6269     }\r
6270 }\r
6271 \r
6272 \r
6273 void\r
6274 ResurrectChessProgram()\r
6275 {\r
6276      /* The chess program may have exited.\r
6277         If so, restart it and feed it all the moves made so far. */\r
6278 \r
6279     if (appData.noChessProgram || first.pr != NoProc) return;\r
6280     \r
6281     StartChessProgram(&first);\r
6282     InitChessProgram(&first);\r
6283     FeedMovesToProgram(&first, currentMove);\r
6284 \r
6285     if (!first.sendTime) {\r
6286         /* can't tell gnuchess what its clock should read,\r
6287            so we bow to its notion. */\r
6288         ResetClocks();\r
6289         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
6290         timeRemaining[1][currentMove] = blackTimeRemaining;\r
6291     }\r
6292 \r
6293     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6294         first.analysisSupport) {\r
6295       SendToProgram("analyze\n", &first);\r
6296       first.analyzing = TRUE;\r
6297     }\r
6298 }\r
6299 \r
6300 /*\r
6301  * Button procedures\r
6302  */\r
6303 void\r
6304 Reset(redraw, init)\r
6305      int redraw, init;\r
6306 {\r
6307     int i;\r
6308 \r
6309     if (appData.debugMode) {\r
6310         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
6311                 redraw, init, gameMode);\r
6312     }\r
6313 \r
6314     pausing = pauseExamInvalid = FALSE;\r
6315     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
6316     firstMove = TRUE;\r
6317     whiteFlag = blackFlag = FALSE;\r
6318     userOfferedDraw = FALSE;\r
6319     hintRequested = bookRequested = FALSE;\r
6320     first.maybeThinking = FALSE;\r
6321     second.maybeThinking = FALSE;\r
6322     thinkOutput[0] = NULLCHAR;\r
6323     lastHint[0] = NULLCHAR;\r
6324     ClearGameInfo(&gameInfo);\r
6325     gameInfo.variant = StringToVariant(appData.variant);\r
6326     ics_user_moved = ics_clock_paused = FALSE;\r
6327     ics_getting_history = H_FALSE;\r
6328     ics_gamenum = -1;\r
6329     white_holding[0] = black_holding[0] = NULLCHAR;\r
6330     ClearProgramStats();\r
6331     \r
6332     ResetFrontEnd();\r
6333     ClearHighlights();\r
6334     flipView = appData.flipView;\r
6335     ClearPremoveHighlights();\r
6336     gotPremove = FALSE;\r
6337     alarmSounded = FALSE;\r
6338 \r
6339     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
6340     ExitAnalyzeMode();\r
6341     gameMode = BeginningOfGame;\r
6342     ModeHighlight();\r
6343     InitPosition(redraw);\r
6344     for (i = 0; i < MAX_MOVES; i++) {\r
6345         if (commentList[i] != NULL) {\r
6346             free(commentList[i]);\r
6347             commentList[i] = NULL;\r
6348         }\r
6349     }\r
6350     ResetClocks();\r
6351     timeRemaining[0][0] = whiteTimeRemaining;\r
6352     timeRemaining[1][0] = blackTimeRemaining;\r
6353     if (first.pr == NULL) {\r
6354         StartChessProgram(&first);\r
6355     }\r
6356     if (init) InitChessProgram(&first);\r
6357     DisplayTitle("");\r
6358     DisplayMessage("", "");\r
6359     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
6360 }\r
6361 \r
6362 void\r
6363 AutoPlayGameLoop()\r
6364 {\r
6365     for (;;) {\r
6366         if (!AutoPlayOneMove())\r
6367           return;\r
6368         if (matchMode || appData.timeDelay == 0)\r
6369           continue;\r
6370         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
6371           return;\r
6372         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
6373         break;\r
6374     }\r
6375 }\r
6376 \r
6377 \r
6378 int\r
6379 AutoPlayOneMove()\r
6380 {\r
6381     int fromX, fromY, toX, toY;\r
6382 \r
6383     if (appData.debugMode) {\r
6384       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
6385     }\r
6386 \r
6387     if (gameMode != PlayFromGameFile)\r
6388       return FALSE;\r
6389 \r
6390     if (currentMove >= forwardMostMove) {\r
6391       gameMode = EditGame;\r
6392       ModeHighlight();\r
6393 \r
6394       /* [AS] Clear current move marker at the end of a game */\r
6395       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
6396 \r
6397       return FALSE;\r
6398     }\r
6399     \r
6400     toX = moveList[currentMove][2] - 'a';\r
6401     toY = moveList[currentMove][3] - ONE;\r
6402 \r
6403     if (moveList[currentMove][1] == '@') {\r
6404         if (appData.highlightLastMove) {\r
6405             SetHighlights(-1, -1, toX, toY);\r
6406         }\r
6407     } else {\r
6408         fromX = moveList[currentMove][0] - 'a';\r
6409         fromY = moveList[currentMove][1] - ONE;\r
6410 \r
6411         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
6412 \r
6413         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
6414 \r
6415         if (appData.highlightLastMove) {\r
6416             SetHighlights(fromX, fromY, toX, toY);\r
6417         }\r
6418     }\r
6419     DisplayMove(currentMove);\r
6420     SendMoveToProgram(currentMove++, &first);\r
6421     DisplayBothClocks();\r
6422     DrawPosition(FALSE, boards[currentMove]);\r
6423     if (commentList[currentMove] != NULL) {\r
6424         DisplayComment(currentMove - 1, commentList[currentMove]);\r
6425     }\r
6426     return TRUE;\r
6427 }\r
6428 \r
6429 \r
6430 int\r
6431 LoadGameOneMove(readAhead)\r
6432      ChessMove readAhead;\r
6433 {\r
6434     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
6435     char promoChar = NULLCHAR;\r
6436     ChessMove moveType;\r
6437     char move[MSG_SIZ];\r
6438     char *p, *q;\r
6439     \r
6440     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
6441         gameMode != AnalyzeMode && gameMode != Training) {\r
6442         gameFileFP = NULL;\r
6443         return FALSE;\r
6444     }\r
6445     \r
6446     yyboardindex = forwardMostMove;\r
6447     if (readAhead != (ChessMove)0) {\r
6448       moveType = readAhead;\r
6449     } else {\r
6450       if (gameFileFP == NULL)\r
6451           return FALSE;\r
6452       moveType = (ChessMove) yylex();\r
6453     }\r
6454     \r
6455     done = FALSE;\r
6456     switch (moveType) {\r
6457       case Comment:\r
6458         if (appData.debugMode) \r
6459           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
6460         p = yy_text;\r
6461         if (*p == '{' || *p == '[' || *p == '(') {\r
6462             p[strlen(p) - 1] = NULLCHAR;\r
6463             p++;\r
6464         }\r
6465 \r
6466         /* append the comment but don't display it */\r
6467         while (*p == '\n') p++;\r
6468         AppendComment(currentMove, p);\r
6469         return TRUE;\r
6470 \r
6471       case WhiteCapturesEnPassant:\r
6472       case BlackCapturesEnPassant:\r
6473 #ifdef FAIRY\r
6474       case WhitePromotionChancellor:\r
6475       case BlackPromotionChancellor:\r
6476       case WhitePromotionArchbishop:\r
6477       case BlackPromotionArchbishop:\r
6478 #endif\r
6479       case WhitePromotionQueen:\r
6480       case BlackPromotionQueen:\r
6481       case WhitePromotionRook:\r
6482       case BlackPromotionRook:\r
6483       case WhitePromotionBishop:\r
6484       case BlackPromotionBishop:\r
6485       case WhitePromotionKnight:\r
6486       case BlackPromotionKnight:\r
6487       case WhitePromotionKing:\r
6488       case BlackPromotionKing:\r
6489       case NormalMove:\r
6490       case WhiteKingSideCastle:\r
6491       case WhiteQueenSideCastle:\r
6492       case BlackKingSideCastle:\r
6493       case BlackQueenSideCastle:\r
6494       case WhiteKingSideCastleWild:\r
6495       case WhiteQueenSideCastleWild:\r
6496       case BlackKingSideCastleWild:\r
6497       case BlackQueenSideCastleWild:\r
6498       /* PUSH Fabien */\r
6499       case WhiteHSideCastleFR:\r
6500       case WhiteASideCastleFR:\r
6501       case BlackHSideCastleFR:\r
6502       case BlackASideCastleFR:\r
6503       /* POP Fabien */\r
6504         if (appData.debugMode)\r
6505           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
6506         fromX = currentMoveString[0] - 'a';\r
6507         fromY = currentMoveString[1] - ONE;\r
6508         toX = currentMoveString[2] - 'a';\r
6509         toY = currentMoveString[3] - ONE;\r
6510         promoChar = currentMoveString[4];\r
6511         break;\r
6512 \r
6513       case WhiteDrop:\r
6514       case BlackDrop:\r
6515         if (appData.debugMode)\r
6516           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
6517         fromX = moveType == WhiteDrop ?\r
6518           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6519         (int) CharToPiece(ToLower(currentMoveString[0]));\r
6520         fromY = DROP_RANK;\r
6521         toX = currentMoveString[2] - 'a';\r
6522         toY = currentMoveString[3] - ONE;\r
6523         break;\r
6524 \r
6525       case WhiteWins:\r
6526       case BlackWins:\r
6527       case GameIsDrawn:\r
6528       case GameUnfinished:\r
6529         if (appData.debugMode)\r
6530           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
6531         p = strchr(yy_text, '{');\r
6532         if (p == NULL) p = strchr(yy_text, '(');\r
6533         if (p == NULL) {\r
6534             p = yy_text;\r
6535             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6536         } else {\r
6537             q = strchr(p, *p == '{' ? '}' : ')');\r
6538             if (q != NULL) *q = NULLCHAR;\r
6539             p++;\r
6540         }\r
6541         GameEnds(moveType, p, GE_FILE);\r
6542         done = TRUE;\r
6543         if (cmailMsgLoaded) {\r
6544             ClearHighlights();\r
6545             flipView = WhiteOnMove(currentMove);\r
6546             if (moveType == GameUnfinished) flipView = !flipView;\r
6547             if (appData.debugMode)\r
6548               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
6549         }\r
6550         break;\r
6551 \r
6552       case (ChessMove) 0:       /* end of file */\r
6553         if (appData.debugMode)\r
6554           fprintf(debugFP, "Parser hit end of file\n");\r
6555         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
6556                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
6557           case MT_NONE:\r
6558           case MT_CHECK:\r
6559             break;\r
6560           case MT_CHECKMATE:\r
6561             if (WhiteOnMove(currentMove)) {\r
6562                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
6563             } else {\r
6564                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
6565             }\r
6566             break;\r
6567           case MT_STALEMATE:\r
6568             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
6569             break;\r
6570         }\r
6571         done = TRUE;\r
6572         break;\r
6573 \r
6574       case MoveNumberOne:\r
6575         if (lastLoadGameStart == GNUChessGame) {\r
6576             /* GNUChessGames have numbers, but they aren't move numbers */\r
6577             if (appData.debugMode)\r
6578               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
6579                       yy_text, (int) moveType);\r
6580             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
6581         }\r
6582         /* else fall thru */\r
6583 \r
6584       case XBoardGame:\r
6585       case GNUChessGame:\r
6586       case PGNTag:\r
6587         /* Reached start of next game in file */\r
6588         if (appData.debugMode)\r
6589           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
6590         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
6591                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
6592           case MT_NONE:\r
6593           case MT_CHECK:\r
6594             break;\r
6595           case MT_CHECKMATE:\r
6596             if (WhiteOnMove(currentMove)) {\r
6597                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
6598             } else {\r
6599                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
6600             }\r
6601             break;\r
6602           case MT_STALEMATE:\r
6603             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
6604             break;\r
6605         }\r
6606         done = TRUE;\r
6607         break;\r
6608 \r
6609       case PositionDiagram:     /* should not happen; ignore */\r
6610       case ElapsedTime:         /* ignore */\r
6611       case NAG:                 /* ignore */\r
6612         if (appData.debugMode)\r
6613           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
6614                   yy_text, (int) moveType);\r
6615         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
6616 \r
6617       case IllegalMove:\r
6618         if (appData.testLegality) {\r
6619             if (appData.debugMode)\r
6620               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
6621             sprintf(move, "Illegal move: %d.%s%s",\r
6622                     (forwardMostMove / 2) + 1,\r
6623                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6624             DisplayError(move, 0);\r
6625             done = TRUE;\r
6626         } else {\r
6627             if (appData.debugMode)\r
6628               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
6629                       yy_text, currentMoveString);\r
6630             fromX = currentMoveString[0] - 'a';\r
6631             fromY = currentMoveString[1] - ONE;\r
6632             toX = currentMoveString[2] - 'a';\r
6633             toY = currentMoveString[3] - ONE;\r
6634             promoChar = currentMoveString[4];\r
6635         }\r
6636         break;\r
6637 \r
6638       case AmbiguousMove:\r
6639         if (appData.debugMode)\r
6640           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
6641         sprintf(move, "Ambiguous move: %d.%s%s",\r
6642                 (forwardMostMove / 2) + 1,\r
6643                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6644         DisplayError(move, 0);\r
6645         done = TRUE;\r
6646         break;\r
6647 \r
6648       default:\r
6649       case ImpossibleMove:\r
6650         if (appData.debugMode)\r
6651           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
6652         sprintf(move, "Illegal move: %d.%s%s",\r
6653                 (forwardMostMove / 2) + 1,\r
6654                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6655         DisplayError(move, 0);\r
6656         done = TRUE;\r
6657         break;\r
6658     }\r
6659 \r
6660     if (done) {\r
6661         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
6662             DrawPosition(FALSE, boards[currentMove]);\r
6663             DisplayBothClocks();\r
6664             if (!appData.matchMode && commentList[currentMove] != NULL)\r
6665               DisplayComment(currentMove - 1, commentList[currentMove]);\r
6666         }\r
6667         (void) StopLoadGameTimer();\r
6668         gameFileFP = NULL;\r
6669         cmailOldMove = forwardMostMove;\r
6670         return FALSE;\r
6671     } else {\r
6672         /* currentMoveString is set as a side-effect of yylex */\r
6673         strcat(currentMoveString, "\n");\r
6674         strcpy(moveList[forwardMostMove], currentMoveString);\r
6675         \r
6676         thinkOutput[0] = NULLCHAR;\r
6677         MakeMove(fromX, fromY, toX, toY, promoChar);\r
6678         currentMove = forwardMostMove;\r
6679         return TRUE;\r
6680     }\r
6681 }\r
6682 \r
6683 /* Load the nth game from the given file */\r
6684 int\r
6685 LoadGameFromFile(filename, n, title, useList)\r
6686      char *filename;\r
6687      int n;\r
6688      char *title;\r
6689      /*Boolean*/ int useList;\r
6690 {\r
6691     FILE *f;\r
6692     char buf[MSG_SIZ];\r
6693 \r
6694     if (strcmp(filename, "-") == 0) {\r
6695         f = stdin;\r
6696         title = "stdin";\r
6697     } else {\r
6698         f = fopen(filename, "rb");\r
6699         if (f == NULL) {\r
6700             sprintf(buf, "Can't open \"%s\"", filename);\r
6701             DisplayError(buf, errno);\r
6702             return FALSE;\r
6703         }\r
6704     }\r
6705     if (fseek(f, 0, 0) == -1) {\r
6706         /* f is not seekable; probably a pipe */\r
6707         useList = FALSE;\r
6708     }\r
6709     if (useList && n == 0) {\r
6710         int error = GameListBuild(f);\r
6711         if (error) {\r
6712             DisplayError("Cannot build game list", error);\r
6713         } else if (!ListEmpty(&gameList) &&\r
6714                    ((ListGame *) gameList.tailPred)->number > 1) {\r
6715             GameListPopUp(f, title);\r
6716             return TRUE;\r
6717         }\r
6718         GameListDestroy();\r
6719         n = 1;\r
6720     }\r
6721     if (n == 0) n = 1;\r
6722     return LoadGame(f, n, title, FALSE);\r
6723 }\r
6724 \r
6725 \r
6726 void\r
6727 MakeRegisteredMove()\r
6728 {\r
6729     int fromX, fromY, toX, toY;\r
6730     char promoChar;\r
6731     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
6732         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
6733           case CMAIL_MOVE:\r
6734           case CMAIL_DRAW:\r
6735             if (appData.debugMode)\r
6736               fprintf(debugFP, "Restoring %s for game %d\n",\r
6737                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
6738     \r
6739             thinkOutput[0] = NULLCHAR;\r
6740             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
6741             fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';\r
6742             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
6743             toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';\r
6744             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
6745             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
6746             MakeMove(fromX, fromY, toX, toY, promoChar);\r
6747             ShowMove(fromX, fromY, toX, toY);\r
6748               \r
6749             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
6750                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
6751               case MT_NONE:\r
6752               case MT_CHECK:\r
6753                 break;\r
6754                 \r
6755               case MT_CHECKMATE:\r
6756                 if (WhiteOnMove(currentMove)) {\r
6757                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
6758                 } else {\r
6759                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
6760                 }\r
6761                 break;\r
6762                 \r
6763               case MT_STALEMATE:\r
6764                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
6765                 break;\r
6766             }\r
6767 \r
6768             break;\r
6769             \r
6770           case CMAIL_RESIGN:\r
6771             if (WhiteOnMove(currentMove)) {\r
6772                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
6773             } else {\r
6774                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
6775             }\r
6776             break;\r
6777             \r
6778           case CMAIL_ACCEPT:\r
6779             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
6780             break;\r
6781               \r
6782           default:\r
6783             break;\r
6784         }\r
6785     }\r
6786 \r
6787     return;\r
6788 }\r
6789 \r
6790 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
6791 int\r
6792 CmailLoadGame(f, gameNumber, title, useList)\r
6793      FILE *f;\r
6794      int gameNumber;\r
6795      char *title;\r
6796      int useList;\r
6797 {\r
6798     int retVal;\r
6799 \r
6800     if (gameNumber > nCmailGames) {\r
6801         DisplayError("No more games in this message", 0);\r
6802         return FALSE;\r
6803     }\r
6804     if (f == lastLoadGameFP) {\r
6805         int offset = gameNumber - lastLoadGameNumber;\r
6806         if (offset == 0) {\r
6807             cmailMsg[0] = NULLCHAR;\r
6808             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
6809                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
6810                 nCmailMovesRegistered--;\r
6811             }\r
6812             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
6813             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
6814                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
6815             }\r
6816         } else {\r
6817             if (! RegisterMove()) return FALSE;\r
6818         }\r
6819     }\r
6820 \r
6821     retVal = LoadGame(f, gameNumber, title, useList);\r
6822 \r
6823     /* Make move registered during previous look at this game, if any */\r
6824     MakeRegisteredMove();\r
6825 \r
6826     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
6827         commentList[currentMove]\r
6828           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
6829         DisplayComment(currentMove - 1, commentList[currentMove]);\r
6830     }\r
6831 \r
6832     return retVal;\r
6833 }\r
6834 \r
6835 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
6836 int\r
6837 ReloadGame(offset)\r
6838      int offset;\r
6839 {\r
6840     int gameNumber = lastLoadGameNumber + offset;\r
6841     if (lastLoadGameFP == NULL) {\r
6842         DisplayError("No game has been loaded yet", 0);\r
6843         return FALSE;\r
6844     }\r
6845     if (gameNumber <= 0) {\r
6846         DisplayError("Can't back up any further", 0);\r
6847         return FALSE;\r
6848     }\r
6849     if (cmailMsgLoaded) {\r
6850         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
6851                              lastLoadGameTitle, lastLoadGameUseList);\r
6852     } else {\r
6853         return LoadGame(lastLoadGameFP, gameNumber,\r
6854                         lastLoadGameTitle, lastLoadGameUseList);\r
6855     }\r
6856 }\r
6857 \r
6858 \r
6859 \r
6860 /* Load the nth game from open file f */\r
6861 int\r
6862 LoadGame(f, gameNumber, title, useList)\r
6863      FILE *f;\r
6864      int gameNumber;\r
6865      char *title;\r
6866      int useList;\r
6867 {\r
6868     ChessMove cm;\r
6869     char buf[MSG_SIZ];\r
6870     int gn = gameNumber;\r
6871     ListGame *lg = NULL;\r
6872     int numPGNTags = 0;\r
6873     int err;\r
6874     GameMode oldGameMode;\r
6875 \r
6876     if (appData.debugMode) \r
6877         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
6878 \r
6879     if (gameMode == Training )\r
6880         SetTrainingModeOff();\r
6881 \r
6882     oldGameMode = gameMode;\r
6883     if (gameMode != BeginningOfGame) {\r
6884       Reset(FALSE, TRUE);\r
6885     }\r
6886 \r
6887     gameFileFP = f;\r
6888     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
6889         fclose(lastLoadGameFP);\r
6890     }\r
6891 \r
6892     if (useList) {\r
6893         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
6894         \r
6895         if (lg) {\r
6896             fseek(f, lg->offset, 0);\r
6897             GameListHighlight(gameNumber);\r
6898             gn = 1;\r
6899         }\r
6900         else {\r
6901             DisplayError("Game number out of range", 0);\r
6902             return FALSE;\r
6903         }\r
6904     } else {\r
6905         GameListDestroy();\r
6906         if (fseek(f, 0, 0) == -1) {\r
6907             if (f == lastLoadGameFP ?\r
6908                 gameNumber == lastLoadGameNumber + 1 :\r
6909                 gameNumber == 1) {\r
6910                 gn = 1;\r
6911             } else {\r
6912                 DisplayError("Can't seek on game file", 0);\r
6913                 return FALSE;\r
6914             }\r
6915         }\r
6916     }\r
6917     lastLoadGameFP = f;\r
6918     lastLoadGameNumber = gameNumber;\r
6919     strcpy(lastLoadGameTitle, title);\r
6920     lastLoadGameUseList = useList;\r
6921 \r
6922     yynewfile(f);\r
6923 \r
6924 \r
6925     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
6926         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
6927                 lg->gameInfo.black);\r
6928             DisplayTitle(buf);\r
6929     } else if (*title != NULLCHAR) {\r
6930         if (gameNumber > 1) {\r
6931             sprintf(buf, "%s %d", title, gameNumber);\r
6932             DisplayTitle(buf);\r
6933         } else {\r
6934             DisplayTitle(title);\r
6935         }\r
6936     }\r
6937 \r
6938     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
6939         gameMode = PlayFromGameFile;\r
6940         ModeHighlight();\r
6941     }\r
6942 \r
6943     currentMove = forwardMostMove = backwardMostMove = 0;\r
6944     CopyBoard(boards[0], initialPosition);\r
6945     StopClocks();\r
6946 \r
6947     /*\r
6948      * Skip the first gn-1 games in the file.\r
6949      * Also skip over anything that precedes an identifiable \r
6950      * start of game marker, to avoid being confused by \r
6951      * garbage at the start of the file.  Currently \r
6952      * recognized start of game markers are the move number "1",\r
6953      * the pattern "gnuchess .* game", the pattern\r
6954      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
6955      * A game that starts with one of the latter two patterns\r
6956      * will also have a move number 1, possibly\r
6957      * following a position diagram.\r
6958      * 5-4-02: Let's try being more lenient and allowing a game to\r
6959      * start with an unnumbered move.  Does that break anything?\r
6960      */\r
6961     cm = lastLoadGameStart = (ChessMove) 0;\r
6962     while (gn > 0) {\r
6963         yyboardindex = forwardMostMove;\r
6964         cm = (ChessMove) yylex();\r
6965         switch (cm) {\r
6966           case (ChessMove) 0:\r
6967             if (cmailMsgLoaded) {\r
6968                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
6969             } else {\r
6970                 Reset(TRUE, TRUE);\r
6971                 DisplayError("Game not found in file", 0);\r
6972             }\r
6973             return FALSE;\r
6974 \r
6975           case GNUChessGame:\r
6976           case XBoardGame:\r
6977             gn--;\r
6978             lastLoadGameStart = cm;\r
6979             break;\r
6980             \r
6981           case MoveNumberOne:\r
6982             switch (lastLoadGameStart) {\r
6983               case GNUChessGame:\r
6984               case XBoardGame:\r
6985               case PGNTag:\r
6986                 break;\r
6987               case MoveNumberOne:\r
6988               case (ChessMove) 0:\r
6989                 gn--;           /* count this game */\r
6990                 lastLoadGameStart = cm;\r
6991                 break;\r
6992               default:\r
6993                 /* impossible */\r
6994                 break;\r
6995             }\r
6996             break;\r
6997 \r
6998           case PGNTag:\r
6999             switch (lastLoadGameStart) {\r
7000               case GNUChessGame:\r
7001               case PGNTag:\r
7002               case MoveNumberOne:\r
7003               case (ChessMove) 0:\r
7004                 gn--;           /* count this game */\r
7005                 lastLoadGameStart = cm;\r
7006                 break;\r
7007               case XBoardGame:\r
7008                 lastLoadGameStart = cm; /* game counted already */\r
7009                 break;\r
7010               default:\r
7011                 /* impossible */\r
7012                 break;\r
7013             }\r
7014             if (gn > 0) {\r
7015                 do {\r
7016                     yyboardindex = forwardMostMove;\r
7017                     cm = (ChessMove) yylex();\r
7018                 } while (cm == PGNTag || cm == Comment);\r
7019             }\r
7020             break;\r
7021 \r
7022           case WhiteWins:\r
7023           case BlackWins:\r
7024           case GameIsDrawn:\r
7025             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
7026                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
7027                     != CMAIL_OLD_RESULT) {\r
7028                     nCmailResults ++ ;\r
7029                     cmailResult[  CMAIL_MAX_GAMES\r
7030                                 - gn - 1] = CMAIL_OLD_RESULT;\r
7031                 }\r
7032             }\r
7033             break;\r
7034 \r
7035           case NormalMove:\r
7036             /* Only a NormalMove can be at the start of a game\r
7037              * without a position diagram. */\r
7038             if (lastLoadGameStart == (ChessMove) 0) {\r
7039               gn--;\r
7040               lastLoadGameStart = MoveNumberOne;\r
7041             }\r
7042             break;\r
7043 \r
7044           default:\r
7045             break;\r
7046         }\r
7047     }\r
7048     \r
7049     if (appData.debugMode)\r
7050       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
7051 \r
7052     if (cm == XBoardGame) {\r
7053         /* Skip any header junk before position diagram and/or move 1 */\r
7054         for (;;) {\r
7055             yyboardindex = forwardMostMove;\r
7056             cm = (ChessMove) yylex();\r
7057 \r
7058             if (cm == (ChessMove) 0 ||\r
7059                 cm == GNUChessGame || cm == XBoardGame) {\r
7060                 /* Empty game; pretend end-of-file and handle later */\r
7061                 cm = (ChessMove) 0;\r
7062                 break;\r
7063             }\r
7064 \r
7065             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
7066                 cm == PGNTag || cm == Comment)\r
7067               break;\r
7068         }\r
7069     } else if (cm == GNUChessGame) {\r
7070         if (gameInfo.event != NULL) {\r
7071             free(gameInfo.event);\r
7072         }\r
7073         gameInfo.event = StrSave(yy_text);\r
7074     }   \r
7075 \r
7076     startedFromSetupPosition = FALSE;\r
7077     while (cm == PGNTag) {\r
7078         if (appData.debugMode) \r
7079           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
7080         err = ParsePGNTag(yy_text, &gameInfo);\r
7081         if (!err) numPGNTags++;\r
7082 \r
7083         if (gameInfo.fen != NULL) {\r
7084           Board initial_position;\r
7085           startedFromSetupPosition = TRUE;\r
7086           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
7087             Reset(TRUE, TRUE);\r
7088             DisplayError("Bad FEN position in file", 0);\r
7089             return FALSE;\r
7090           }\r
7091           CopyBoard(boards[0], initial_position);\r
7092           /* [HGM] copy FEN attributes as well */\r
7093           {   int i;\r
7094               initialRulePlies = FENrulePlies;\r
7095               epStatus[0] = FENepStatus;\r
7096               for( i=0; i< nrCastlingRights; i++ )\r
7097                   castlingRights[0][i] = FENcastlingRights[i];\r
7098           }\r
7099           if (blackPlaysFirst) {\r
7100             currentMove = forwardMostMove = backwardMostMove = 1;\r
7101             CopyBoard(boards[1], initial_position);\r
7102             strcpy(moveList[0], "");\r
7103             strcpy(parseList[0], "");\r
7104             timeRemaining[0][1] = whiteTimeRemaining;\r
7105             timeRemaining[1][1] = blackTimeRemaining;\r
7106             if (commentList[0] != NULL) {\r
7107               commentList[1] = commentList[0];\r
7108               commentList[0] = NULL;\r
7109             }\r
7110           } else {\r
7111             currentMove = forwardMostMove = backwardMostMove = 0;\r
7112           }\r
7113           yyboardindex = forwardMostMove;\r
7114           free(gameInfo.fen);\r
7115           gameInfo.fen = NULL;\r
7116         }\r
7117 \r
7118         yyboardindex = forwardMostMove;\r
7119         cm = (ChessMove) yylex();\r
7120 \r
7121         /* Handle comments interspersed among the tags */\r
7122         while (cm == Comment) {\r
7123             char *p;\r
7124             if (appData.debugMode) \r
7125               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7126             p = yy_text;\r
7127             if (*p == '{' || *p == '[' || *p == '(') {\r
7128                 p[strlen(p) - 1] = NULLCHAR;\r
7129                 p++;\r
7130             }\r
7131             while (*p == '\n') p++;\r
7132             AppendComment(currentMove, p);\r
7133             yyboardindex = forwardMostMove;\r
7134             cm = (ChessMove) yylex();\r
7135         }\r
7136     }\r
7137 \r
7138     /* don't rely on existence of Event tag since if game was\r
7139      * pasted from clipboard the Event tag may not exist\r
7140      */\r
7141     if (numPGNTags > 0){\r
7142         char *tags;\r
7143         if (gameInfo.variant == VariantNormal) {\r
7144           gameInfo.variant = StringToVariant(gameInfo.event);\r
7145         }\r
7146         if (!matchMode) {\r
7147           if( appData.autoDisplayTags ) {\r
7148             tags = PGNTags(&gameInfo);\r
7149             TagsPopUp(tags, CmailMsg());\r
7150             free(tags);\r
7151           }\r
7152         }\r
7153     } else {\r
7154         /* Make something up, but don't display it now */\r
7155         SetGameInfo();\r
7156         TagsPopDown();\r
7157     }\r
7158 \r
7159     if (cm == PositionDiagram) {\r
7160         int i, j;\r
7161         char *p;\r
7162         Board initial_position;\r
7163 \r
7164         if (appData.debugMode)\r
7165           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
7166 \r
7167         if (!startedFromSetupPosition) {\r
7168             p = yy_text;\r
7169             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
7170               for (j = 0; j < BOARD_WIDTH; p++)\r
7171                 switch (*p) {\r
7172                   case '[':\r
7173                   case '-':\r
7174                   case ' ':\r
7175                   case '\t':\r
7176                   case '\n':\r
7177                   case '\r':\r
7178                     break;\r
7179                   default:\r
7180                     initial_position[i][j++] = CharToPiece(*p);\r
7181                     break;\r
7182                 }\r
7183             while (*p == ' ' || *p == '\t' ||\r
7184                    *p == '\n' || *p == '\r') p++;\r
7185         \r
7186             if (strncmp(p, "black", strlen("black"))==0)\r
7187               blackPlaysFirst = TRUE;\r
7188             else\r
7189               blackPlaysFirst = FALSE;\r
7190             startedFromSetupPosition = TRUE;\r
7191         \r
7192             CopyBoard(boards[0], initial_position);\r
7193             if (blackPlaysFirst) {\r
7194                 currentMove = forwardMostMove = backwardMostMove = 1;\r
7195                 CopyBoard(boards[1], initial_position);\r
7196                 strcpy(moveList[0], "");\r
7197                 strcpy(parseList[0], "");\r
7198                 timeRemaining[0][1] = whiteTimeRemaining;\r
7199                 timeRemaining[1][1] = blackTimeRemaining;\r
7200                 if (commentList[0] != NULL) {\r
7201                     commentList[1] = commentList[0];\r
7202                     commentList[0] = NULL;\r
7203                 }\r
7204             } else {\r
7205                 currentMove = forwardMostMove = backwardMostMove = 0;\r
7206             }\r
7207         }\r
7208         yyboardindex = forwardMostMove;\r
7209         cm = (ChessMove) yylex();\r
7210     }\r
7211 \r
7212     if (first.pr == NoProc) {\r
7213         StartChessProgram(&first);\r
7214     }\r
7215     InitChessProgram(&first);\r
7216     SendToProgram("force\n", &first);\r
7217     if (startedFromSetupPosition) {\r
7218         SendBoard(&first, forwardMostMove);\r
7219         DisplayBothClocks();\r
7220     }      \r
7221 \r
7222     while (cm == Comment) {\r
7223         char *p;\r
7224         if (appData.debugMode) \r
7225           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7226         p = yy_text;\r
7227         if (*p == '{' || *p == '[' || *p == '(') {\r
7228             p[strlen(p) - 1] = NULLCHAR;\r
7229             p++;\r
7230         }\r
7231         while (*p == '\n') p++;\r
7232         AppendComment(currentMove, p);\r
7233         yyboardindex = forwardMostMove;\r
7234         cm = (ChessMove) yylex();\r
7235     }\r
7236 \r
7237     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
7238         cm == WhiteWins || cm == BlackWins ||\r
7239         cm == GameIsDrawn || cm == GameUnfinished) {\r
7240         DisplayMessage("", "No moves in game");\r
7241         if (cmailMsgLoaded) {\r
7242             if (appData.debugMode)\r
7243               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
7244             ClearHighlights();\r
7245             flipView = FALSE;\r
7246         }\r
7247         DrawPosition(FALSE, boards[currentMove]);\r
7248         DisplayBothClocks();\r
7249         gameMode = EditGame;\r
7250         ModeHighlight();\r
7251         gameFileFP = NULL;\r
7252         cmailOldMove = 0;\r
7253         return TRUE;\r
7254     }\r
7255 \r
7256     if (commentList[currentMove] != NULL) {\r
7257       if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
7258         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7259       }\r
7260     }\r
7261     if (!matchMode && appData.timeDelay != 0) \r
7262       DrawPosition(FALSE, boards[currentMove]);\r
7263 \r
7264     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
7265       programStats.ok_to_send = 1;\r
7266     }\r
7267 \r
7268     /* if the first token after the PGN tags is a move\r
7269      * and not move number 1, retrieve it from the parser \r
7270      */\r
7271     if (cm != MoveNumberOne)\r
7272         LoadGameOneMove(cm);\r
7273 \r
7274     /* load the remaining moves from the file */\r
7275     while (LoadGameOneMove((ChessMove)0)) {\r
7276       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7277       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7278     }\r
7279 \r
7280     /* rewind to the start of the game */\r
7281     currentMove = backwardMostMove;\r
7282 \r
7283     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7284 \r
7285     if (oldGameMode == AnalyzeFile ||\r
7286         oldGameMode == AnalyzeMode) {\r
7287       AnalyzeFileEvent();\r
7288     }\r
7289 \r
7290     if (matchMode || appData.timeDelay == 0) {\r
7291       ToEndEvent();\r
7292       gameMode = EditGame;\r
7293       ModeHighlight();\r
7294     } else if (appData.timeDelay > 0) {\r
7295       AutoPlayGameLoop();\r
7296     }\r
7297 \r
7298     if (appData.debugMode) \r
7299         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
7300     return TRUE;\r
7301 }\r
7302 \r
7303 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
7304 int\r
7305 ReloadPosition(offset)\r
7306      int offset;\r
7307 {\r
7308     int positionNumber = lastLoadPositionNumber + offset;\r
7309     if (lastLoadPositionFP == NULL) {\r
7310         DisplayError("No position has been loaded yet", 0);\r
7311         return FALSE;\r
7312     }\r
7313     if (positionNumber <= 0) {\r
7314         DisplayError("Can't back up any further", 0);\r
7315         return FALSE;\r
7316     }\r
7317     return LoadPosition(lastLoadPositionFP, positionNumber,\r
7318                         lastLoadPositionTitle);\r
7319 }\r
7320 \r
7321 /* Load the nth position from the given file */\r
7322 int\r
7323 LoadPositionFromFile(filename, n, title)\r
7324      char *filename;\r
7325      int n;\r
7326      char *title;\r
7327 {\r
7328     FILE *f;\r
7329     char buf[MSG_SIZ];\r
7330 \r
7331     if (strcmp(filename, "-") == 0) {\r
7332         return LoadPosition(stdin, n, "stdin");\r
7333     } else {\r
7334         f = fopen(filename, "rb");\r
7335         if (f == NULL) {\r
7336             sprintf(buf, "Can't open \"%s\"", filename);\r
7337             DisplayError(buf, errno);\r
7338             return FALSE;\r
7339         } else {\r
7340             return LoadPosition(f, n, title);\r
7341         }\r
7342     }\r
7343 }\r
7344 \r
7345 /* Load the nth position from the given open file, and close it */\r
7346 int\r
7347 LoadPosition(f, positionNumber, title)\r
7348      FILE *f;\r
7349      int positionNumber;\r
7350      char *title;\r
7351 {\r
7352     char *p, line[MSG_SIZ];\r
7353     Board initial_position;\r
7354     int i, j, fenMode, pn;\r
7355     \r
7356     if (gameMode == Training )\r
7357         SetTrainingModeOff();\r
7358 \r
7359     if (gameMode != BeginningOfGame) {\r
7360         Reset(FALSE, TRUE);\r
7361     }\r
7362     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
7363         fclose(lastLoadPositionFP);\r
7364     }\r
7365     if (positionNumber == 0) positionNumber = 1;\r
7366     lastLoadPositionFP = f;\r
7367     lastLoadPositionNumber = positionNumber;\r
7368     strcpy(lastLoadPositionTitle, title);\r
7369     if (first.pr == NoProc) {\r
7370       StartChessProgram(&first);\r
7371       InitChessProgram(&first);\r
7372     }    \r
7373     pn = positionNumber;\r
7374     if (positionNumber < 0) {\r
7375         /* Negative position number means to seek to that byte offset */\r
7376         if (fseek(f, -positionNumber, 0) == -1) {\r
7377             DisplayError("Can't seek on position file", 0);\r
7378             return FALSE;\r
7379         };\r
7380         pn = 1;\r
7381     } else {\r
7382         if (fseek(f, 0, 0) == -1) {\r
7383             if (f == lastLoadPositionFP ?\r
7384                 positionNumber == lastLoadPositionNumber + 1 :\r
7385                 positionNumber == 1) {\r
7386                 pn = 1;\r
7387             } else {\r
7388                 DisplayError("Can't seek on position file", 0);\r
7389                 return FALSE;\r
7390             }\r
7391         }\r
7392     }\r
7393     /* See if this file is FEN or old-style xboard */\r
7394     if (fgets(line, MSG_SIZ, f) == NULL) {\r
7395         DisplayError("Position not found in file", 0);\r
7396         return FALSE;\r
7397     }\r
7398     switch (line[0]) {\r
7399       case '#':  case 'x':\r
7400       default:\r
7401         fenMode = FALSE;\r
7402         break;\r
7403       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
7404       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
7405       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
7406       case '7':  case '8':  case '9':\r
7407 #ifdef FAIRY\r
7408       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
7409       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
7410       case 'C':  case 'W':             case 'c':  case 'w': \r
7411 #endif\r
7412         fenMode = TRUE;\r
7413         break;\r
7414     }\r
7415 \r
7416     if (pn >= 2) {\r
7417         if (fenMode || line[0] == '#') pn--;\r
7418         while (pn > 0) {\r
7419             /* skip postions before number pn */\r
7420             if (fgets(line, MSG_SIZ, f) == NULL) {\r
7421                 Reset(TRUE, TRUE);\r
7422                 DisplayError("Position not found in file", 0);\r
7423                 return FALSE;\r
7424             }\r
7425             if (fenMode || line[0] == '#') pn--;\r
7426         }\r
7427     }\r
7428 \r
7429     if (fenMode) {\r
7430         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
7431             DisplayError("Bad FEN position in file", 0);\r
7432             return FALSE;\r
7433         }\r
7434     } else {\r
7435         (void) fgets(line, MSG_SIZ, f);\r
7436         (void) fgets(line, MSG_SIZ, f);\r
7437     \r
7438         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
7439             (void) fgets(line, MSG_SIZ, f);\r
7440             for (p = line, j = 0; j < BOARD_WIDTH; p++) {\r
7441                 if (*p == ' ')\r
7442                   continue;\r
7443                 initial_position[i][j++] = CharToPiece(*p);\r
7444             }\r
7445         }\r
7446     \r
7447         blackPlaysFirst = FALSE;\r
7448         if (!feof(f)) {\r
7449             (void) fgets(line, MSG_SIZ, f);\r
7450             if (strncmp(line, "black", strlen("black"))==0)\r
7451               blackPlaysFirst = TRUE;\r
7452         }\r
7453     }\r
7454     startedFromSetupPosition = TRUE;\r
7455     \r
7456     SendToProgram("force\n", &first);\r
7457     CopyBoard(boards[0], initial_position);\r
7458           /* [HGM] copy FEN attributes as well */\r
7459           {   int i;\r
7460               initialRulePlies = FENrulePlies;\r
7461               epStatus[0] = FENepStatus;\r
7462               for( i=0; i< nrCastlingRights; i++ )\r
7463                   castlingRights[0][i] = FENcastlingRights[i];\r
7464           }\r
7465     if (blackPlaysFirst) {\r
7466         currentMove = forwardMostMove = backwardMostMove = 1;\r
7467         strcpy(moveList[0], "");\r
7468         strcpy(parseList[0], "");\r
7469         CopyBoard(boards[1], initial_position);\r
7470         DisplayMessage("", "Black to play");\r
7471     } else {\r
7472         currentMove = forwardMostMove = backwardMostMove = 0;\r
7473         DisplayMessage("", "White to play");\r
7474     }\r
7475     SendBoard(&first, forwardMostMove);\r
7476 \r
7477     if (positionNumber > 1) {\r
7478         sprintf(line, "%s %d", title, positionNumber);\r
7479         DisplayTitle(line);\r
7480     } else {\r
7481         DisplayTitle(title);\r
7482     }\r
7483     gameMode = EditGame;\r
7484     ModeHighlight();\r
7485     ResetClocks();\r
7486     timeRemaining[0][1] = whiteTimeRemaining;\r
7487     timeRemaining[1][1] = blackTimeRemaining;\r
7488     DrawPosition(FALSE, boards[currentMove]);\r
7489    \r
7490     return TRUE;\r
7491 }\r
7492 \r
7493 \r
7494 void\r
7495 CopyPlayerNameIntoFileName(dest, src)\r
7496      char **dest, *src;\r
7497 {\r
7498     while (*src != NULLCHAR && *src != ',') {\r
7499         if (*src == ' ') {\r
7500             *(*dest)++ = '_';\r
7501             src++;\r
7502         } else {\r
7503             *(*dest)++ = *src++;\r
7504         }\r
7505     }\r
7506 }\r
7507 \r
7508 char *DefaultFileName(ext)\r
7509      char *ext;\r
7510 {\r
7511     static char def[MSG_SIZ];\r
7512     char *p;\r
7513 \r
7514     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
7515         p = def;\r
7516         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
7517         *p++ = '-';\r
7518         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
7519         *p++ = '.';\r
7520         strcpy(p, ext);\r
7521     } else {\r
7522         def[0] = NULLCHAR;\r
7523     }\r
7524     return def;\r
7525 }\r
7526 \r
7527 /* Save the current game to the given file */\r
7528 int\r
7529 SaveGameToFile(filename, append)\r
7530      char *filename;\r
7531      int append;\r
7532 {\r
7533     FILE *f;\r
7534     char buf[MSG_SIZ];\r
7535 \r
7536     if (strcmp(filename, "-") == 0) {\r
7537         return SaveGame(stdout, 0, NULL);\r
7538     } else {\r
7539         f = fopen(filename, append ? "a" : "w");\r
7540         if (f == NULL) {\r
7541             sprintf(buf, "Can't open \"%s\"", filename);\r
7542             DisplayError(buf, errno);\r
7543             return FALSE;\r
7544         } else {\r
7545             return SaveGame(f, 0, NULL);\r
7546         }\r
7547     }\r
7548 }\r
7549 \r
7550 char *\r
7551 SavePart(str)\r
7552      char *str;\r
7553 {\r
7554     static char buf[MSG_SIZ];\r
7555     char *p;\r
7556     \r
7557     p = strchr(str, ' ');\r
7558     if (p == NULL) return str;\r
7559     strncpy(buf, str, p - str);\r
7560     buf[p - str] = NULLCHAR;\r
7561     return buf;\r
7562 }\r
7563 \r
7564 #define PGN_MAX_LINE 75\r
7565 \r
7566 #define PGN_SIDE_WHITE  0\r
7567 #define PGN_SIDE_BLACK  1\r
7568 \r
7569 /* [AS] */\r
7570 static int FindFirstMoveOutOfBook( int side )\r
7571 {\r
7572     int result = -1;\r
7573 \r
7574     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
7575         int index = backwardMostMove;\r
7576         int has_book_hit = 0;\r
7577 \r
7578         if( (index % 2) != side ) {\r
7579             index++;\r
7580         }\r
7581 \r
7582         while( index < forwardMostMove ) {\r
7583             /* Check to see if engine is in book */\r
7584             int depth = pvInfoList[index].depth;\r
7585             int score = pvInfoList[index].score;\r
7586             int in_book = 0;\r
7587 \r
7588             if( depth <= 2 ) {\r
7589                 in_book = 1;\r
7590             }\r
7591             else if( score == 0 && depth == 63 ) {\r
7592                 in_book = 1; /* Zappa */\r
7593             }\r
7594             else if( score == 2 && depth == 99 ) {\r
7595                 in_book = 1; /* Abrok */\r
7596             }\r
7597 \r
7598             has_book_hit += in_book;\r
7599 \r
7600             if( ! in_book ) {\r
7601                 result = index;\r
7602 \r
7603                 break;\r
7604             }\r
7605 \r
7606             index += 2;\r
7607         }\r
7608     }\r
7609 \r
7610     return result;\r
7611 }\r
7612 \r
7613 /* [AS] */\r
7614 void GetOutOfBookInfo( char * buf )\r
7615 {\r
7616     int oob[2];\r
7617     int i;\r
7618     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
7619 \r
7620     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
7621     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
7622 \r
7623     *buf = '\0';\r
7624 \r
7625     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
7626         for( i=0; i<2; i++ ) {\r
7627             int idx = oob[i];\r
7628 \r
7629             if( idx >= 0 ) {\r
7630                 if( i > 0 && oob[0] >= 0 ) {\r
7631                     strcat( buf, "   " );\r
7632                 }\r
7633 \r
7634                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
7635                 sprintf( buf+strlen(buf), "%s%.2f", \r
7636                     pvInfoList[idx].score >= 0 ? "+" : "",\r
7637                     pvInfoList[idx].score / 100.0 );\r
7638             }\r
7639         }\r
7640     }\r
7641 }\r
7642 \r
7643 /* Save game in PGN style and close the file */\r
7644 int\r
7645 SaveGamePGN(f)\r
7646      FILE *f;\r
7647 {\r
7648     int i, offset, linelen, newblock;\r
7649     time_t tm;\r
7650     char *movetext;\r
7651     char numtext[32];\r
7652     int movelen, numlen, blank;\r
7653     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
7654 \r
7655     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
7656     \r
7657     tm = time((time_t *) NULL);\r
7658     \r
7659     PrintPGNTags(f, &gameInfo);\r
7660     \r
7661     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
7662         char *fen = PositionToFEN(backwardMostMove, 1);\r
7663         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
7664         fprintf(f, "\n{--------------\n");\r
7665         PrintPosition(f, backwardMostMove);\r
7666         fprintf(f, "--------------}\n");\r
7667         free(fen);\r
7668     }\r
7669     else {\r
7670         /* [AS] Out of book annotation */\r
7671         if( appData.saveOutOfBookInfo ) {\r
7672             char buf[64];\r
7673 \r
7674             GetOutOfBookInfo( buf );\r
7675 \r
7676             if( buf[0] != '\0' ) {\r
7677                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
7678             }\r
7679         }\r
7680 \r
7681         fprintf(f, "\n");\r
7682     }\r
7683 \r
7684     i = backwardMostMove;\r
7685     linelen = 0;\r
7686     newblock = TRUE;\r
7687 \r
7688     while (i < forwardMostMove) {\r
7689         /* Print comments preceding this move */\r
7690         if (commentList[i] != NULL) {\r
7691             if (linelen > 0) fprintf(f, "\n");\r
7692             fprintf(f, "{\n%s}\n", commentList[i]);\r
7693             linelen = 0;\r
7694             newblock = TRUE;\r
7695         }\r
7696 \r
7697         /* Format move number */\r
7698         if ((i % 2) == 0) {\r
7699             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
7700         } else {\r
7701             if (newblock) {\r
7702                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
7703             } else {\r
7704                 numtext[0] = NULLCHAR;\r
7705             }\r
7706         }\r
7707         numlen = strlen(numtext);\r
7708         newblock = FALSE;\r
7709 \r
7710         /* Print move number */\r
7711         blank = linelen > 0 && numlen > 0;\r
7712         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
7713             fprintf(f, "\n");\r
7714             linelen = 0;\r
7715             blank = 0;\r
7716         }\r
7717         if (blank) {\r
7718             fprintf(f, " ");\r
7719             linelen++;\r
7720         }\r
7721         fprintf(f, numtext);\r
7722         linelen += numlen;\r
7723 \r
7724         /* Get move */\r
7725         movetext = SavePart(parseList[i]);\r
7726 \r
7727         /* [AS] Add PV info if present */\r
7728         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
7729             /* [HGM] add time */\r
7730             char buf[MSG_SIZ]; int seconds = 0;\r
7731 \r
7732             if(i >= backwardMostMove) {\r
7733                 /* take the time that changed */\r
7734                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
7735                 if(seconds <= 0)\r
7736                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
7737             }\r
7738             seconds /= 1000;\r
7739     if (appData.debugMode) {\r
7740         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
7741                 timeRemaining[0][i+1], timeRemaining[0][i],\r
7742                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
7743         );\r
7744     }\r
7745 \r
7746             if( seconds < 0 ) buf[0] = 0; else\r
7747             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
7748             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
7749 \r
7750             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
7751                 movetext, \r
7752                 pvInfoList[i].score >= 0 ? "+" : "",\r
7753                 pvInfoList[i].score / 100.0,\r
7754                 pvInfoList[i].depth,\r
7755                 buf );\r
7756             movetext = move_buffer;\r
7757         }\r
7758 \r
7759         movelen = strlen(movetext);\r
7760 \r
7761         /* Print move */\r
7762         blank = linelen > 0 && movelen > 0;\r
7763         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
7764             fprintf(f, "\n");\r
7765             linelen = 0;\r
7766             blank = 0;\r
7767         }\r
7768         if (blank) {\r
7769             fprintf(f, " ");\r
7770             linelen++;\r
7771         }\r
7772         fprintf(f, movetext);\r
7773         linelen += movelen;\r
7774 \r
7775         i++;\r
7776     }\r
7777     \r
7778     /* Start a new line */\r
7779     if (linelen > 0) fprintf(f, "\n");\r
7780 \r
7781     /* Print comments after last move */\r
7782     if (commentList[i] != NULL) {\r
7783         fprintf(f, "{\n%s}\n", commentList[i]);\r
7784     }\r
7785 \r
7786     /* Print result */\r
7787     if (gameInfo.resultDetails != NULL &&\r
7788         gameInfo.resultDetails[0] != NULLCHAR) {\r
7789         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
7790                 PGNResult(gameInfo.result));\r
7791     } else {\r
7792         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
7793     }\r
7794 \r
7795     fclose(f);\r
7796     return TRUE;\r
7797 }\r
7798 \r
7799 /* Save game in old style and close the file */\r
7800 int\r
7801 SaveGameOldStyle(f)\r
7802      FILE *f;\r
7803 {\r
7804     int i, offset;\r
7805     time_t tm;\r
7806     \r
7807     tm = time((time_t *) NULL);\r
7808     \r
7809     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
7810     PrintOpponents(f);\r
7811     \r
7812     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
7813         fprintf(f, "\n[--------------\n");\r
7814         PrintPosition(f, backwardMostMove);\r
7815         fprintf(f, "--------------]\n");\r
7816     } else {\r
7817         fprintf(f, "\n");\r
7818     }\r
7819 \r
7820     i = backwardMostMove;\r
7821     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
7822 \r
7823     while (i < forwardMostMove) {\r
7824         if (commentList[i] != NULL) {\r
7825             fprintf(f, "[%s]\n", commentList[i]);\r
7826         }\r
7827 \r
7828         if ((i % 2) == 1) {\r
7829             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
7830             i++;\r
7831         } else {\r
7832             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
7833             i++;\r
7834             if (commentList[i] != NULL) {\r
7835                 fprintf(f, "\n");\r
7836                 continue;\r
7837             }\r
7838             if (i >= forwardMostMove) {\r
7839                 fprintf(f, "\n");\r
7840                 break;\r
7841             }\r
7842             fprintf(f, "%s\n", parseList[i]);\r
7843             i++;\r
7844         }\r
7845     }\r
7846     \r
7847     if (commentList[i] != NULL) {\r
7848         fprintf(f, "[%s]\n", commentList[i]);\r
7849     }\r
7850 \r
7851     /* This isn't really the old style, but it's close enough */\r
7852     if (gameInfo.resultDetails != NULL &&\r
7853         gameInfo.resultDetails[0] != NULLCHAR) {\r
7854         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
7855                 gameInfo.resultDetails);\r
7856     } else {\r
7857         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
7858     }\r
7859 \r
7860     fclose(f);\r
7861     return TRUE;\r
7862 }\r
7863 \r
7864 /* Save the current game to open file f and close the file */\r
7865 int\r
7866 SaveGame(f, dummy, dummy2)\r
7867      FILE *f;\r
7868      int dummy;\r
7869      char *dummy2;\r
7870 {\r
7871     if (gameMode == EditPosition) EditPositionDone();\r
7872     if (appData.oldSaveStyle)\r
7873       return SaveGameOldStyle(f);\r
7874     else\r
7875       return SaveGamePGN(f);\r
7876 }\r
7877 \r
7878 /* Save the current position to the given file */\r
7879 int\r
7880 SavePositionToFile(filename)\r
7881      char *filename;\r
7882 {\r
7883     FILE *f;\r
7884     char buf[MSG_SIZ];\r
7885 \r
7886     if (strcmp(filename, "-") == 0) {\r
7887         return SavePosition(stdout, 0, NULL);\r
7888     } else {\r
7889         f = fopen(filename, "a");\r
7890         if (f == NULL) {\r
7891             sprintf(buf, "Can't open \"%s\"", filename);\r
7892             DisplayError(buf, errno);\r
7893             return FALSE;\r
7894         } else {\r
7895             SavePosition(f, 0, NULL);\r
7896             return TRUE;\r
7897         }\r
7898     }\r
7899 }\r
7900 \r
7901 /* Save the current position to the given open file and close the file */\r
7902 int\r
7903 SavePosition(f, dummy, dummy2)\r
7904      FILE *f;\r
7905      int dummy;\r
7906      char *dummy2;\r
7907 {\r
7908     time_t tm;\r
7909     char *fen;\r
7910     \r
7911     if (appData.oldSaveStyle) {\r
7912         tm = time((time_t *) NULL);\r
7913     \r
7914         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
7915         PrintOpponents(f);\r
7916         fprintf(f, "[--------------\n");\r
7917         PrintPosition(f, currentMove);\r
7918         fprintf(f, "--------------]\n");\r
7919     } else {\r
7920         fen = PositionToFEN(currentMove, 1);\r
7921         fprintf(f, "%s\n", fen);\r
7922         free(fen);\r
7923     }\r
7924     fclose(f);\r
7925     return TRUE;\r
7926 }\r
7927 \r
7928 void\r
7929 ReloadCmailMsgEvent(unregister)\r
7930      int unregister;\r
7931 {\r
7932 #if !WIN32\r
7933     static char *inFilename = NULL;\r
7934     static char *outFilename;\r
7935     int i;\r
7936     struct stat inbuf, outbuf;\r
7937     int status;\r
7938     \r
7939     /* Any registered moves are unregistered if unregister is set, */\r
7940     /* i.e. invoked by the signal handler */\r
7941     if (unregister) {\r
7942         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
7943             cmailMoveRegistered[i] = FALSE;\r
7944             if (cmailCommentList[i] != NULL) {\r
7945                 free(cmailCommentList[i]);\r
7946                 cmailCommentList[i] = NULL;\r
7947             }\r
7948         }\r
7949         nCmailMovesRegistered = 0;\r
7950     }\r
7951 \r
7952     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
7953         cmailResult[i] = CMAIL_NOT_RESULT;\r
7954     }\r
7955     nCmailResults = 0;\r
7956 \r
7957     if (inFilename == NULL) {\r
7958         /* Because the filenames are static they only get malloced once  */\r
7959         /* and they never get freed                                      */\r
7960         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
7961         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
7962 \r
7963         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
7964         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
7965     }\r
7966     \r
7967     status = stat(outFilename, &outbuf);\r
7968     if (status < 0) {\r
7969         cmailMailedMove = FALSE;\r
7970     } else {\r
7971         status = stat(inFilename, &inbuf);\r
7972         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
7973     }\r
7974     \r
7975     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
7976        counts the games, notes how each one terminated, etc.\r
7977        \r
7978        It would be nice to remove this kludge and instead gather all\r
7979        the information while building the game list.  (And to keep it\r
7980        in the game list nodes instead of having a bunch of fixed-size\r
7981        parallel arrays.)  Note this will require getting each game's\r
7982        termination from the PGN tags, as the game list builder does\r
7983        not process the game moves.  --mann\r
7984        */\r
7985     cmailMsgLoaded = TRUE;\r
7986     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
7987     \r
7988     /* Load first game in the file or popup game menu */\r
7989     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
7990 \r
7991 #endif /* !WIN32 */\r
7992     return;\r
7993 }\r
7994 \r
7995 int\r
7996 RegisterMove()\r
7997 {\r
7998     FILE *f;\r
7999     char string[MSG_SIZ];\r
8000 \r
8001     if (   cmailMailedMove\r
8002         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
8003         return TRUE;            /* Allow free viewing  */\r
8004     }\r
8005 \r
8006     /* Unregister move to ensure that we don't leave RegisterMove        */\r
8007     /* with the move registered when the conditions for registering no   */\r
8008     /* longer hold                                                       */\r
8009     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8010         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8011         nCmailMovesRegistered --;\r
8012 \r
8013         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
8014           {\r
8015               free(cmailCommentList[lastLoadGameNumber - 1]);\r
8016               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
8017           }\r
8018     }\r
8019 \r
8020     if (cmailOldMove == -1) {\r
8021         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
8022         return FALSE;\r
8023     }\r
8024 \r
8025     if (currentMove > cmailOldMove + 1) {\r
8026         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
8027         return FALSE;\r
8028     }\r
8029 \r
8030     if (currentMove < cmailOldMove) {\r
8031         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
8032         return FALSE;\r
8033     }\r
8034 \r
8035     if (forwardMostMove > currentMove) {\r
8036         /* Silently truncate extra moves */\r
8037         TruncateGame();\r
8038     }\r
8039 \r
8040     if (   (currentMove == cmailOldMove + 1)\r
8041         || (   (currentMove == cmailOldMove)\r
8042             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
8043                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
8044         if (gameInfo.result != GameUnfinished) {\r
8045             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
8046         }\r
8047 \r
8048         if (commentList[currentMove] != NULL) {\r
8049             cmailCommentList[lastLoadGameNumber - 1]\r
8050               = StrSave(commentList[currentMove]);\r
8051         }\r
8052         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
8053 \r
8054         if (appData.debugMode)\r
8055           fprintf(debugFP, "Saving %s for game %d\n",\r
8056                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8057 \r
8058         sprintf(string,\r
8059                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
8060         \r
8061         f = fopen(string, "w");\r
8062         if (appData.oldSaveStyle) {\r
8063             SaveGameOldStyle(f); /* also closes the file */\r
8064             \r
8065             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
8066             f = fopen(string, "w");\r
8067             SavePosition(f, 0, NULL); /* also closes the file */\r
8068         } else {\r
8069             fprintf(f, "{--------------\n");\r
8070             PrintPosition(f, currentMove);\r
8071             fprintf(f, "--------------}\n\n");\r
8072             \r
8073             SaveGame(f, 0, NULL); /* also closes the file*/\r
8074         }\r
8075         \r
8076         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
8077         nCmailMovesRegistered ++;\r
8078     } else if (nCmailGames == 1) {\r
8079         DisplayError("You have not made a move yet", 0);\r
8080         return FALSE;\r
8081     }\r
8082 \r
8083     return TRUE;\r
8084 }\r
8085 \r
8086 void\r
8087 MailMoveEvent()\r
8088 {\r
8089 #if !WIN32\r
8090     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
8091     FILE *commandOutput;\r
8092     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
8093     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
8094     int nBuffers;\r
8095     int i;\r
8096     int archived;\r
8097     char *arcDir;\r
8098 \r
8099     if (! cmailMsgLoaded) {\r
8100         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
8101         return;\r
8102     }\r
8103 \r
8104     if (nCmailGames == nCmailResults) {\r
8105         DisplayError("No unfinished games", 0);\r
8106         return;\r
8107     }\r
8108 \r
8109 #if CMAIL_PROHIBIT_REMAIL\r
8110     if (cmailMailedMove) {\r
8111         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);\r
8112         DisplayError(msg, 0);\r
8113         return;\r
8114     }\r
8115 #endif\r
8116 \r
8117     if (! (cmailMailedMove || RegisterMove())) return;\r
8118     \r
8119     if (   cmailMailedMove\r
8120         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
8121         sprintf(string, partCommandString,\r
8122                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
8123         commandOutput = popen(string, "rb");\r
8124 \r
8125         if (commandOutput == NULL) {\r
8126             DisplayError("Failed to invoke cmail", 0);\r
8127         } else {\r
8128             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
8129                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
8130             }\r
8131             if (nBuffers > 1) {\r
8132                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
8133                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
8134                 nBytes = MSG_SIZ - 1;\r
8135             } else {\r
8136                 (void) memcpy(msg, buffer, nBytes);\r
8137             }\r
8138             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
8139 \r
8140             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
8141                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
8142 \r
8143                 archived = TRUE;\r
8144                 for (i = 0; i < nCmailGames; i ++) {\r
8145                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
8146                         archived = FALSE;\r
8147                     }\r
8148                 }\r
8149                 if (   archived\r
8150                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
8151                         != NULL)) {\r
8152                     sprintf(buffer, "%s/%s.%s.archive",\r
8153                             arcDir,\r
8154                             appData.cmailGameName,\r
8155                             gameInfo.date);\r
8156                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
8157                     cmailMsgLoaded = FALSE;\r
8158                 }\r
8159             }\r
8160 \r
8161             DisplayInformation(msg);\r
8162             pclose(commandOutput);\r
8163         }\r
8164     } else {\r
8165         if ((*cmailMsg) != '\0') {\r
8166             DisplayInformation(cmailMsg);\r
8167         }\r
8168     }\r
8169 \r
8170     return;\r
8171 #endif /* !WIN32 */\r
8172 }\r
8173 \r
8174 char *\r
8175 CmailMsg()\r
8176 {\r
8177 #if WIN32\r
8178     return NULL;\r
8179 #else\r
8180     int  prependComma = 0;\r
8181     char number[5];\r
8182     char string[MSG_SIZ];       /* Space for game-list */\r
8183     int  i;\r
8184     \r
8185     if (!cmailMsgLoaded) return "";\r
8186 \r
8187     if (cmailMailedMove) {\r
8188         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
8189     } else {\r
8190         /* Create a list of games left */\r
8191         sprintf(string, "[");\r
8192         for (i = 0; i < nCmailGames; i ++) {\r
8193             if (! (   cmailMoveRegistered[i]\r
8194                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
8195                 if (prependComma) {\r
8196                     sprintf(number, ",%d", i + 1);\r
8197                 } else {\r
8198                     sprintf(number, "%d", i + 1);\r
8199                     prependComma = 1;\r
8200                 }\r
8201                 \r
8202                 strcat(string, number);\r
8203             }\r
8204         }\r
8205         strcat(string, "]");\r
8206 \r
8207         if (nCmailMovesRegistered + nCmailResults == 0) {\r
8208             switch (nCmailGames) {\r
8209               case 1:\r
8210                 sprintf(cmailMsg,\r
8211                         "Still need to make move for game\n");\r
8212                 break;\r
8213                 \r
8214               case 2:\r
8215                 sprintf(cmailMsg,\r
8216                         "Still need to make moves for both games\n");\r
8217                 break;\r
8218                 \r
8219               default:\r
8220                 sprintf(cmailMsg,\r
8221                         "Still need to make moves for all %d games\n",\r
8222                         nCmailGames);\r
8223                 break;\r
8224             }\r
8225         } else {\r
8226             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
8227               case 1:\r
8228                 sprintf(cmailMsg,\r
8229                         "Still need to make a move for game %s\n",\r
8230                         string);\r
8231                 break;\r
8232                 \r
8233               case 0:\r
8234                 if (nCmailResults == nCmailGames) {\r
8235                     sprintf(cmailMsg, "No unfinished games\n");\r
8236                 } else {\r
8237                     sprintf(cmailMsg, "Ready to send mail\n");\r
8238                 }\r
8239                 break;\r
8240                 \r
8241               default:\r
8242                 sprintf(cmailMsg,\r
8243                         "Still need to make moves for games %s\n",\r
8244                         string);\r
8245             }\r
8246         }\r
8247     }\r
8248     return cmailMsg;\r
8249 #endif /* WIN32 */\r
8250 }\r
8251 \r
8252 void\r
8253 ResetGameEvent()\r
8254 {\r
8255     if (gameMode == Training)\r
8256       SetTrainingModeOff();\r
8257 \r
8258     Reset(TRUE, TRUE);\r
8259     cmailMsgLoaded = FALSE;\r
8260     if (appData.icsActive) {\r
8261       SendToICS(ics_prefix);\r
8262       SendToICS("refresh\n");\r
8263     }\r
8264 }\r
8265 \r
8266 static int exiting = 0;\r
8267 \r
8268 void\r
8269 ExitEvent(status)\r
8270      int status;\r
8271 {\r
8272     exiting++;\r
8273     if (exiting > 2) {\r
8274       /* Give up on clean exit */\r
8275       exit(status);\r
8276     }\r
8277     if (exiting > 1) {\r
8278       /* Keep trying for clean exit */\r
8279       return;\r
8280     }\r
8281 \r
8282     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
8283 \r
8284     if (telnetISR != NULL) {\r
8285       RemoveInputSource(telnetISR);\r
8286     }\r
8287     if (icsPR != NoProc) {\r
8288       DestroyChildProcess(icsPR, TRUE);\r
8289     }\r
8290     /* Save game if resource set and not already saved by GameEnds() */\r
8291     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {\r
8292       if (*appData.saveGameFile != NULLCHAR) {\r
8293         SaveGameToFile(appData.saveGameFile, TRUE);\r
8294       } else if (appData.autoSaveGames) {\r
8295         AutoSaveGame();\r
8296       }\r
8297       if (*appData.savePositionFile != NULLCHAR) {\r
8298         SavePositionToFile(appData.savePositionFile);\r
8299       }\r
8300     }\r
8301     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8302 \r
8303     /* Kill off chess programs */\r
8304     if (first.pr != NoProc) {\r
8305         ExitAnalyzeMode();\r
8306         \r
8307         DoSleep( appData.delayBeforeQuit );\r
8308         SendToProgram("quit\n", &first);\r
8309         DoSleep( appData.delayAfterQuit );\r
8310         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
8311     }\r
8312     if (second.pr != NoProc) {\r
8313         DoSleep( appData.delayBeforeQuit );\r
8314         SendToProgram("quit\n", &second);\r
8315         DoSleep( appData.delayAfterQuit );\r
8316         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
8317     }\r
8318     if (first.isr != NULL) {\r
8319         RemoveInputSource(first.isr);\r
8320     }\r
8321     if (second.isr != NULL) {\r
8322         RemoveInputSource(second.isr);\r
8323     }\r
8324 \r
8325     ShutDownFrontEnd();\r
8326     exit(status);\r
8327 }\r
8328 \r
8329 void\r
8330 PauseEvent()\r
8331 {\r
8332     if (appData.debugMode)\r
8333         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
8334     if (pausing) {\r
8335         pausing = FALSE;\r
8336         ModeHighlight();\r
8337         if (gameMode == MachinePlaysWhite ||\r
8338             gameMode == MachinePlaysBlack) {\r
8339             StartClocks();\r
8340         } else {\r
8341             DisplayBothClocks();\r
8342         }\r
8343         if (gameMode == PlayFromGameFile) {\r
8344             if (appData.timeDelay >= 0) \r
8345                 AutoPlayGameLoop();\r
8346         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
8347             Reset(FALSE, TRUE);\r
8348             SendToICS(ics_prefix);\r
8349             SendToICS("refresh\n");\r
8350         } else if (currentMove < forwardMostMove) {\r
8351             ForwardInner(forwardMostMove);\r
8352         }\r
8353         pauseExamInvalid = FALSE;\r
8354     } else {\r
8355         switch (gameMode) {\r
8356           default:\r
8357             return;\r
8358           case IcsExamining:\r
8359             pauseExamForwardMostMove = forwardMostMove;\r
8360             pauseExamInvalid = FALSE;\r
8361             /* fall through */\r
8362           case IcsObserving:\r
8363           case IcsPlayingWhite:\r
8364           case IcsPlayingBlack:\r
8365             pausing = TRUE;\r
8366             ModeHighlight();\r
8367             return;\r
8368           case PlayFromGameFile:\r
8369             (void) StopLoadGameTimer();\r
8370             pausing = TRUE;\r
8371             ModeHighlight();\r
8372             break;\r
8373           case BeginningOfGame:\r
8374             if (appData.icsActive) return;\r
8375             /* else fall through */\r
8376           case MachinePlaysWhite:\r
8377           case MachinePlaysBlack:\r
8378           case TwoMachinesPlay:\r
8379             if (forwardMostMove == 0)\r
8380               return;           /* don't pause if no one has moved */\r
8381             if ((gameMode == MachinePlaysWhite &&\r
8382                  !WhiteOnMove(forwardMostMove)) ||\r
8383                 (gameMode == MachinePlaysBlack &&\r
8384                  WhiteOnMove(forwardMostMove))) {\r
8385                 StopClocks();\r
8386             }\r
8387             pausing = TRUE;\r
8388             ModeHighlight();\r
8389             break;\r
8390         }\r
8391     }\r
8392 }\r
8393 \r
8394 void\r
8395 EditCommentEvent()\r
8396 {\r
8397     char title[MSG_SIZ];\r
8398 \r
8399     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
8400         strcpy(title, "Edit comment");\r
8401     } else {\r
8402         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
8403                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
8404                 parseList[currentMove - 1]);\r
8405     }\r
8406 \r
8407     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
8408 }\r
8409 \r
8410 \r
8411 void\r
8412 EditTagsEvent()\r
8413 {\r
8414     char *tags = PGNTags(&gameInfo);\r
8415     EditTagsPopUp(tags);\r
8416     free(tags);\r
8417 }\r
8418 \r
8419 void\r
8420 AnalyzeModeEvent()\r
8421 {\r
8422     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
8423       return;\r
8424 \r
8425     if (gameMode != AnalyzeFile) {\r
8426         EditGameEvent();\r
8427         if (gameMode != EditGame) return;\r
8428         ResurrectChessProgram();\r
8429         SendToProgram("analyze\n", &first);\r
8430         first.analyzing = TRUE;\r
8431         /*first.maybeThinking = TRUE;*/\r
8432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8433         AnalysisPopUp("Analysis",\r
8434                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8435     }\r
8436     gameMode = AnalyzeMode;\r
8437     pausing = FALSE;\r
8438     ModeHighlight();\r
8439     SetGameInfo();\r
8440 \r
8441     StartAnalysisClock();\r
8442     GetTimeMark(&lastNodeCountTime);\r
8443     lastNodeCount = 0;\r
8444 }\r
8445 \r
8446 void\r
8447 AnalyzeFileEvent()\r
8448 {\r
8449     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
8450       return;\r
8451 \r
8452     if (gameMode != AnalyzeMode) {\r
8453         EditGameEvent();\r
8454         if (gameMode != EditGame) return;\r
8455         ResurrectChessProgram();\r
8456         SendToProgram("analyze\n", &first);\r
8457         first.analyzing = TRUE;\r
8458         /*first.maybeThinking = TRUE;*/\r
8459         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8460         AnalysisPopUp("Analysis",\r
8461                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8462     }\r
8463     gameMode = AnalyzeFile;\r
8464     pausing = FALSE;\r
8465     ModeHighlight();\r
8466     SetGameInfo();\r
8467 \r
8468     StartAnalysisClock();\r
8469     GetTimeMark(&lastNodeCountTime);\r
8470     lastNodeCount = 0;\r
8471 }\r
8472 \r
8473 void\r
8474 MachineWhiteEvent()\r
8475 {\r
8476     char buf[MSG_SIZ];\r
8477 \r
8478     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
8479       return;\r
8480 \r
8481 \r
8482     if (gameMode == PlayFromGameFile || \r
8483         gameMode == TwoMachinesPlay  || \r
8484         gameMode == Training         || \r
8485         gameMode == AnalyzeMode      || \r
8486         gameMode == EndOfGame)\r
8487         EditGameEvent();\r
8488 \r
8489     if (gameMode == EditPosition) \r
8490         EditPositionDone();\r
8491 \r
8492     if (!WhiteOnMove(currentMove)) {\r
8493         DisplayError("It is not White's turn", 0);\r
8494         return;\r
8495     }\r
8496   \r
8497     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
8498       ExitAnalyzeMode();\r
8499 \r
8500     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
8501         gameMode == AnalyzeFile)\r
8502         TruncateGame();\r
8503 \r
8504     ResurrectChessProgram();    /* in case it isn't running */\r
8505     gameMode = MachinePlaysWhite;\r
8506     pausing = FALSE;\r
8507     ModeHighlight();\r
8508     SetGameInfo();\r
8509     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8510     DisplayTitle(buf);\r
8511     if (first.sendName) {\r
8512       sprintf(buf, "name %s\n", gameInfo.black);\r
8513       SendToProgram(buf, &first);\r
8514     }\r
8515     if (first.sendTime) {\r
8516       if (first.useColors) {\r
8517         SendToProgram("black\n", &first); /*gnu kludge*/\r
8518       }\r
8519       SendTimeRemaining(&first, TRUE);\r
8520     }\r
8521     if (first.useColors) {\r
8522       SendToProgram("white\ngo\n", &first);\r
8523     } else {\r
8524       SendToProgram("go\n", &first);\r
8525     }\r
8526     SetMachineThinkingEnables();\r
8527     first.maybeThinking = TRUE;\r
8528     StartClocks();\r
8529 \r
8530     if (appData.autoFlipView && !flipView) {\r
8531       flipView = !flipView;\r
8532       DrawPosition(FALSE, NULL);\r
8533     }\r
8534 }\r
8535 \r
8536 void\r
8537 MachineBlackEvent()\r
8538 {\r
8539     char buf[MSG_SIZ];\r
8540 \r
8541     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
8542         return;\r
8543 \r
8544 \r
8545     if (gameMode == PlayFromGameFile || \r
8546         gameMode == TwoMachinesPlay  || \r
8547         gameMode == Training         || \r
8548         gameMode == AnalyzeMode      || \r
8549         gameMode == EndOfGame)\r
8550         EditGameEvent();\r
8551 \r
8552     if (gameMode == EditPosition) \r
8553         EditPositionDone();\r
8554 \r
8555     if (WhiteOnMove(currentMove)) {\r
8556         DisplayError("It is not Black's turn", 0);\r
8557         return;\r
8558     }\r
8559     \r
8560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
8561       ExitAnalyzeMode();\r
8562 \r
8563     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
8564         gameMode == AnalyzeFile)\r
8565         TruncateGame();\r
8566 \r
8567     ResurrectChessProgram();    /* in case it isn't running */\r
8568     gameMode = MachinePlaysBlack;\r
8569     pausing = FALSE;\r
8570     ModeHighlight();\r
8571     SetGameInfo();\r
8572     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8573     DisplayTitle(buf);\r
8574     if (first.sendName) {\r
8575       sprintf(buf, "name %s\n", gameInfo.white);\r
8576       SendToProgram(buf, &first);\r
8577     }\r
8578     if (first.sendTime) {\r
8579       if (first.useColors) {\r
8580         SendToProgram("white\n", &first); /*gnu kludge*/\r
8581       }\r
8582       SendTimeRemaining(&first, FALSE);\r
8583     }\r
8584     if (first.useColors) {\r
8585       SendToProgram("black\ngo\n", &first);\r
8586     } else {\r
8587       SendToProgram("go\n", &first);\r
8588     }\r
8589     SetMachineThinkingEnables();\r
8590     first.maybeThinking = TRUE;\r
8591     StartClocks();\r
8592 \r
8593     if (appData.autoFlipView && flipView) {\r
8594       flipView = !flipView;\r
8595       DrawPosition(FALSE, NULL);\r
8596     }\r
8597 }\r
8598 \r
8599 \r
8600 void\r
8601 DisplayTwoMachinesTitle()\r
8602 {\r
8603     char buf[MSG_SIZ];\r
8604     if (appData.matchGames > 0) {\r
8605         if (first.twoMachinesColor[0] == 'w') {\r
8606             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
8607                     gameInfo.white, gameInfo.black,\r
8608                     first.matchWins, second.matchWins,\r
8609                     matchGame - 1 - (first.matchWins + second.matchWins));\r
8610         } else {\r
8611             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
8612                     gameInfo.white, gameInfo.black,\r
8613                     second.matchWins, first.matchWins,\r
8614                     matchGame - 1 - (first.matchWins + second.matchWins));\r
8615         }\r
8616     } else {\r
8617         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8618     }\r
8619     DisplayTitle(buf);\r
8620 }\r
8621 \r
8622 void\r
8623 TwoMachinesEvent P((void))\r
8624 {\r
8625     int i;\r
8626     char buf[MSG_SIZ];\r
8627     ChessProgramState *onmove;\r
8628     \r
8629     if (appData.noChessProgram) return;\r
8630 \r
8631     switch (gameMode) {\r
8632       case TwoMachinesPlay:\r
8633         return;\r
8634       case MachinePlaysWhite:\r
8635       case MachinePlaysBlack:\r
8636         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
8637             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
8638             return;\r
8639         }\r
8640         /* fall through */\r
8641       case BeginningOfGame:\r
8642       case PlayFromGameFile:\r
8643       case EndOfGame:\r
8644         EditGameEvent();\r
8645         if (gameMode != EditGame) return;\r
8646         break;\r
8647       case EditPosition:\r
8648         EditPositionDone();\r
8649         break;\r
8650       case AnalyzeMode:\r
8651       case AnalyzeFile:\r
8652         ExitAnalyzeMode();\r
8653         break;\r
8654       case EditGame:\r
8655       default:\r
8656         break;\r
8657     }\r
8658 \r
8659     forwardMostMove = currentMove;\r
8660     ResurrectChessProgram();    /* in case first program isn't running */\r
8661 \r
8662     if (second.pr == NULL) {\r
8663         StartChessProgram(&second);\r
8664         if (second.protocolVersion == 1) {\r
8665           TwoMachinesEventIfReady();\r
8666         } else {\r
8667           /* kludge: allow timeout for initial "feature" command */\r
8668           FreezeUI();\r
8669           DisplayMessage("", "Starting second chess program");\r
8670           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
8671         }\r
8672         return;\r
8673     }\r
8674     DisplayMessage("", "");\r
8675     InitChessProgram(&second);\r
8676     SendToProgram("force\n", &second);\r
8677     if (startedFromSetupPosition) {\r
8678         SendBoard(&second, backwardMostMove);\r
8679     }\r
8680     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
8681         SendMoveToProgram(i, &second);\r
8682     }\r
8683 \r
8684     gameMode = TwoMachinesPlay;\r
8685     pausing = FALSE;\r
8686     ModeHighlight();\r
8687     SetGameInfo();\r
8688     DisplayTwoMachinesTitle();\r
8689     firstMove = TRUE;\r
8690     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
8691         onmove = &first;\r
8692     } else {\r
8693         onmove = &second;\r
8694     }\r
8695 \r
8696     SendToProgram(first.computerString, &first);\r
8697     if (first.sendName) {\r
8698       sprintf(buf, "name %s\n", second.tidy);\r
8699       SendToProgram(buf, &first);\r
8700     }\r
8701     SendToProgram(second.computerString, &second);\r
8702     if (second.sendName) {\r
8703       sprintf(buf, "name %s\n", first.tidy);\r
8704       SendToProgram(buf, &second);\r
8705     }\r
8706 \r
8707     if (!first.sendTime || !second.sendTime) {\r
8708         ResetClocks();\r
8709         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
8710         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
8711     }\r
8712     if (onmove->sendTime) {\r
8713       if (onmove->useColors) {\r
8714         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
8715       }\r
8716       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
8717     }\r
8718     if (onmove->useColors) {\r
8719       SendToProgram(onmove->twoMachinesColor, onmove);\r
8720     }\r
8721     SendToProgram("go\n", onmove);\r
8722     onmove->maybeThinking = TRUE;\r
8723     SetMachineThinkingEnables();\r
8724 \r
8725     StartClocks();\r
8726 }\r
8727 \r
8728 void\r
8729 TrainingEvent()\r
8730 {\r
8731     if (gameMode == Training) {\r
8732       SetTrainingModeOff();\r
8733       gameMode = PlayFromGameFile;\r
8734       DisplayMessage("", "Training mode off");\r
8735     } else {\r
8736       gameMode = Training;\r
8737       animateTraining = appData.animate;\r
8738 \r
8739       /* make sure we are not already at the end of the game */\r
8740       if (currentMove < forwardMostMove) {\r
8741         SetTrainingModeOn();\r
8742         DisplayMessage("", "Training mode on");\r
8743       } else {\r
8744         gameMode = PlayFromGameFile;\r
8745         DisplayError("Already at end of game", 0);\r
8746       }\r
8747     }\r
8748     ModeHighlight();\r
8749 }\r
8750 \r
8751 void\r
8752 IcsClientEvent()\r
8753 {\r
8754     if (!appData.icsActive) return;\r
8755     switch (gameMode) {\r
8756       case IcsPlayingWhite:\r
8757       case IcsPlayingBlack:\r
8758       case IcsObserving:\r
8759       case IcsIdle:\r
8760       case BeginningOfGame:\r
8761       case IcsExamining:\r
8762         return;\r
8763 \r
8764       case EditGame:\r
8765         break;\r
8766 \r
8767       case EditPosition:\r
8768         EditPositionDone();\r
8769         break;\r
8770 \r
8771       case AnalyzeMode:\r
8772       case AnalyzeFile:\r
8773         ExitAnalyzeMode();\r
8774         break;\r
8775         \r
8776       default:\r
8777         EditGameEvent();\r
8778         break;\r
8779     }\r
8780 \r
8781     gameMode = IcsIdle;\r
8782     ModeHighlight();\r
8783     return;\r
8784 }\r
8785 \r
8786 \r
8787 void\r
8788 EditGameEvent()\r
8789 {\r
8790     int i;\r
8791 \r
8792     switch (gameMode) {\r
8793       case Training:\r
8794         SetTrainingModeOff();\r
8795         break;\r
8796       case MachinePlaysWhite:\r
8797       case MachinePlaysBlack:\r
8798       case BeginningOfGame:\r
8799         SendToProgram("force\n", &first);\r
8800         SetUserThinkingEnables();\r
8801         break;\r
8802       case PlayFromGameFile:\r
8803         (void) StopLoadGameTimer();\r
8804         if (gameFileFP != NULL) {\r
8805             gameFileFP = NULL;\r
8806         }\r
8807         break;\r
8808       case EditPosition:\r
8809         EditPositionDone();\r
8810         break;\r
8811       case AnalyzeMode:\r
8812       case AnalyzeFile:\r
8813         ExitAnalyzeMode();\r
8814         SendToProgram("force\n", &first);\r
8815         break;\r
8816       case TwoMachinesPlay:\r
8817         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8818         ResurrectChessProgram();\r
8819         SetUserThinkingEnables();\r
8820         break;\r
8821       case EndOfGame:\r
8822         ResurrectChessProgram();\r
8823         break;\r
8824       case IcsPlayingBlack:\r
8825       case IcsPlayingWhite:\r
8826         DisplayError("Warning: You are still playing a game", 0);\r
8827         break;\r
8828       case IcsObserving:\r
8829         DisplayError("Warning: You are still observing a game", 0);\r
8830         break;\r
8831       case IcsExamining:\r
8832         DisplayError("Warning: You are still examining a game", 0);\r
8833         break;\r
8834       case IcsIdle:\r
8835         break;\r
8836       case EditGame:\r
8837       default:\r
8838         return;\r
8839     }\r
8840     \r
8841     pausing = FALSE;\r
8842     StopClocks();\r
8843     first.offeredDraw = second.offeredDraw = 0;\r
8844 \r
8845     if (gameMode == PlayFromGameFile) {\r
8846         whiteTimeRemaining = timeRemaining[0][currentMove];\r
8847         blackTimeRemaining = timeRemaining[1][currentMove];\r
8848         DisplayTitle("");\r
8849     }\r
8850 \r
8851     if (gameMode == MachinePlaysWhite ||\r
8852         gameMode == MachinePlaysBlack ||\r
8853         gameMode == TwoMachinesPlay ||\r
8854         gameMode == EndOfGame) {\r
8855         i = forwardMostMove;\r
8856         while (i > currentMove) {\r
8857             SendToProgram("undo\n", &first);\r
8858             i--;\r
8859         }\r
8860         whiteTimeRemaining = timeRemaining[0][currentMove];\r
8861         blackTimeRemaining = timeRemaining[1][currentMove];\r
8862         DisplayBothClocks();\r
8863         if (whiteFlag || blackFlag) {\r
8864             whiteFlag = blackFlag = 0;\r
8865         }\r
8866         DisplayTitle("");\r
8867     }           \r
8868     \r
8869     gameMode = EditGame;\r
8870     ModeHighlight();\r
8871     SetGameInfo();\r
8872 }\r
8873 \r
8874 \r
8875 void\r
8876 EditPositionEvent()\r
8877 {\r
8878     if (gameMode == EditPosition) {\r
8879         EditGameEvent();\r
8880         return;\r
8881     }\r
8882     \r
8883     EditGameEvent();\r
8884     if (gameMode != EditGame) return;\r
8885     \r
8886     gameMode = EditPosition;\r
8887     ModeHighlight();\r
8888     SetGameInfo();\r
8889     if (currentMove > 0)\r
8890       CopyBoard(boards[0], boards[currentMove]);\r
8891     \r
8892     blackPlaysFirst = !WhiteOnMove(currentMove);\r
8893     ResetClocks();\r
8894     currentMove = forwardMostMove = backwardMostMove = 0;\r
8895     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8896     DisplayMove(-1);\r
8897 }\r
8898 \r
8899 void\r
8900 ExitAnalyzeMode()\r
8901 {\r
8902     if (first.analysisSupport && first.analyzing) {\r
8903       SendToProgram("exit\n", &first);\r
8904       first.analyzing = FALSE;\r
8905     }\r
8906     AnalysisPopDown();\r
8907     thinkOutput[0] = NULLCHAR;\r
8908 }\r
8909 \r
8910 void\r
8911 EditPositionDone()\r
8912 {\r
8913     startedFromSetupPosition = TRUE;\r
8914     InitChessProgram(&first);\r
8915     SendToProgram("force\n", &first);\r
8916     if (blackPlaysFirst) {\r
8917         strcpy(moveList[0], "");\r
8918         strcpy(parseList[0], "");\r
8919         currentMove = forwardMostMove = backwardMostMove = 1;\r
8920         CopyBoard(boards[1], boards[0]);\r
8921     } else {\r
8922         currentMove = forwardMostMove = backwardMostMove = 0;\r
8923     }\r
8924     SendBoard(&first, forwardMostMove);\r
8925     DisplayTitle("");\r
8926     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
8927     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
8928     gameMode = EditGame;\r
8929     ModeHighlight();\r
8930     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8931     ClearHighlights(); /* [AS] */\r
8932 }\r
8933 \r
8934 /* Pause for `ms' milliseconds */\r
8935 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
8936 void\r
8937 TimeDelay(ms)\r
8938      long ms;\r
8939 {\r
8940     TimeMark m1, m2;\r
8941 \r
8942     GetTimeMark(&m1);\r
8943     do {\r
8944         GetTimeMark(&m2);\r
8945     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
8946 }\r
8947 \r
8948 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
8949 void\r
8950 SendMultiLineToICS(buf)\r
8951      char *buf;\r
8952 {\r
8953     char temp[MSG_SIZ+1], *p;\r
8954     int len;\r
8955 \r
8956     len = strlen(buf);\r
8957     if (len > MSG_SIZ)\r
8958       len = MSG_SIZ;\r
8959   \r
8960     strncpy(temp, buf, len);\r
8961     temp[len] = 0;\r
8962 \r
8963     p = temp;\r
8964     while (*p) {\r
8965         if (*p == '\n' || *p == '\r')\r
8966           *p = ' ';\r
8967         ++p;\r
8968     }\r
8969 \r
8970     strcat(temp, "\n");\r
8971     SendToICS(temp);\r
8972     SendToPlayer(temp, strlen(temp));\r
8973 }\r
8974 \r
8975 void\r
8976 SetWhiteToPlayEvent()\r
8977 {\r
8978     if (gameMode == EditPosition) {\r
8979         blackPlaysFirst = FALSE;\r
8980         DisplayBothClocks();    /* works because currentMove is 0 */\r
8981     } else if (gameMode == IcsExamining) {\r
8982         SendToICS(ics_prefix);\r
8983         SendToICS("tomove white\n");\r
8984     }\r
8985 }\r
8986 \r
8987 void\r
8988 SetBlackToPlayEvent()\r
8989 {\r
8990     if (gameMode == EditPosition) {\r
8991         blackPlaysFirst = TRUE;\r
8992         currentMove = 1;        /* kludge */\r
8993         DisplayBothClocks();\r
8994         currentMove = 0;\r
8995     } else if (gameMode == IcsExamining) {\r
8996         SendToICS(ics_prefix);\r
8997         SendToICS("tomove black\n");\r
8998     }\r
8999 }\r
9000 \r
9001 void\r
9002 EditPositionMenuEvent(selection, x, y)\r
9003      ChessSquare selection;\r
9004      int x, y;\r
9005 {\r
9006     char buf[MSG_SIZ];\r
9007 \r
9008     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
9009 \r
9010     switch (selection) {\r
9011       case ClearBoard:\r
9012         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
9013             SendToICS(ics_prefix);\r
9014             SendToICS("bsetup clear\n");\r
9015         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
9016             SendToICS(ics_prefix);\r
9017             SendToICS("clearboard\n");\r
9018         } else {\r
9019             for (x = 0; x < BOARD_WIDTH; x++) {\r
9020                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
9021                     if (gameMode == IcsExamining) {\r
9022                         if (boards[currentMove][y][x] != EmptySquare) {\r
9023                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
9024                                     'a' + x, ONE + y);\r
9025                             SendToICS(buf);\r
9026                         }\r
9027                     } else {\r
9028                         boards[0][y][x] = EmptySquare;\r
9029                     }\r
9030                 }\r
9031             }\r
9032         }\r
9033         if (gameMode == EditPosition) {\r
9034             DrawPosition(FALSE, boards[0]);\r
9035         }\r
9036         break;\r
9037 \r
9038       case WhitePlay:\r
9039         SetWhiteToPlayEvent();\r
9040         break;\r
9041 \r
9042       case BlackPlay:\r
9043         SetBlackToPlayEvent();\r
9044         break;\r
9045 \r
9046       case EmptySquare:\r
9047         if (gameMode == IcsExamining) {\r
9048             sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, ONE + y);\r
9049             SendToICS(buf);\r
9050         } else {\r
9051             boards[0][y][x] = EmptySquare;\r
9052             DrawPosition(FALSE, boards[0]);\r
9053         }\r
9054         break;\r
9055 \r
9056       default:\r
9057         if (gameMode == IcsExamining) {\r
9058             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
9059                     PieceToChar(selection), 'a' + x, ONE + y);\r
9060             SendToICS(buf);\r
9061         } else {\r
9062             boards[0][y][x] = selection;\r
9063             DrawPosition(FALSE, boards[0]);\r
9064         }\r
9065         break;\r
9066     }\r
9067 }\r
9068 \r
9069 \r
9070 void\r
9071 DropMenuEvent(selection, x, y)\r
9072      ChessSquare selection;\r
9073      int x, y;\r
9074 {\r
9075     ChessMove moveType;\r
9076 \r
9077     switch (gameMode) {\r
9078       case IcsPlayingWhite:\r
9079       case MachinePlaysBlack:\r
9080         if (!WhiteOnMove(currentMove)) {\r
9081             DisplayMoveError("It is Black's turn");\r
9082             return;\r
9083         }\r
9084         moveType = WhiteDrop;\r
9085         break;\r
9086       case IcsPlayingBlack:\r
9087       case MachinePlaysWhite:\r
9088         if (WhiteOnMove(currentMove)) {\r
9089             DisplayMoveError("It is White's turn");\r
9090             return;\r
9091         }\r
9092         moveType = BlackDrop;\r
9093         break;\r
9094       case EditGame:\r
9095         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
9096         break;\r
9097       default:\r
9098         return;\r
9099     }\r
9100 \r
9101     if (moveType == BlackDrop && selection < BlackPawn) {\r
9102       selection = (ChessSquare) ((int) selection\r
9103                                  + (int) BlackPawn - (int) WhitePawn);\r
9104     }\r
9105     if (boards[currentMove][y][x] != EmptySquare) {\r
9106         DisplayMoveError("That square is occupied");\r
9107         return;\r
9108     }\r
9109 \r
9110     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
9111 }\r
9112 \r
9113 void\r
9114 AcceptEvent()\r
9115 {\r
9116     /* Accept a pending offer of any kind from opponent */\r
9117     \r
9118     if (appData.icsActive) {\r
9119         SendToICS(ics_prefix);\r
9120         SendToICS("accept\n");\r
9121     } else if (cmailMsgLoaded) {\r
9122         if (currentMove == cmailOldMove &&\r
9123             commentList[cmailOldMove] != NULL &&\r
9124             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9125                    "Black offers a draw" : "White offers a draw")) {\r
9126             TruncateGame();\r
9127             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9129         } else {\r
9130             DisplayError("There is no pending offer on this move", 0);\r
9131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9132         }\r
9133     } else {\r
9134         /* Not used for offers from chess program */\r
9135     }\r
9136 }\r
9137 \r
9138 void\r
9139 DeclineEvent()\r
9140 {\r
9141     /* Decline a pending offer of any kind from opponent */\r
9142     \r
9143     if (appData.icsActive) {\r
9144         SendToICS(ics_prefix);\r
9145         SendToICS("decline\n");\r
9146     } else if (cmailMsgLoaded) {\r
9147         if (currentMove == cmailOldMove &&\r
9148             commentList[cmailOldMove] != NULL &&\r
9149             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9150                    "Black offers a draw" : "White offers a draw")) {\r
9151 #ifdef NOTDEF\r
9152             AppendComment(cmailOldMove, "Draw declined");\r
9153             DisplayComment(cmailOldMove - 1, "Draw declined");\r
9154 #endif /*NOTDEF*/\r
9155         } else {\r
9156             DisplayError("There is no pending offer on this move", 0);\r
9157         }\r
9158     } else {\r
9159         /* Not used for offers from chess program */\r
9160     }\r
9161 }\r
9162 \r
9163 void\r
9164 RematchEvent()\r
9165 {\r
9166     /* Issue ICS rematch command */\r
9167     if (appData.icsActive) {\r
9168         SendToICS(ics_prefix);\r
9169         SendToICS("rematch\n");\r
9170     }\r
9171 }\r
9172 \r
9173 void\r
9174 CallFlagEvent()\r
9175 {\r
9176     /* Call your opponent's flag (claim a win on time) */\r
9177     if (appData.icsActive) {\r
9178         SendToICS(ics_prefix);\r
9179         SendToICS("flag\n");\r
9180     } else {\r
9181         switch (gameMode) {\r
9182           default:\r
9183             return;\r
9184           case MachinePlaysWhite:\r
9185             if (whiteFlag) {\r
9186                 if (blackFlag)\r
9187                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9188                            GE_PLAYER);\r
9189                 else\r
9190                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
9191             } else {\r
9192                 DisplayError("Your opponent is not out of time", 0);\r
9193             }\r
9194             break;\r
9195           case MachinePlaysBlack:\r
9196             if (blackFlag) {\r
9197                 if (whiteFlag)\r
9198                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9199                            GE_PLAYER);\r
9200                 else\r
9201                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
9202             } else {\r
9203                 DisplayError("Your opponent is not out of time", 0);\r
9204             }\r
9205             break;\r
9206         }\r
9207     }\r
9208 }\r
9209 \r
9210 void\r
9211 DrawEvent()\r
9212 {\r
9213     /* Offer draw or accept pending draw offer from opponent */\r
9214     \r
9215     if (appData.icsActive) {\r
9216         /* Note: tournament rules require draw offers to be\r
9217            made after you make your move but before you punch\r
9218            your clock.  Currently ICS doesn't let you do that;\r
9219            instead, you immediately punch your clock after making\r
9220            a move, but you can offer a draw at any time. */\r
9221         \r
9222         SendToICS(ics_prefix);\r
9223         SendToICS("draw\n");\r
9224     } else if (cmailMsgLoaded) {\r
9225         if (currentMove == cmailOldMove &&\r
9226             commentList[cmailOldMove] != NULL &&\r
9227             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9228                    "Black offers a draw" : "White offers a draw")) {\r
9229             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9230             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9231         } else if (currentMove == cmailOldMove + 1) {\r
9232             char *offer = WhiteOnMove(cmailOldMove) ?\r
9233               "White offers a draw" : "Black offers a draw";\r
9234             AppendComment(currentMove, offer);\r
9235             DisplayComment(currentMove - 1, offer);\r
9236             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
9237         } else {\r
9238             DisplayError("You must make your move before offering a draw", 0);\r
9239             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9240         }\r
9241     } else if (first.offeredDraw) {\r
9242         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
9243     } else {\r
9244         if (first.sendDrawOffers) {\r
9245             SendToProgram("draw\n", &first);\r
9246             userOfferedDraw = TRUE;\r
9247         }\r
9248     }\r
9249 }\r
9250 \r
9251 void\r
9252 AdjournEvent()\r
9253 {\r
9254     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
9255     \r
9256     if (appData.icsActive) {\r
9257         SendToICS(ics_prefix);\r
9258         SendToICS("adjourn\n");\r
9259     } else {\r
9260         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
9261     }\r
9262 }\r
9263 \r
9264 \r
9265 void\r
9266 AbortEvent()\r
9267 {\r
9268     /* Offer Abort or accept pending Abort offer from opponent */\r
9269     \r
9270     if (appData.icsActive) {\r
9271         SendToICS(ics_prefix);\r
9272         SendToICS("abort\n");\r
9273     } else {\r
9274         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
9275     }\r
9276 }\r
9277 \r
9278 void\r
9279 ResignEvent()\r
9280 {\r
9281     /* Resign.  You can do this even if it's not your turn. */\r
9282     \r
9283     if (appData.icsActive) {\r
9284         SendToICS(ics_prefix);\r
9285         SendToICS("resign\n");\r
9286     } else {\r
9287         switch (gameMode) {\r
9288           case MachinePlaysWhite:\r
9289             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9290             break;\r
9291           case MachinePlaysBlack:\r
9292             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9293             break;\r
9294           case EditGame:\r
9295             if (cmailMsgLoaded) {\r
9296                 TruncateGame();\r
9297                 if (WhiteOnMove(cmailOldMove)) {\r
9298                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9299                 } else {\r
9300                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9301                 }\r
9302                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
9303             }\r
9304             break;\r
9305           default:\r
9306             break;\r
9307         }\r
9308     }\r
9309 }\r
9310 \r
9311 \r
9312 void\r
9313 StopObservingEvent()\r
9314 {\r
9315     /* Stop observing current games */\r
9316     SendToICS(ics_prefix);\r
9317     SendToICS("unobserve\n");\r
9318 }\r
9319 \r
9320 void\r
9321 StopExaminingEvent()\r
9322 {\r
9323     /* Stop observing current game */\r
9324     SendToICS(ics_prefix);\r
9325     SendToICS("unexamine\n");\r
9326 }\r
9327 \r
9328 void\r
9329 ForwardInner(target)\r
9330      int target;\r
9331 {\r
9332     int limit;\r
9333 \r
9334     if (appData.debugMode)\r
9335         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
9336                 target, currentMove, forwardMostMove);\r
9337 \r
9338     if (gameMode == EditPosition)\r
9339       return;\r
9340 \r
9341     if (gameMode == PlayFromGameFile && !pausing)\r
9342       PauseEvent();\r
9343     \r
9344     if (gameMode == IcsExamining && pausing)\r
9345       limit = pauseExamForwardMostMove;\r
9346     else\r
9347       limit = forwardMostMove;\r
9348     \r
9349     if (target > limit) target = limit;\r
9350 \r
9351     if (target > 0 && moveList[target - 1][0]) {\r
9352         int fromX, fromY, toX, toY;\r
9353         toX = moveList[target - 1][2] - 'a';\r
9354         toY = moveList[target - 1][3] - ONE;\r
9355         if (moveList[target - 1][1] == '@') {\r
9356             if (appData.highlightLastMove) {\r
9357                 SetHighlights(-1, -1, toX, toY);\r
9358             }\r
9359         } else {\r
9360             fromX = moveList[target - 1][0] - 'a';\r
9361             fromY = moveList[target - 1][1] - ONE;\r
9362             if (target == currentMove + 1) {\r
9363                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
9364             }\r
9365             if (appData.highlightLastMove) {\r
9366                 SetHighlights(fromX, fromY, toX, toY);\r
9367             }\r
9368         }\r
9369     }\r
9370     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9371         gameMode == Training || gameMode == PlayFromGameFile || \r
9372         gameMode == AnalyzeFile) {\r
9373         while (currentMove < target) {\r
9374             SendMoveToProgram(currentMove++, &first);\r
9375         }\r
9376     } else {\r
9377         currentMove = target;\r
9378     }\r
9379     \r
9380     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9381         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9382         blackTimeRemaining = timeRemaining[1][currentMove];\r
9383     }\r
9384     DisplayBothClocks();\r
9385     DisplayMove(currentMove - 1);\r
9386     DrawPosition(FALSE, boards[currentMove]);\r
9387     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9388     if (commentList[currentMove] && !matchMode && gameMode != Training) {\r
9389         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9390     }\r
9391 }\r
9392 \r
9393 \r
9394 void\r
9395 ForwardEvent()\r
9396 {\r
9397     if (gameMode == IcsExamining && !pausing) {\r
9398         SendToICS(ics_prefix);\r
9399         SendToICS("forward\n");\r
9400     } else {\r
9401         ForwardInner(currentMove + 1);\r
9402     }\r
9403 }\r
9404 \r
9405 void\r
9406 ToEndEvent()\r
9407 {\r
9408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9409         /* to optimze, we temporarily turn off analysis mode while we feed\r
9410          * the remaining moves to the engine. Otherwise we get analysis output\r
9411          * after each move.\r
9412          */ \r
9413         if (first.analysisSupport) {\r
9414           SendToProgram("exit\nforce\n", &first);\r
9415           first.analyzing = FALSE;\r
9416         }\r
9417     }\r
9418         \r
9419     if (gameMode == IcsExamining && !pausing) {\r
9420         SendToICS(ics_prefix);\r
9421         SendToICS("forward 999999\n");\r
9422     } else {\r
9423         ForwardInner(forwardMostMove);\r
9424     }\r
9425 \r
9426     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9427         /* we have fed all the moves, so reactivate analysis mode */\r
9428         SendToProgram("analyze\n", &first);\r
9429         first.analyzing = TRUE;\r
9430         /*first.maybeThinking = TRUE;*/\r
9431         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9432     }\r
9433 }\r
9434 \r
9435 void\r
9436 BackwardInner(target)\r
9437      int target;\r
9438 {\r
9439     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
9440 \r
9441     if (appData.debugMode)\r
9442         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
9443                 target, currentMove, forwardMostMove);\r
9444 \r
9445     if (gameMode == EditPosition) return;\r
9446     if (currentMove <= backwardMostMove) {\r
9447         ClearHighlights();\r
9448         DrawPosition(full_redraw, boards[currentMove]);\r
9449         return;\r
9450     }\r
9451     if (gameMode == PlayFromGameFile && !pausing)\r
9452       PauseEvent();\r
9453     \r
9454     if (moveList[target][0]) {\r
9455         int fromX, fromY, toX, toY;\r
9456         toX = moveList[target][2] - 'a';\r
9457         toY = moveList[target][3] - ONE;\r
9458         if (moveList[target][1] == '@') {\r
9459             if (appData.highlightLastMove) {\r
9460                 SetHighlights(-1, -1, toX, toY);\r
9461             }\r
9462         } else {\r
9463             fromX = moveList[target][0] - 'a';\r
9464             fromY = moveList[target][1] - ONE;\r
9465             if (target == currentMove - 1) {\r
9466                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
9467             }\r
9468             if (appData.highlightLastMove) {\r
9469                 SetHighlights(fromX, fromY, toX, toY);\r
9470             }\r
9471         }\r
9472     }\r
9473     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
9474         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
9475         while (currentMove > target) {\r
9476             SendToProgram("undo\n", &first);\r
9477             currentMove--;\r
9478         }\r
9479     } else {\r
9480         currentMove = target;\r
9481     }\r
9482     \r
9483     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9484         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9485         blackTimeRemaining = timeRemaining[1][currentMove];\r
9486     }\r
9487     DisplayBothClocks();\r
9488     DisplayMove(currentMove - 1);\r
9489     DrawPosition(full_redraw, boards[currentMove]);\r
9490     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9491     if (commentList[currentMove] != NULL) {\r
9492         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9493     }\r
9494 }\r
9495 \r
9496 void\r
9497 BackwardEvent()\r
9498 {\r
9499     if (gameMode == IcsExamining && !pausing) {\r
9500         SendToICS(ics_prefix);\r
9501         SendToICS("backward\n");\r
9502     } else {\r
9503         BackwardInner(currentMove - 1);\r
9504     }\r
9505 }\r
9506 \r
9507 void\r
9508 ToStartEvent()\r
9509 {\r
9510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9511         /* to optimze, we temporarily turn off analysis mode while we undo\r
9512          * all the moves. Otherwise we get analysis output after each undo.\r
9513          */ \r
9514         if (first.analysisSupport) {\r
9515           SendToProgram("exit\nforce\n", &first);\r
9516           first.analyzing = FALSE;\r
9517         }\r
9518     }\r
9519 \r
9520     if (gameMode == IcsExamining && !pausing) {\r
9521         SendToICS(ics_prefix);\r
9522         SendToICS("backward 999999\n");\r
9523     } else {\r
9524         BackwardInner(backwardMostMove);\r
9525     }\r
9526 \r
9527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9528         /* we have fed all the moves, so reactivate analysis mode */\r
9529         SendToProgram("analyze\n", &first);\r
9530         first.analyzing = TRUE;\r
9531         /*first.maybeThinking = TRUE;*/\r
9532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9533     }\r
9534 }\r
9535 \r
9536 void\r
9537 ToNrEvent(int to)\r
9538 {\r
9539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
9540   if (to >= forwardMostMove) to = forwardMostMove;\r
9541   if (to <= backwardMostMove) to = backwardMostMove;\r
9542   if (to < currentMove) {\r
9543     BackwardInner(to);\r
9544   } else {\r
9545     ForwardInner(to);\r
9546   }\r
9547 }\r
9548 \r
9549 void\r
9550 RevertEvent()\r
9551 {\r
9552     if (gameMode != IcsExamining) {\r
9553         DisplayError("You are not examining a game", 0);\r
9554         return;\r
9555     }\r
9556     if (pausing) {\r
9557         DisplayError("You can't revert while pausing", 0);\r
9558         return;\r
9559     }\r
9560     SendToICS(ics_prefix);\r
9561     SendToICS("revert\n");\r
9562 }\r
9563 \r
9564 void\r
9565 RetractMoveEvent()\r
9566 {\r
9567     switch (gameMode) {\r
9568       case MachinePlaysWhite:\r
9569       case MachinePlaysBlack:\r
9570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9571             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9572             return;\r
9573         }\r
9574         if (forwardMostMove < 2) return;\r
9575         currentMove = forwardMostMove = forwardMostMove - 2;\r
9576         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9577         blackTimeRemaining = timeRemaining[1][currentMove];\r
9578         DisplayBothClocks();\r
9579         DisplayMove(currentMove - 1);\r
9580         ClearHighlights();/*!! could figure this out*/\r
9581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
9582         SendToProgram("remove\n", &first);\r
9583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
9584         break;\r
9585 \r
9586       case BeginningOfGame:\r
9587       default:\r
9588         break;\r
9589 \r
9590       case IcsPlayingWhite:\r
9591       case IcsPlayingBlack:\r
9592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
9593             SendToICS(ics_prefix);\r
9594             SendToICS("takeback 2\n");\r
9595         } else {\r
9596             SendToICS(ics_prefix);\r
9597             SendToICS("takeback 1\n");\r
9598         }\r
9599         break;\r
9600     }\r
9601 }\r
9602 \r
9603 void\r
9604 MoveNowEvent()\r
9605 {\r
9606     ChessProgramState *cps;\r
9607 \r
9608     switch (gameMode) {\r
9609       case MachinePlaysWhite:\r
9610         if (!WhiteOnMove(forwardMostMove)) {\r
9611             DisplayError("It is your turn", 0);\r
9612             return;\r
9613         }\r
9614         cps = &first;\r
9615         break;\r
9616       case MachinePlaysBlack:\r
9617         if (WhiteOnMove(forwardMostMove)) {\r
9618             DisplayError("It is your turn", 0);\r
9619             return;\r
9620         }\r
9621         cps = &first;\r
9622         break;\r
9623       case TwoMachinesPlay:\r
9624         if (WhiteOnMove(forwardMostMove) ==\r
9625             (first.twoMachinesColor[0] == 'w')) {\r
9626             cps = &first;\r
9627         } else {\r
9628             cps = &second;\r
9629         }\r
9630         break;\r
9631       case BeginningOfGame:\r
9632       default:\r
9633         return;\r
9634     }\r
9635     SendToProgram("?\n", cps);\r
9636 }\r
9637 \r
9638 void\r
9639 TruncateGameEvent()\r
9640 {\r
9641     EditGameEvent();\r
9642     if (gameMode != EditGame) return;\r
9643     TruncateGame();\r
9644 }\r
9645 \r
9646 void\r
9647 TruncateGame()\r
9648 {\r
9649     if (forwardMostMove > currentMove) {\r
9650         if (gameInfo.resultDetails != NULL) {\r
9651             free(gameInfo.resultDetails);\r
9652             gameInfo.resultDetails = NULL;\r
9653             gameInfo.result = GameUnfinished;\r
9654         }\r
9655         forwardMostMove = currentMove;\r
9656         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
9657                    currentMove-1);\r
9658     }\r
9659 }\r
9660 \r
9661 void\r
9662 HintEvent()\r
9663 {\r
9664     if (appData.noChessProgram) return;\r
9665     switch (gameMode) {\r
9666       case MachinePlaysWhite:\r
9667         if (WhiteOnMove(forwardMostMove)) {\r
9668             DisplayError("Wait until your turn", 0);\r
9669             return;\r
9670         }\r
9671         break;\r
9672       case BeginningOfGame:\r
9673       case MachinePlaysBlack:\r
9674         if (!WhiteOnMove(forwardMostMove)) {\r
9675             DisplayError("Wait until your turn", 0);\r
9676             return;\r
9677         }\r
9678         break;\r
9679       default:\r
9680         DisplayError("No hint available", 0);\r
9681         return;\r
9682     }\r
9683     SendToProgram("hint\n", &first);\r
9684     hintRequested = TRUE;\r
9685 }\r
9686 \r
9687 void\r
9688 BookEvent()\r
9689 {\r
9690     if (appData.noChessProgram) return;\r
9691     switch (gameMode) {\r
9692       case MachinePlaysWhite:\r
9693         if (WhiteOnMove(forwardMostMove)) {\r
9694             DisplayError("Wait until your turn", 0);\r
9695             return;\r
9696         }\r
9697         break;\r
9698       case BeginningOfGame:\r
9699       case MachinePlaysBlack:\r
9700         if (!WhiteOnMove(forwardMostMove)) {\r
9701             DisplayError("Wait until your turn", 0);\r
9702             return;\r
9703         }\r
9704         break;\r
9705       case EditPosition:\r
9706         EditPositionDone();\r
9707         break;\r
9708       case TwoMachinesPlay:\r
9709         return;\r
9710       default:\r
9711         break;\r
9712     }\r
9713     SendToProgram("bk\n", &first);\r
9714     bookOutput[0] = NULLCHAR;\r
9715     bookRequested = TRUE;\r
9716 }\r
9717 \r
9718 void\r
9719 AboutGameEvent()\r
9720 {\r
9721     char *tags = PGNTags(&gameInfo);\r
9722     TagsPopUp(tags, CmailMsg());\r
9723     free(tags);\r
9724 }\r
9725 \r
9726 /* end button procedures */\r
9727 \r
9728 void\r
9729 PrintPosition(fp, move)\r
9730      FILE *fp;\r
9731      int move;\r
9732 {\r
9733     int i, j;\r
9734     \r
9735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9736         for (j = 0; j < BOARD_WIDTH; j++) {\r
9737             char c = PieceToChar(boards[move][i][j]);\r
9738             fputc(c == 'x' ? '.' : c, fp);\r
9739             fputc(j == BOARD_WIDTH - 1 ? '\n' : ' ', fp);\r
9740         }\r
9741     }\r
9742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
9743       fprintf(fp, "white to play\n");\r
9744     else\r
9745       fprintf(fp, "black to play\n");\r
9746 }\r
9747 \r
9748 void\r
9749 PrintOpponents(fp)\r
9750      FILE *fp;\r
9751 {\r
9752     if (gameInfo.white != NULL) {\r
9753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
9754     } else {\r
9755         fprintf(fp, "\n");\r
9756     }\r
9757 }\r
9758 \r
9759 /* Find last component of program's own name, using some heuristics */\r
9760 void\r
9761 TidyProgramName(prog, host, buf)\r
9762      char *prog, *host, buf[MSG_SIZ];\r
9763 {\r
9764     char *p, *q;\r
9765     int local = (strcmp(host, "localhost") == 0);\r
9766     while (!local && (p = strchr(prog, ';')) != NULL) {\r
9767         p++;\r
9768         while (*p == ' ') p++;\r
9769         prog = p;\r
9770     }\r
9771     if (*prog == '"' || *prog == '\'') {\r
9772         q = strchr(prog + 1, *prog);\r
9773     } else {\r
9774         q = strchr(prog, ' ');\r
9775     }\r
9776     if (q == NULL) q = prog + strlen(prog);\r
9777     p = q;\r
9778     while (p >= prog && *p != '/' && *p != '\\') p--;\r
9779     p++;\r
9780     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
9781     memcpy(buf, p, q - p);\r
9782     buf[q - p] = NULLCHAR;\r
9783     if (!local) {\r
9784         strcat(buf, "@");\r
9785         strcat(buf, host);\r
9786     }\r
9787 }\r
9788 \r
9789 char *\r
9790 TimeControlTagValue()\r
9791 {\r
9792     char buf[MSG_SIZ];\r
9793     if (!appData.clockMode) {\r
9794         strcpy(buf, "-");\r
9795     } else if (movesPerSession > 0) {\r
9796         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
9797     } else if (timeIncrement == 0) {\r
9798         sprintf(buf, "%ld", timeControl/1000);\r
9799     } else {\r
9800         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
9801     }\r
9802     return StrSave(buf);\r
9803 }\r
9804 \r
9805 void\r
9806 SetGameInfo()\r
9807 {\r
9808     /* This routine is used only for certain modes */\r
9809     VariantClass v = gameInfo.variant;\r
9810     ClearGameInfo(&gameInfo);\r
9811     gameInfo.variant = v;\r
9812 \r
9813     switch (gameMode) {\r
9814       case MachinePlaysWhite:\r
9815         gameInfo.event = StrSave( appData.pgnEventHeader );\r
9816         gameInfo.site = StrSave(HostName());\r
9817         gameInfo.date = PGNDate();\r
9818         gameInfo.round = StrSave("-");\r
9819         gameInfo.white = StrSave(first.tidy);\r
9820         gameInfo.black = StrSave(UserName());\r
9821         gameInfo.timeControl = TimeControlTagValue();\r
9822         break;\r
9823 \r
9824       case MachinePlaysBlack:\r
9825         gameInfo.event = StrSave( appData.pgnEventHeader );\r
9826         gameInfo.site = StrSave(HostName());\r
9827         gameInfo.date = PGNDate();\r
9828         gameInfo.round = StrSave("-");\r
9829         gameInfo.white = StrSave(UserName());\r
9830         gameInfo.black = StrSave(first.tidy);\r
9831         gameInfo.timeControl = TimeControlTagValue();\r
9832         break;\r
9833 \r
9834       case TwoMachinesPlay:\r
9835         gameInfo.event = StrSave( appData.pgnEventHeader );\r
9836         gameInfo.site = StrSave(HostName());\r
9837         gameInfo.date = PGNDate();\r
9838         if (matchGame > 0) {\r
9839             char buf[MSG_SIZ];\r
9840             sprintf(buf, "%d", matchGame);\r
9841             gameInfo.round = StrSave(buf);\r
9842         } else {\r
9843             gameInfo.round = StrSave("-");\r
9844         }\r
9845         if (first.twoMachinesColor[0] == 'w') {\r
9846             gameInfo.white = StrSave(first.tidy);\r
9847             gameInfo.black = StrSave(second.tidy);\r
9848         } else {\r
9849             gameInfo.white = StrSave(second.tidy);\r
9850             gameInfo.black = StrSave(first.tidy);\r
9851         }\r
9852         gameInfo.timeControl = TimeControlTagValue();\r
9853         break;\r
9854 \r
9855       case EditGame:\r
9856         gameInfo.event = StrSave("Edited game");\r
9857         gameInfo.site = StrSave(HostName());\r
9858         gameInfo.date = PGNDate();\r
9859         gameInfo.round = StrSave("-");\r
9860         gameInfo.white = StrSave("-");\r
9861         gameInfo.black = StrSave("-");\r
9862         break;\r
9863 \r
9864       case EditPosition:\r
9865         gameInfo.event = StrSave("Edited position");\r
9866         gameInfo.site = StrSave(HostName());\r
9867         gameInfo.date = PGNDate();\r
9868         gameInfo.round = StrSave("-");\r
9869         gameInfo.white = StrSave("-");\r
9870         gameInfo.black = StrSave("-");\r
9871         break;\r
9872 \r
9873       case IcsPlayingWhite:\r
9874       case IcsPlayingBlack:\r
9875       case IcsObserving:\r
9876       case IcsExamining:\r
9877         break;\r
9878 \r
9879       case PlayFromGameFile:\r
9880         gameInfo.event = StrSave("Game from non-PGN file");\r
9881         gameInfo.site = StrSave(HostName());\r
9882         gameInfo.date = PGNDate();\r
9883         gameInfo.round = StrSave("-");\r
9884         gameInfo.white = StrSave("?");\r
9885         gameInfo.black = StrSave("?");\r
9886         break;\r
9887 \r
9888       default:\r
9889         break;\r
9890     }\r
9891 }\r
9892 \r
9893 void\r
9894 ReplaceComment(index, text)\r
9895      int index;\r
9896      char *text;\r
9897 {\r
9898     int len;\r
9899 \r
9900     while (*text == '\n') text++;\r
9901     len = strlen(text);\r
9902     while (len > 0 && text[len - 1] == '\n') len--;\r
9903 \r
9904     if (commentList[index] != NULL)\r
9905       free(commentList[index]);\r
9906 \r
9907     if (len == 0) {\r
9908         commentList[index] = NULL;\r
9909         return;\r
9910     }\r
9911     commentList[index] = (char *) malloc(len + 2);\r
9912     strncpy(commentList[index], text, len);\r
9913     commentList[index][len] = '\n';\r
9914     commentList[index][len + 1] = NULLCHAR;\r
9915 }\r
9916 \r
9917 void\r
9918 CrushCRs(text)\r
9919      char *text;\r
9920 {\r
9921   char *p = text;\r
9922   char *q = text;\r
9923   char ch;\r
9924 \r
9925   do {\r
9926     ch = *p++;\r
9927     if (ch == '\r') continue;\r
9928     *q++ = ch;\r
9929   } while (ch != '\0');\r
9930 }\r
9931 \r
9932 void\r
9933 AppendComment(index, text)\r
9934      int index;\r
9935      char *text;\r
9936 {\r
9937     int oldlen, len;\r
9938     char *old;\r
9939 \r
9940     GetInfoFromComment( index, text );\r
9941 \r
9942     CrushCRs(text);\r
9943     while (*text == '\n') text++;\r
9944     len = strlen(text);\r
9945     while (len > 0 && text[len - 1] == '\n') len--;\r
9946 \r
9947     if (len == 0) return;\r
9948 \r
9949     if (commentList[index] != NULL) {\r
9950         old = commentList[index];\r
9951         oldlen = strlen(old);\r
9952         commentList[index] = (char *) malloc(oldlen + len + 2);\r
9953         strcpy(commentList[index], old);\r
9954         free(old);\r
9955         strncpy(&commentList[index][oldlen], text, len);\r
9956         commentList[index][oldlen + len] = '\n';\r
9957         commentList[index][oldlen + len + 1] = NULLCHAR;\r
9958     } else {\r
9959         commentList[index] = (char *) malloc(len + 2);\r
9960         strncpy(commentList[index], text, len);\r
9961         commentList[index][len] = '\n';\r
9962         commentList[index][len + 1] = NULLCHAR;\r
9963     }\r
9964 }\r
9965 \r
9966 static char * FindStr( char * text, char * sub_text )\r
9967 {\r
9968     char * result = strstr( text, sub_text );\r
9969 \r
9970     if( result != NULL ) {\r
9971         result += strlen( sub_text );\r
9972     }\r
9973 \r
9974     return result;\r
9975 }\r
9976 \r
9977 /* [AS] Try to extract PV info from PGN comment */\r
9978 void GetInfoFromComment( int index, char * text )\r
9979 {\r
9980     if( text != NULL && index > 0 ) {\r
9981         int score = 0;\r
9982         int depth = 0;\r
9983         int time = -1;\r
9984         char * s_eval = FindStr( text, "[%eval " );\r
9985         char * s_emt = FindStr( text, "[%emt " );\r
9986 \r
9987         if( s_eval != NULL || s_emt != NULL ) {\r
9988             /* New style */\r
9989             char delim;\r
9990 \r
9991             if( s_eval != NULL ) {\r
9992                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
9993                     return;\r
9994                 }\r
9995 \r
9996                 if( delim != ']' ) {\r
9997                     return;\r
9998                 }\r
9999             }\r
10000 \r
10001             if( s_emt != NULL ) {\r
10002             }\r
10003         }\r
10004         else {\r
10005             /* We expect something like: [+|-]nnn.nn/dd */\r
10006             char * sep = strchr( text, '/' );\r
10007             int score_lo = 0;\r
10008 \r
10009             if( sep == NULL || sep < (text+4) ) {\r
10010                 return;\r
10011             }\r
10012 \r
10013             if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
10014                 return;\r
10015             }\r
10016 \r
10017             if( score_lo < 0 || score_lo >= 100 ) {\r
10018                 return;\r
10019             }\r
10020 \r
10021             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
10022         }\r
10023 \r
10024         if( depth <= 0 ) {\r
10025             return;\r
10026         }\r
10027 \r
10028         if( time < 0 ) {\r
10029             time = -1;\r
10030         }\r
10031 \r
10032         pvInfoList[index-1].depth = depth;\r
10033         pvInfoList[index-1].score = score;\r
10034         pvInfoList[index-1].time = time;\r
10035     }\r
10036 }\r
10037 \r
10038 void\r
10039 SendToProgram(message, cps)\r
10040      char *message;\r
10041      ChessProgramState *cps;\r
10042 {\r
10043     int count, outCount, error;\r
10044     char buf[MSG_SIZ];\r
10045 \r
10046     if (cps->pr == NULL) return;\r
10047     Attention(cps);\r
10048     \r
10049     if (appData.debugMode) {\r
10050         TimeMark now;\r
10051         GetTimeMark(&now);\r
10052         fprintf(debugFP, "%ld >%-6s: %s", \r
10053                 SubtractTimeMarks(&now, &programStartTime),\r
10054                 cps->which, message);\r
10055     }\r
10056     \r
10057     count = strlen(message);\r
10058     outCount = OutputToProcess(cps->pr, message, count, &error);\r
10059     if (outCount < count && !exiting) {\r
10060         sprintf(buf, "Error writing to %s chess program", cps->which);\r
10061         DisplayFatalError(buf, error, 1);\r
10062     }\r
10063 }\r
10064 \r
10065 void\r
10066 ReceiveFromProgram(isr, closure, message, count, error)\r
10067      InputSourceRef isr;\r
10068      VOIDSTAR closure;\r
10069      char *message;\r
10070      int count;\r
10071      int error;\r
10072 {\r
10073     char *end_str;\r
10074     char buf[MSG_SIZ];\r
10075     ChessProgramState *cps = (ChessProgramState *)closure;\r
10076 \r
10077     if (isr != cps->isr) return; /* Killed intentionally */\r
10078     if (count <= 0) {\r
10079         if (count == 0) {\r
10080             sprintf(buf,\r
10081                     "Error: %s chess program (%s) exited unexpectedly",\r
10082                     cps->which, cps->program);\r
10083             RemoveInputSource(cps->isr);\r
10084             DisplayFatalError(buf, 0, 1);\r
10085         } else {\r
10086             sprintf(buf,\r
10087                     "Error reading from %s chess program (%s)",\r
10088                     cps->which, cps->program);\r
10089             RemoveInputSource(cps->isr);\r
10090 \r
10091             /* [AS] Program is misbehaving badly... kill it */\r
10092             if( count == -2 ) {\r
10093                 DestroyChildProcess( cps->pr, 9 );\r
10094                 cps->pr = NoProc;\r
10095             }\r
10096 \r
10097             DisplayFatalError(buf, error, 1);\r
10098         }\r
10099         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10100         return;\r
10101     }\r
10102     \r
10103     if ((end_str = strchr(message, '\r')) != NULL)\r
10104       *end_str = NULLCHAR;\r
10105     if ((end_str = strchr(message, '\n')) != NULL)\r
10106       *end_str = NULLCHAR;\r
10107     \r
10108     if (appData.debugMode) {\r
10109         TimeMark now;\r
10110         GetTimeMark(&now);\r
10111         fprintf(debugFP, "%ld <%-6s: %s\n", \r
10112                 SubtractTimeMarks(&now, &programStartTime),\r
10113                 cps->which, message);\r
10114     }\r
10115     HandleMachineMove(message, cps);\r
10116 }\r
10117 \r
10118 \r
10119 void\r
10120 SendTimeControl(cps, mps, tc, inc, sd, st)\r
10121      ChessProgramState *cps;\r
10122      int mps, inc, sd, st;\r
10123      long tc;\r
10124 {\r
10125     char buf[MSG_SIZ];\r
10126     int seconds = (tc / 1000) % 60;\r
10127 \r
10128     if( timeControl_2 > 0 ) {\r
10129         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
10130             tc = timeControl_2;\r
10131         }\r
10132     }\r
10133 \r
10134     if (st > 0) {\r
10135       /* Set exact time per move, normally using st command */\r
10136       if (cps->stKludge) {\r
10137         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
10138         seconds = st % 60;\r
10139         if (seconds == 0) {\r
10140           sprintf(buf, "level 1 %d\n", st/60);\r
10141         } else {\r
10142           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
10143         }\r
10144       } else {\r
10145         sprintf(buf, "st %d\n", st);\r
10146       }\r
10147     } else {\r
10148       /* Set conventional or incremental time control, using level command */\r
10149       if (seconds == 0) {\r
10150         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
10151            Fixed in later versions, but still avoid :seconds\r
10152            when seconds is 0. */\r
10153         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
10154       } else {\r
10155         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
10156                 seconds, inc/1000);\r
10157       }\r
10158     }\r
10159     SendToProgram(buf, cps);\r
10160 \r
10161     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
10162     /* Orthogonally, limit search to given depth */\r
10163     if (sd > 0) {\r
10164       if (cps->sdKludge) {\r
10165         sprintf(buf, "depth\n%d\n", sd);\r
10166       } else {\r
10167         sprintf(buf, "sd %d\n", sd);\r
10168       }\r
10169       SendToProgram(buf, cps);\r
10170     }\r
10171 }\r
10172 \r
10173 void\r
10174 SendTimeRemaining(cps, machineWhite)\r
10175      ChessProgramState *cps;\r
10176      int /*boolean*/ machineWhite;\r
10177 {\r
10178     char message[MSG_SIZ];\r
10179     long time, otime;\r
10180 \r
10181     /* Note: this routine must be called when the clocks are stopped\r
10182        or when they have *just* been set or switched; otherwise\r
10183        it will be off by the time since the current tick started.\r
10184     */\r
10185     if (machineWhite) {\r
10186         time = whiteTimeRemaining / 10;\r
10187         otime = blackTimeRemaining / 10;\r
10188     } else {\r
10189         time = blackTimeRemaining / 10;\r
10190         otime = whiteTimeRemaining / 10;\r
10191     }\r
10192     if (time <= 0) time = 1;\r
10193     if (otime <= 0) otime = 1;\r
10194     \r
10195     sprintf(message, "time %ld\n", time);\r
10196     SendToProgram(message, cps);\r
10197 \r
10198     sprintf(message, "otim %ld\n", otime);\r
10199     SendToProgram(message, cps);\r
10200 }\r
10201 \r
10202 int\r
10203 BoolFeature(p, name, loc, cps)\r
10204      char **p;\r
10205      char *name;\r
10206      int *loc;\r
10207      ChessProgramState *cps;\r
10208 {\r
10209   char buf[MSG_SIZ];\r
10210   int len = strlen(name);\r
10211   int val;\r
10212   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10213     (*p) += len + 1;\r
10214     sscanf(*p, "%d", &val);\r
10215     *loc = (val != 0);\r
10216     while (**p && **p != ' ') (*p)++;\r
10217     sprintf(buf, "accepted %s\n", name);\r
10218     SendToProgram(buf, cps);\r
10219     return TRUE;\r
10220   }\r
10221   return FALSE;\r
10222 }\r
10223 \r
10224 int\r
10225 IntFeature(p, name, loc, cps)\r
10226      char **p;\r
10227      char *name;\r
10228      int *loc;\r
10229      ChessProgramState *cps;\r
10230 {\r
10231   char buf[MSG_SIZ];\r
10232   int len = strlen(name);\r
10233   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10234     (*p) += len + 1;\r
10235     sscanf(*p, "%d", loc);\r
10236     while (**p && **p != ' ') (*p)++;\r
10237     sprintf(buf, "accepted %s\n", name);\r
10238     SendToProgram(buf, cps);\r
10239     return TRUE;\r
10240   }\r
10241   return FALSE;\r
10242 }\r
10243 \r
10244 int\r
10245 StringFeature(p, name, loc, cps)\r
10246      char **p;\r
10247      char *name;\r
10248      char loc[];\r
10249      ChessProgramState *cps;\r
10250 {\r
10251   char buf[MSG_SIZ];\r
10252   int len = strlen(name);\r
10253   if (strncmp((*p), name, len) == 0\r
10254       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
10255     (*p) += len + 2;\r
10256     sscanf(*p, "%[^\"]", loc);\r
10257     while (**p && **p != '\"') (*p)++;\r
10258     if (**p == '\"') (*p)++;\r
10259     sprintf(buf, "accepted %s\n", name);\r
10260     SendToProgram(buf, cps);\r
10261     return TRUE;\r
10262   }\r
10263   return FALSE;\r
10264 }\r
10265 \r
10266 void\r
10267 FeatureDone(cps, val)\r
10268      ChessProgramState* cps;\r
10269      int val;\r
10270 {\r
10271   DelayedEventCallback cb = GetDelayedEvent();\r
10272   if ((cb == InitBackEnd3 && cps == &first) ||\r
10273       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
10274     CancelDelayedEvent();\r
10275     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
10276   }\r
10277   cps->initDone = val;\r
10278 }\r
10279 \r
10280 /* Parse feature command from engine */\r
10281 void\r
10282 ParseFeatures(args, cps)\r
10283      char* args;\r
10284      ChessProgramState *cps;  \r
10285 {\r
10286   char *p = args;\r
10287   char *q;\r
10288   int val;\r
10289   char buf[MSG_SIZ];\r
10290 \r
10291   for (;;) {\r
10292     while (*p == ' ') p++;\r
10293     if (*p == NULLCHAR) return;\r
10294 \r
10295     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
10296     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
10297     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
10298     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
10299     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
10300     if (BoolFeature(&p, "reuse", &val, cps)) {\r
10301       /* Engine can disable reuse, but can't enable it if user said no */\r
10302       if (!val) cps->reuse = FALSE;\r
10303       continue;\r
10304     }\r
10305     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
10306     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
10307       if (gameMode == TwoMachinesPlay) {\r
10308         DisplayTwoMachinesTitle();\r
10309       } else {\r
10310         DisplayTitle("");\r
10311       }\r
10312       continue;\r
10313     }\r
10314     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
10315     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
10316     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
10317     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
10318     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
10319     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
10320     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
10321     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
10322     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
10323     if (IntFeature(&p, "done", &val, cps)) {\r
10324       FeatureDone(cps, val);\r
10325       continue;\r
10326     }\r
10327     /* Added by Tord: */\r
10328     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
10329     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
10330     /* End of additions by Tord */\r
10331 \r
10332     /* unknown feature: complain and skip */\r
10333     q = p;\r
10334     while (*q && *q != '=') q++;\r
10335     sprintf(buf, "rejected %.*s\n", q-p, p);\r
10336     SendToProgram(buf, cps);\r
10337     p = q;\r
10338     if (*p == '=') {\r
10339       p++;\r
10340       if (*p == '\"') {\r
10341         p++;\r
10342         while (*p && *p != '\"') p++;\r
10343         if (*p == '\"') p++;\r
10344       } else {\r
10345         while (*p && *p != ' ') p++;\r
10346       }\r
10347     }\r
10348   }\r
10349 \r
10350 }\r
10351 \r
10352 void\r
10353 PeriodicUpdatesEvent(newState)\r
10354      int newState;\r
10355 {\r
10356     if (newState == appData.periodicUpdates)\r
10357       return;\r
10358 \r
10359     appData.periodicUpdates=newState;\r
10360 \r
10361     /* Display type changes, so update it now */\r
10362     DisplayAnalysis();\r
10363 \r
10364     /* Get the ball rolling again... */\r
10365     if (newState) {\r
10366         AnalysisPeriodicEvent(1);\r
10367         StartAnalysisClock();\r
10368     }\r
10369 }\r
10370 \r
10371 void\r
10372 PonderNextMoveEvent(newState)\r
10373      int newState;\r
10374 {\r
10375     if (newState == appData.ponderNextMove) return;\r
10376     if (gameMode == EditPosition) EditPositionDone();\r
10377     if (newState) {\r
10378         SendToProgram("hard\n", &first);\r
10379         if (gameMode == TwoMachinesPlay) {\r
10380             SendToProgram("hard\n", &second);\r
10381         }\r
10382     } else {\r
10383         SendToProgram("easy\n", &first);\r
10384         thinkOutput[0] = NULLCHAR;\r
10385         if (gameMode == TwoMachinesPlay) {\r
10386             SendToProgram("easy\n", &second);\r
10387         }\r
10388     }\r
10389     appData.ponderNextMove = newState;\r
10390 }\r
10391 \r
10392 void\r
10393 ShowThinkingEvent(newState)\r
10394      int newState;\r
10395 {\r
10396     if (newState == appData.showThinking) return;\r
10397     if (gameMode == EditPosition) EditPositionDone();\r
10398     if (newState) {\r
10399         SendToProgram("post\n", &first);\r
10400         if (gameMode == TwoMachinesPlay) {\r
10401             SendToProgram("post\n", &second);\r
10402         }\r
10403     } else {\r
10404         SendToProgram("nopost\n", &first);\r
10405         thinkOutput[0] = NULLCHAR;\r
10406         if (gameMode == TwoMachinesPlay) {\r
10407             SendToProgram("nopost\n", &second);\r
10408         }\r
10409     }\r
10410     appData.showThinking = newState;\r
10411 }\r
10412 \r
10413 void\r
10414 AskQuestionEvent(title, question, replyPrefix, which)\r
10415      char *title; char *question; char *replyPrefix; char *which;\r
10416 {\r
10417   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
10418   if (pr == NoProc) return;\r
10419   AskQuestion(title, question, replyPrefix, pr);\r
10420 }\r
10421 \r
10422 void\r
10423 DisplayMove(moveNumber)\r
10424      int moveNumber;\r
10425 {\r
10426     char message[MSG_SIZ];\r
10427     char res[MSG_SIZ];\r
10428     char cpThinkOutput[MSG_SIZ];\r
10429 \r
10430     if (moveNumber == forwardMostMove - 1 || \r
10431         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10432 \r
10433         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
10434 \r
10435         if (strchr(cpThinkOutput, '\n')) {\r
10436             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
10437         }\r
10438     } else {\r
10439         *cpThinkOutput = NULLCHAR;\r
10440     }\r
10441 \r
10442     /* [AS] Hide thinking from human user */\r
10443     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
10444         *cpThinkOutput = NULLCHAR;\r
10445         if( thinkOutput[0] != NULLCHAR ) {\r
10446             int i;\r
10447 \r
10448             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
10449                 cpThinkOutput[i] = '.';\r
10450             }\r
10451             cpThinkOutput[i] = NULLCHAR;\r
10452             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
10453         }\r
10454     }\r
10455 \r
10456     if (moveNumber == forwardMostMove - 1 &&\r
10457         gameInfo.resultDetails != NULL) {\r
10458         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
10459             sprintf(res, " %s", PGNResult(gameInfo.result));\r
10460         } else {\r
10461             sprintf(res, " {%s} %s",\r
10462                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
10463         }\r
10464     } else {\r
10465         res[0] = NULLCHAR;\r
10466     }\r
10467     \r
10468     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
10469         DisplayMessage(res, cpThinkOutput);\r
10470     } else {\r
10471         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
10472                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
10473                 parseList[moveNumber], res);\r
10474         DisplayMessage(message, cpThinkOutput);\r
10475     }\r
10476 }\r
10477 \r
10478 void\r
10479 DisplayAnalysisText(text)\r
10480      char *text;\r
10481 {\r
10482     char buf[MSG_SIZ];\r
10483 \r
10484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10485         sprintf(buf, "Analysis (%s)", first.tidy);\r
10486         AnalysisPopUp(buf, text);\r
10487     }\r
10488 }\r
10489 \r
10490 static int\r
10491 only_one_move(str)\r
10492      char *str;\r
10493 {\r
10494     while (*str && isspace(*str)) ++str;\r
10495     while (*str && !isspace(*str)) ++str;\r
10496     if (!*str) return 1;\r
10497     while (*str && isspace(*str)) ++str;\r
10498     if (!*str) return 1;\r
10499     return 0;\r
10500 }\r
10501 \r
10502 void\r
10503 DisplayAnalysis()\r
10504 {\r
10505     char buf[MSG_SIZ];\r
10506     char lst[MSG_SIZ / 2];\r
10507     double nps;\r
10508     static char *xtra[] = { "", " (--)", " (++)" };\r
10509     int h, m, s, cs;\r
10510   \r
10511     if (programStats.time == 0) {\r
10512         programStats.time = 1;\r
10513     }\r
10514   \r
10515     if (programStats.got_only_move) {\r
10516         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
10517     } else {\r
10518         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
10519 \r
10520         nps = (((double)programStats.nodes) /\r
10521                (((double)programStats.time)/100.0));\r
10522 \r
10523         cs = programStats.time % 100;\r
10524         s = programStats.time / 100;\r
10525         h = (s / (60*60));\r
10526         s = s - h*60*60;\r
10527         m = (s/60);\r
10528         s = s - m*60;\r
10529 \r
10530         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
10531           if (programStats.move_name[0] != NULLCHAR) {\r
10532             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10533                     programStats.depth,\r
10534                     programStats.nr_moves-programStats.moves_left,\r
10535                     programStats.nr_moves, programStats.move_name,\r
10536                     ((float)programStats.score)/100.0, lst,\r
10537                     only_one_move(lst)?\r
10538                     xtra[programStats.got_fail] : "",\r
10539                     programStats.nodes, (int)nps, h, m, s, cs);\r
10540           } else {\r
10541             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10542                     programStats.depth,\r
10543                     programStats.nr_moves-programStats.moves_left,\r
10544                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
10545                     lst,\r
10546                     only_one_move(lst)?\r
10547                     xtra[programStats.got_fail] : "",\r
10548                     programStats.nodes, (int)nps, h, m, s, cs);\r
10549           }\r
10550         } else {\r
10551             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10552                     programStats.depth,\r
10553                     ((float)programStats.score)/100.0,\r
10554                     lst,\r
10555                     only_one_move(lst)?\r
10556                     xtra[programStats.got_fail] : "",\r
10557                     programStats.nodes, (int)nps, h, m, s, cs);\r
10558         }\r
10559     }\r
10560     DisplayAnalysisText(buf);\r
10561 }\r
10562 \r
10563 void\r
10564 DisplayComment(moveNumber, text)\r
10565      int moveNumber;\r
10566      char *text;\r
10567 {\r
10568     char title[MSG_SIZ];\r
10569 \r
10570     if( appData.autoDisplayComment ) {\r
10571         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
10572             strcpy(title, "Comment");\r
10573         } else {\r
10574             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
10575                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
10576                     parseList[moveNumber]);\r
10577         }\r
10578 \r
10579         CommentPopUp(title, text);\r
10580     }\r
10581 }\r
10582 \r
10583 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
10584  * might be busy thinking or pondering.  It can be omitted if your\r
10585  * gnuchess is configured to stop thinking immediately on any user\r
10586  * input.  However, that gnuchess feature depends on the FIONREAD\r
10587  * ioctl, which does not work properly on some flavors of Unix.\r
10588  */\r
10589 void\r
10590 Attention(cps)\r
10591      ChessProgramState *cps;\r
10592 {\r
10593 #if ATTENTION\r
10594     if (!cps->useSigint) return;\r
10595     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
10596     switch (gameMode) {\r
10597       case MachinePlaysWhite:\r
10598       case MachinePlaysBlack:\r
10599       case TwoMachinesPlay:\r
10600       case IcsPlayingWhite:\r
10601       case IcsPlayingBlack:\r
10602       case AnalyzeMode:\r
10603       case AnalyzeFile:\r
10604         /* Skip if we know it isn't thinking */\r
10605         if (!cps->maybeThinking) return;\r
10606         if (appData.debugMode)\r
10607           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
10608         InterruptChildProcess(cps->pr);\r
10609         cps->maybeThinking = FALSE;\r
10610         break;\r
10611       default:\r
10612         break;\r
10613     }\r
10614 #endif /*ATTENTION*/\r
10615 }\r
10616 \r
10617 int\r
10618 CheckFlags()\r
10619 {\r
10620     if (whiteTimeRemaining <= 0) {\r
10621         if (!whiteFlag) {\r
10622             whiteFlag = TRUE;\r
10623             if (appData.icsActive) {\r
10624                 if (appData.autoCallFlag &&\r
10625                     gameMode == IcsPlayingBlack && !blackFlag) {\r
10626                   SendToICS(ics_prefix);\r
10627                   SendToICS("flag\n");\r
10628                 }\r
10629             } else {\r
10630                 if (blackFlag) {\r
10631                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
10632                 } else {\r
10633                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
10634                     if (appData.autoCallFlag) {\r
10635                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
10636                         return TRUE;\r
10637                     }\r
10638                 }\r
10639             }\r
10640         }\r
10641     }\r
10642     if (blackTimeRemaining <= 0) {\r
10643         if (!blackFlag) {\r
10644             blackFlag = TRUE;\r
10645             if (appData.icsActive) {\r
10646                 if (appData.autoCallFlag &&\r
10647                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
10648                   SendToICS(ics_prefix);\r
10649                   SendToICS("flag\n");\r
10650                 }\r
10651             } else {\r
10652                 if (whiteFlag) {\r
10653                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
10654                 } else {\r
10655                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
10656                     if (appData.autoCallFlag) {\r
10657                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
10658                         return TRUE;\r
10659                     }\r
10660                 }\r
10661             }\r
10662         }\r
10663     }\r
10664     return FALSE;\r
10665 }\r
10666 \r
10667 void\r
10668 CheckTimeControl()\r
10669 {\r
10670     if (!appData.clockMode || appData.icsActive ||\r
10671         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
10672 \r
10673     if (timeIncrement >= 0) {\r
10674         if (WhiteOnMove(forwardMostMove)) {\r
10675             blackTimeRemaining += timeIncrement;\r
10676         } else {\r
10677             whiteTimeRemaining += timeIncrement;\r
10678         }\r
10679     }\r
10680     /*\r
10681      * add time to clocks when time control is achieved\r
10682      */\r
10683     if (movesPerSession) {\r
10684       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {\r
10685       case 0:\r
10686         /* White made time control */\r
10687         whiteTimeRemaining += GetTimeControlForWhite();\r
10688         break;\r
10689       case 1:\r
10690         /* Black made time control */\r
10691         blackTimeRemaining += GetTimeControlForBlack();\r
10692         break;\r
10693       default:\r
10694         break;\r
10695       }\r
10696     }\r
10697 }\r
10698 \r
10699 void\r
10700 DisplayBothClocks()\r
10701 {\r
10702     int wom = gameMode == EditPosition ?\r
10703       !blackPlaysFirst : WhiteOnMove(currentMove);\r
10704     DisplayWhiteClock(whiteTimeRemaining, wom);\r
10705     DisplayBlackClock(blackTimeRemaining, !wom);\r
10706 }\r
10707 \r
10708 \r
10709 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
10710    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
10711    to use other calls if you don't.  Clocks will be less accurate if\r
10712    you have neither ftime nor gettimeofday.\r
10713 */\r
10714 \r
10715 /* Get the current time as a TimeMark */\r
10716 void\r
10717 GetTimeMark(tm)\r
10718      TimeMark *tm;\r
10719 {\r
10720 #if HAVE_GETTIMEOFDAY\r
10721 \r
10722     struct timeval timeVal;\r
10723     struct timezone timeZone;\r
10724 \r
10725     gettimeofday(&timeVal, &timeZone);\r
10726     tm->sec = (long) timeVal.tv_sec; \r
10727     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
10728 \r
10729 #else /*!HAVE_GETTIMEOFDAY*/\r
10730 #if HAVE_FTIME\r
10731 \r
10732 #include <sys/timeb.h>\r
10733     struct timeb timeB;\r
10734 \r
10735     ftime(&timeB);\r
10736     tm->sec = (long) timeB.time;\r
10737     tm->ms = (int) timeB.millitm;\r
10738 \r
10739 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
10740     tm->sec = (long) time(NULL);\r
10741     tm->ms = 0;\r
10742 #endif\r
10743 #endif\r
10744 }\r
10745 \r
10746 /* Return the difference in milliseconds between two\r
10747    time marks.  We assume the difference will fit in a long!\r
10748 */\r
10749 long\r
10750 SubtractTimeMarks(tm2, tm1)\r
10751      TimeMark *tm2, *tm1;\r
10752 {\r
10753     return 1000L*(tm2->sec - tm1->sec) +\r
10754            (long) (tm2->ms - tm1->ms);\r
10755 }\r
10756 \r
10757 \r
10758 /*\r
10759  * Code to manage the game clocks.\r
10760  *\r
10761  * In tournament play, black starts the clock and then white makes a move.\r
10762  * We give the human user a slight advantage if he is playing white---the\r
10763  * clocks don't run until he makes his first move, so it takes zero time.\r
10764  * Also, we don't account for network lag, so we could get out of sync\r
10765  * with GNU Chess's clock -- but then, referees are always right.  \r
10766  */\r
10767 \r
10768 static TimeMark tickStartTM;\r
10769 static long intendedTickLength;\r
10770 \r
10771 long\r
10772 NextTickLength(timeRemaining)\r
10773      long timeRemaining;\r
10774 {\r
10775     long nominalTickLength, nextTickLength;\r
10776 \r
10777     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
10778       nominalTickLength = 100L;\r
10779     else\r
10780       nominalTickLength = 1000L;\r
10781     nextTickLength = timeRemaining % nominalTickLength;\r
10782     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
10783 \r
10784     return nextTickLength;\r
10785 }\r
10786 \r
10787 /* Stop clocks and reset to a fresh time control */\r
10788 void\r
10789 ResetClocks() \r
10790 {\r
10791     (void) StopClockTimer();\r
10792     if (appData.icsActive) {\r
10793         whiteTimeRemaining = blackTimeRemaining = 0;\r
10794     } else {\r
10795         whiteTimeRemaining = GetTimeControlForWhite();\r
10796         blackTimeRemaining = GetTimeControlForBlack();\r
10797     }\r
10798     if (whiteFlag || blackFlag) {\r
10799         DisplayTitle("");\r
10800         whiteFlag = blackFlag = FALSE;\r
10801     }\r
10802     DisplayBothClocks();\r
10803 }\r
10804 \r
10805 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
10806 \r
10807 /* Decrement running clock by amount of time that has passed */\r
10808 void\r
10809 DecrementClocks()\r
10810 {\r
10811     long timeRemaining;\r
10812     long lastTickLength, fudge;\r
10813     TimeMark now;\r
10814 \r
10815     if (!appData.clockMode) return;\r
10816     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
10817         \r
10818     GetTimeMark(&now);\r
10819 \r
10820     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
10821 \r
10822     /* Fudge if we woke up a little too soon */\r
10823     fudge = intendedTickLength - lastTickLength;\r
10824     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
10825 \r
10826     if (WhiteOnMove(forwardMostMove)) {\r
10827         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
10828         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
10829                           WhiteOnMove(currentMove));\r
10830     } else {\r
10831         timeRemaining = blackTimeRemaining -= lastTickLength;\r
10832         DisplayBlackClock(blackTimeRemaining - fudge,\r
10833                           !WhiteOnMove(currentMove));\r
10834     }\r
10835 \r
10836     if (CheckFlags()) return;\r
10837         \r
10838     tickStartTM = now;\r
10839     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
10840     StartClockTimer(intendedTickLength);\r
10841 \r
10842     /* if the time remaining has fallen below the alarm threshold, sound the\r
10843      * alarm. if the alarm has sounded and (due to a takeback or time control\r
10844      * with increment) the time remaining has increased to a level above the\r
10845      * threshold, reset the alarm so it can sound again. \r
10846      */\r
10847     \r
10848     if (appData.icsActive && appData.icsAlarm) {\r
10849 \r
10850         /* make sure we are dealing with the user's clock */\r
10851         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
10852                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
10853            )) return;\r
10854 \r
10855         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
10856             alarmSounded = FALSE;\r
10857         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
10858             PlayAlarmSound();\r
10859             alarmSounded = TRUE;\r
10860         }\r
10861     }\r
10862 }\r
10863 \r
10864 \r
10865 /* A player has just moved, so stop the previously running\r
10866    clock and (if in clock mode) start the other one.\r
10867    We redisplay both clocks in case we're in ICS mode, because\r
10868    ICS gives us an update to both clocks after every move.\r
10869    Note that this routine is called *after* forwardMostMove\r
10870    is updated, so the last fractional tick must be subtracted\r
10871    from the color that is *not* on move now.\r
10872 */\r
10873 void\r
10874 SwitchClocks()\r
10875 {\r
10876     long lastTickLength;\r
10877     TimeMark now;\r
10878     int flagged = FALSE;\r
10879 \r
10880     GetTimeMark(&now);\r
10881 \r
10882     if (StopClockTimer() && appData.clockMode) {\r
10883         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
10884         if (WhiteOnMove(forwardMostMove)) {\r
10885             blackTimeRemaining -= lastTickLength;\r
10886         } else {\r
10887             whiteTimeRemaining -= lastTickLength;\r
10888         }\r
10889         /* [HGM] save time for PGN file if engine did not give it */\r
10890         if(pvInfoList[forwardMostMove-1].time == -1)\r
10891              pvInfoList[forwardMostMove-1].time = lastTickLength/100;\r
10892         flagged = CheckFlags();\r
10893     }\r
10894     CheckTimeControl();\r
10895 \r
10896     if (flagged || !appData.clockMode) return;\r
10897 \r
10898     switch (gameMode) {\r
10899       case MachinePlaysBlack:\r
10900       case MachinePlaysWhite:\r
10901       case BeginningOfGame:\r
10902         if (pausing) return;\r
10903         break;\r
10904 \r
10905       case EditGame:\r
10906       case PlayFromGameFile:\r
10907       case IcsExamining:\r
10908         return;\r
10909 \r
10910       default:\r
10911         break;\r
10912     }\r
10913 \r
10914     tickStartTM = now;\r
10915     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
10916       whiteTimeRemaining : blackTimeRemaining);\r
10917     StartClockTimer(intendedTickLength);\r
10918 }\r
10919         \r
10920 \r
10921 /* Stop both clocks */\r
10922 void\r
10923 StopClocks()\r
10924 {       \r
10925     long lastTickLength;\r
10926     TimeMark now;\r
10927 \r
10928     if (!StopClockTimer()) return;\r
10929     if (!appData.clockMode) return;\r
10930 \r
10931     GetTimeMark(&now);\r
10932 \r
10933     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
10934     if (WhiteOnMove(forwardMostMove)) {\r
10935         whiteTimeRemaining -= lastTickLength;\r
10936         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
10937     } else {\r
10938         blackTimeRemaining -= lastTickLength;\r
10939         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
10940     }\r
10941     CheckFlags();\r
10942 }\r
10943         \r
10944 /* Start clock of player on move.  Time may have been reset, so\r
10945    if clock is already running, stop and restart it. */\r
10946 void\r
10947 StartClocks()\r
10948 {\r
10949     (void) StopClockTimer(); /* in case it was running already */\r
10950     DisplayBothClocks();\r
10951     if (CheckFlags()) return;\r
10952 \r
10953     if (!appData.clockMode) return;\r
10954     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
10955 \r
10956     GetTimeMark(&tickStartTM);\r
10957     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
10958       whiteTimeRemaining : blackTimeRemaining);\r
10959     StartClockTimer(intendedTickLength);\r
10960 }\r
10961 \r
10962 char *\r
10963 TimeString(ms)\r
10964      long ms;\r
10965 {\r
10966     long second, minute, hour, day;\r
10967     char *sign = "";\r
10968     static char buf[32];\r
10969     \r
10970     if (ms > 0 && ms <= 9900) {\r
10971       /* convert milliseconds to tenths, rounding up */\r
10972       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
10973 \r
10974       sprintf(buf, " %03.1f ", tenths/10.0);\r
10975       return buf;\r
10976     }\r
10977 \r
10978     /* convert milliseconds to seconds, rounding up */\r
10979     /* use floating point to avoid strangeness of integer division\r
10980        with negative dividends on many machines */\r
10981     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
10982 \r
10983     if (second < 0) {\r
10984         sign = "-";\r
10985         second = -second;\r
10986     }\r
10987     \r
10988     day = second / (60 * 60 * 24);\r
10989     second = second % (60 * 60 * 24);\r
10990     hour = second / (60 * 60);\r
10991     second = second % (60 * 60);\r
10992     minute = second / 60;\r
10993     second = second % 60;\r
10994     \r
10995     if (day > 0)\r
10996       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
10997               sign, day, hour, minute, second);\r
10998     else if (hour > 0)\r
10999       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
11000     else\r
11001       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
11002     \r
11003     return buf;\r
11004 }\r
11005 \r
11006 \r
11007 /*\r
11008  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
11009  */\r
11010 char *\r
11011 StrStr(string, match)\r
11012      char *string, *match;\r
11013 {\r
11014     int i, length;\r
11015     \r
11016     length = strlen(match);\r
11017     \r
11018     for (i = strlen(string) - length; i >= 0; i--, string++)\r
11019       if (!strncmp(match, string, length))\r
11020         return string;\r
11021     \r
11022     return NULL;\r
11023 }\r
11024 \r
11025 char *\r
11026 StrCaseStr(string, match)\r
11027      char *string, *match;\r
11028 {\r
11029     int i, j, length;\r
11030     \r
11031     length = strlen(match);\r
11032     \r
11033     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
11034         for (j = 0; j < length; j++) {\r
11035             if (ToLower(match[j]) != ToLower(string[j]))\r
11036               break;\r
11037         }\r
11038         if (j == length) return string;\r
11039     }\r
11040 \r
11041     return NULL;\r
11042 }\r
11043 \r
11044 #ifndef _amigados\r
11045 int\r
11046 StrCaseCmp(s1, s2)\r
11047      char *s1, *s2;\r
11048 {\r
11049     char c1, c2;\r
11050     \r
11051     for (;;) {\r
11052         c1 = ToLower(*s1++);\r
11053         c2 = ToLower(*s2++);\r
11054         if (c1 > c2) return 1;\r
11055         if (c1 < c2) return -1;\r
11056         if (c1 == NULLCHAR) return 0;\r
11057     }\r
11058 }\r
11059 \r
11060 \r
11061 int\r
11062 ToLower(c)\r
11063      int c;\r
11064 {\r
11065     return isupper(c) ? tolower(c) : c;\r
11066 }\r
11067 \r
11068 \r
11069 int\r
11070 ToUpper(c)\r
11071      int c;\r
11072 {\r
11073     return islower(c) ? toupper(c) : c;\r
11074 }\r
11075 #endif /* !_amigados    */\r
11076 \r
11077 char *\r
11078 StrSave(s)\r
11079      char *s;\r
11080 {\r
11081     char *ret;\r
11082 \r
11083     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
11084         strcpy(ret, s);\r
11085     }\r
11086     return ret;\r
11087 }\r
11088 \r
11089 char *\r
11090 StrSavePtr(s, savePtr)\r
11091      char *s, **savePtr;\r
11092 {\r
11093     if (*savePtr) {\r
11094         free(*savePtr);\r
11095     }\r
11096     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
11097         strcpy(*savePtr, s);\r
11098     }\r
11099     return(*savePtr);\r
11100 }\r
11101 \r
11102 char *\r
11103 PGNDate()\r
11104 {\r
11105     time_t clock;\r
11106     struct tm *tm;\r
11107     char buf[MSG_SIZ];\r
11108 \r
11109     clock = time((time_t *)NULL);\r
11110     tm = localtime(&clock);\r
11111     sprintf(buf, "%04d.%02d.%02d",\r
11112             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
11113     return StrSave(buf);\r
11114 }\r
11115 \r
11116 \r
11117 char *\r
11118 PositionToFEN(move, useFEN960)\r
11119      int move;\r
11120      int useFEN960;\r
11121 {\r
11122     int i, j, fromX, fromY, toX, toY;\r
11123     int whiteToPlay;\r
11124     char buf[128];\r
11125     char *p, *q;\r
11126     int emptycount;\r
11127 \r
11128     whiteToPlay = (gameMode == EditPosition) ?\r
11129       !blackPlaysFirst : (move % 2 == 0);\r
11130     p = buf;\r
11131 \r
11132     /* Piece placement data */\r
11133     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11134         emptycount = 0;\r
11135         for (j = 0; j < BOARD_WIDTH; j++) {\r
11136             if (boards[move][i][j] == EmptySquare) {\r
11137                 emptycount++;\r
11138             } else {\r
11139                 if (emptycount > 0) {\r
11140                     if(emptycount<10) /* [HGM] can be >= 10 */\r
11141                         *p++ = '0' + emptycount;\r
11142                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11143                     emptycount = 0;\r
11144                 }\r
11145                 *p++ = PieceToChar(boards[move][i][j]);\r
11146             }\r
11147         }\r
11148         if (emptycount > 0) {\r
11149             if(emptycount<10) /* [HGM] can be >= 10 */\r
11150                 *p++ = '0' + emptycount;\r
11151             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11152             emptycount = 0;\r
11153         }\r
11154         *p++ = '/';\r
11155     }\r
11156     *(p - 1) = ' ';\r
11157 \r
11158     /* Active color */\r
11159     *p++ = whiteToPlay ? 'w' : 'b';\r
11160     *p++ = ' ';\r
11161 \r
11162     /* HACK: we don't keep track of castling availability, so fake it! */\r
11163     /* Tord! please fix with the aid of castlingRights[move][...] */\r
11164 \r
11165     /* PUSH Fabien & Tord */\r
11166 \r
11167     /* Declare all potential FRC castling rights (conservative) */\r
11168     /* outermost rook on each side of the king */\r
11169 \r
11170     if( gameInfo.variant == VariantFischeRandom ) {\r
11171        int fk, fr;\r
11172 \r
11173        q = p;\r
11174 \r
11175        /* White castling rights */\r
11176 \r
11177        for (fk = 1; fk < BOARD_WIDTH-1; fk++) {\r
11178 \r
11179           if (boards[move][0][fk] == WhiteKing) {\r
11180 \r
11181              for (fr = BOARD_WIDTH-1; fr > fk; fr--) { /* H side */\r
11182                 if (boards[move][0][fr] == WhiteRook) {\r
11183                    *p++ = useFEN960 ? 'A' + fr : 'K';\r
11184                    break;\r
11185                 }\r
11186              }\r
11187 \r
11188              for (fr = 0; fr < fk; fr++) { /* A side */\r
11189                 if (boards[move][0][fr] == WhiteRook) {\r
11190                    *p++ = useFEN960 ? 'A' + fr : 'Q';\r
11191                    break;\r
11192                 }\r
11193              }\r
11194           }\r
11195        }\r
11196 \r
11197        /* Black castling rights */\r
11198 \r
11199        for (fk = 1; fk < BOARD_WIDTH-1; fk++) {\r
11200 \r
11201           if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {\r
11202 \r
11203              for (fr = BOARD_WIDTH-1; fr > fk; fr--) { /* H side */\r
11204                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11205                    *p++ = useFEN960 ? 'a' + fr : 'k';\r
11206                    break;\r
11207                 }\r
11208              }\r
11209 \r
11210              for (fr = 0; fr < fk; fr++) { /* A side */\r
11211                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11212                    *p++ = useFEN960 ? 'a' + fr : 'q';\r
11213                    break;\r
11214                 }\r
11215              }\r
11216           }\r
11217        }\r
11218 \r
11219        if (q == p) *p++ = '-'; /* No castling rights */\r
11220        *p++ = ' ';\r
11221     }\r
11222     else {\r
11223         q = p;\r
11224 \r
11225 #ifdef OLDCASTLINGCODE\r
11226         if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {\r
11227             if (boards[move][0][BOARD_WIDTH-1] == WhiteRook) *p++ = 'K';\r
11228             if (boards[move][0][0] == WhiteRook) *p++ = 'Q';\r
11229         }\r
11230         if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {\r
11231             if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';\r
11232             if (boards[move][BOARD_HEIGHT-1][0] == BlackRook) *p++ = 'q';\r
11233         }           \r
11234 #else\r
11235         /* [HGM] write true castling rights */\r
11236         if( nrCastlingRights == 6 ) {\r
11237             if(castlingRights[move][0] == BOARD_WIDTH-1 &&\r
11238                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
11239             if(castlingRights[move][1] == 0 &&\r
11240                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
11241             if(castlingRights[move][3] == BOARD_WIDTH-1 &&\r
11242                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
11243             if(castlingRights[move][4] == 0 &&\r
11244                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
11245         }\r
11246 #endif\r
11247         if (q == p) *p++ = '-';\r
11248         *p++ = ' ';\r
11249     }\r
11250 \r
11251     /* POP Fabien & Tord */\r
11252 \r
11253     /* En passant target square */\r
11254     if (move > backwardMostMove) {\r
11255         fromX = moveList[move - 1][0] - 'a';\r
11256         fromY = moveList[move - 1][1] - ONE;\r
11257         toX = moveList[move - 1][2] - 'a';\r
11258         toY = moveList[move - 1][3] - ONE;\r
11259         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
11260             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
11261             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
11262             fromX == toX) {\r
11263             /* 2-square pawn move just happened */\r
11264             *p++ = toX + 'a';\r
11265             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
11266         } else {\r
11267             *p++ = '-';\r
11268         }\r
11269     } else {\r
11270         *p++ = '-';\r
11271     }\r
11272 \r
11273     /* [HGM] find reversible plies */\r
11274     {   int i = 0, j=move;\r
11275 \r
11276     if (appData.debugMode) { int k;\r
11277         fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
11278         for(k=backwardMostMove; k<=forwardMostMove; k++)\r
11279             fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
11280 \r
11281     }\r
11282 \r
11283         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
11284         if( j == backwardMostMove ) i += initialRulePlies;\r
11285         sprintf(p, " %d", i);\r
11286       p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
11287     }\r
11288     /* Fullmove number */\r
11289     sprintf(p, " %d", (move / 2) + 1);\r
11290     \r
11291     return StrSave(buf);\r
11292 }\r
11293 \r
11294 Boolean\r
11295 ParseFEN(board, blackPlaysFirst, fen)\r
11296     Board board;\r
11297      int *blackPlaysFirst;\r
11298      char *fen;\r
11299 {\r
11300     int i, j;\r
11301     char *p;\r
11302     int emptycount;\r
11303 \r
11304     p = fen;\r
11305 \r
11306     /* Piece placement data */\r
11307     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11308         j = 0;\r
11309         for (;;) {\r
11310             if (*p == '/' || *p == ' ') {\r
11311                 if (*p == '/') p++;\r
11312                 emptycount = BOARD_WIDTH - j;\r
11313                 while (emptycount--) board[i][j++] = EmptySquare;\r
11314                 break;\r
11315 #if(BOARD_SIZE >= 10)\r
11316             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
11317                 p++; emptycount=10;\r
11318                 if (j + emptycount > BOARD_WIDTH) return FALSE;\r
11319                 while (emptycount--) board[i][j++] = EmptySquare;\r
11320 #endif\r
11321             } else if (isdigit(*p)) {\r
11322                 emptycount = *p++ - '0';\r
11323                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
11324                 if (j + emptycount > BOARD_WIDTH) return FALSE;\r
11325                 while (emptycount--) board[i][j++] = EmptySquare;\r
11326             } else if (isalpha(*p)) {\r
11327                 if (j >= BOARD_WIDTH) return FALSE;\r
11328                 board[i][j++] = CharToPiece(*p++);\r
11329             } else {\r
11330                 return FALSE;\r
11331             }\r
11332         }\r
11333     }\r
11334     while (*p == '/' || *p == ' ') p++;\r
11335 \r
11336     /* Active color */\r
11337     switch (*p++) {\r
11338       case 'w':\r
11339         *blackPlaysFirst = FALSE;\r
11340         break;\r
11341       case 'b': \r
11342         *blackPlaysFirst = TRUE;\r
11343         break;\r
11344       default:\r
11345         return FALSE;\r
11346     }\r
11347 \r
11348     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
11349     /* return the extra info in global variiables             */\r
11350   {\r
11351     /* set defaults in case FEN is incomplete */\r
11352     FENepStatus = EP_UNKNOWN;\r
11353     for(i=0; i<nrCastlingRights; i++ ) {\r
11354         FENcastlingRights[i] = initialRights[i];\r
11355     }   /* assume possible unless obviously impossible */\r
11356     if(board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
11357     if(board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
11358     if(board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
11359     if(board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
11360     if(board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
11361     if(board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
11362     FENrulePlies = 0;\r
11363 \r
11364     while(*p==' ') p++;\r
11365 \r
11366     if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11367               /* castling indicator present, so default is impossible */\r
11368               for(i=0; i<nrCastlingRights; i++ ) {\r
11369                      FENcastlingRights[i] = -1;\r
11370               }\r
11371     }\r
11372     while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11373         switch(*p++) {\r
11374           case'K':\r
11375               FENcastlingRights[0] = BOARD_WIDTH-1;\r
11376               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11377               break;\r
11378           case'Q':\r
11379               FENcastlingRights[1] = 0;\r
11380               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11381               break;\r
11382           case'k':\r
11383               FENcastlingRights[3] = BOARD_WIDTH-1;\r
11384               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11385               break;\r
11386           case'q':\r
11387               FENcastlingRights[4] = 0;\r
11388               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11389               break;\r
11390           /* Tord! FRC! */\r
11391         }\r
11392     }\r
11393 \r
11394     while(*p==' ') p++;\r
11395 \r
11396 \r
11397     if(*p=='-') {\r
11398         p++; FENepStatus = EP_NONE;\r
11399     } else {\r
11400        char c = *p++ - 'a';\r
11401 \r
11402        if(c < 0 || c >= BOARD_WIDTH) return TRUE;\r
11403        if(*p >= '0' && *p <='9') *p++;\r
11404        FENepStatus = c;\r
11405     }\r
11406 \r
11407     if(sscanf(p, "%d", &i) == 1) {\r
11408         FENrulePlies = i; /* 50-move ply counter */\r
11409         /* (The move number is still ignored)    */\r
11410     }\r
11411  }\r
11412     return TRUE;\r
11413 }\r
11414       \r
11415 void\r
11416 EditPositionPasteFEN(char *fen)\r
11417 {\r
11418   if (fen != NULL) {\r
11419     Board initial_position;\r
11420 \r
11421     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
11422       DisplayError("Bad FEN position in clipboard", 0);\r
11423       return ;\r
11424     } else {\r
11425       int savedBlackPlaysFirst = blackPlaysFirst;\r
11426       EditPositionEvent();\r
11427       blackPlaysFirst = savedBlackPlaysFirst;\r
11428       CopyBoard(boards[0], initial_position);\r
11429           /* [HGM] copy FEN attributes as well */\r
11430           {   int i;\r
11431               initialRulePlies = FENrulePlies;\r
11432               epStatus[0] = FENepStatus;\r
11433               for( i=0; i<nrCastlingRights; i++ )\r
11434                   castlingRights[0][i] = FENcastlingRights[i];\r
11435           }\r
11436       EditPositionDone();\r
11437       DisplayBothClocks();\r
11438       DrawPosition(FALSE, boards[currentMove]);\r
11439     }\r
11440   }\r
11441 }\r