changes by H.G. Muller; version 4.3.4
[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  KnightmateArray[2][BOARD_SIZE] = {\r
429     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
430         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
431     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
432         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
433 };\r
434 \r
435 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
436     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
437         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
438     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
439         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
440 };\r
441 \r
442 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
443     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
444         WhiteKing, WhiteAlfil, WhiteKnight, WhiteRook },\r
445     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
446         BlackKing, BlackAlfil, BlackKnight, BlackRook }\r
447 };\r
448 \r
449 \r
450 #if (BOARD_SIZE>=10)\r
451 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
452     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
453         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
454     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
455         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
456 };\r
457 \r
458 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
459     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
460         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
461     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
462         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
463 };\r
464 \r
465 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
466     { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen, \r
467         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
468     { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen, \r
469         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
470 };\r
471 \r
472 #ifdef GOTHIC\r
473 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
475         WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook },\r
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
477         BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook }\r
478 };\r
479 #else // !GOTHIC\r
480 #define GothicArray CapablancaArray\r
481 #endif // !GOTHIC\r
482 \r
483 #else // !(BOARD_SIZE>=10)\r
484 #define XiangqiPosition FIDEArray\r
485 #define CapablancaArray FIDEArray\r
486 #define GothicArray FIDEArray\r
487 #endif // !(BOARD_SIZE>=10)\r
488 \r
489 #if (BOARD_SIZE>=12)\r
490 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
491     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
492         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
493     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
494         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
495 };\r
496 #else // !(BOARD_SIZE>=12)\r
497 #define CourierArray CapablancaArray\r
498 #endif // !(BOARD_SIZE>=12)\r
499 #endif // FAIRY\r
500 \r
501 \r
502 Board initialPosition;\r
503 \r
504 \r
505 /* Convert str to a rating. Checks for special cases of "----",\r
506 \r
507    "++++", etc. Also strips ()'s */\r
508 int\r
509 string_to_rating(str)\r
510   char *str;\r
511 {\r
512   while(*str && !isdigit(*str)) ++str;\r
513   if (!*str)\r
514     return 0;   /* One of the special "no rating" cases */\r
515   else\r
516     return atoi(str);\r
517 }\r
518 \r
519 void\r
520 ClearProgramStats()\r
521 {\r
522     /* Init programStats */\r
523     programStats.movelist[0] = 0;\r
524     programStats.depth = 0;\r
525     programStats.nr_moves = 0;\r
526     programStats.moves_left = 0;\r
527     programStats.nodes = 0;\r
528     programStats.time = 100;\r
529     programStats.score = 0;\r
530     programStats.got_only_move = 0;\r
531     programStats.got_fail = 0;\r
532     programStats.line_is_book = 0;\r
533 }\r
534 \r
535 void\r
536 InitBackEnd1()\r
537 {\r
538     int matched, min, sec;\r
539 \r
540     GetTimeMark(&programStartTime);\r
541 \r
542     ClearProgramStats();\r
543     programStats.ok_to_send = 1;\r
544     programStats.seen_stat = 0;\r
545 \r
546     /*\r
547      * Initialize game list\r
548      */\r
549     ListNew(&gameList);\r
550 \r
551 \r
552     /*\r
553      * Internet chess server status\r
554      */\r
555     if (appData.icsActive) {\r
556         appData.matchMode = FALSE;\r
557         appData.matchGames = 0;\r
558 #if ZIPPY       \r
559         appData.noChessProgram = !appData.zippyPlay;\r
560 #else\r
561         appData.zippyPlay = FALSE;\r
562         appData.zippyTalk = FALSE;\r
563         appData.noChessProgram = TRUE;\r
564 #endif\r
565         if (*appData.icsHelper != NULLCHAR) {\r
566             appData.useTelnet = TRUE;\r
567             appData.telnetProgram = appData.icsHelper;\r
568         }\r
569     } else {\r
570         appData.zippyTalk = appData.zippyPlay = FALSE;\r
571     }\r
572 \r
573     /* [AS] Initialize pv info list [HGM] and game state */\r
574     {\r
575         int i, j;\r
576 \r
577         for( i=0; i<MAX_MOVES; i++ ) {\r
578             pvInfoList[i].depth = -1;\r
579             epStatus[i]=EP_NONE;\r
580             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
581         }\r
582     }\r
583 \r
584     /*\r
585      * Parse timeControl resource\r
586      */\r
587     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
588                           appData.movesPerSession)) {\r
589         char buf[MSG_SIZ];\r
590         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
591         DisplayFatalError(buf, 0, 2);\r
592     }\r
593 \r
594     /*\r
595      * Parse searchTime resource\r
596      */\r
597     if (*appData.searchTime != NULLCHAR) {\r
598         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
599         if (matched == 1) {\r
600             searchTime = min * 60;\r
601         } else if (matched == 2) {\r
602             searchTime = min * 60 + sec;\r
603         } else {\r
604             char buf[MSG_SIZ];\r
605             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
606             DisplayFatalError(buf, 0, 2);\r
607         }\r
608     }\r
609 \r
610     /* [AS] Adjudication threshold */\r
611     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
612     \r
613     first.which = "first";\r
614     second.which = "second";\r
615     first.maybeThinking = second.maybeThinking = FALSE;\r
616     first.pr = second.pr = NoProc;\r
617     first.isr = second.isr = NULL;\r
618     first.sendTime = second.sendTime = 2;\r
619     first.sendDrawOffers = 1;\r
620     if (appData.firstPlaysBlack) {\r
621         first.twoMachinesColor = "black\n";\r
622         second.twoMachinesColor = "white\n";\r
623     } else {\r
624         first.twoMachinesColor = "white\n";\r
625         second.twoMachinesColor = "black\n";\r
626     }\r
627     first.program = appData.firstChessProgram;\r
628     second.program = appData.secondChessProgram;\r
629     first.host = appData.firstHost;\r
630     second.host = appData.secondHost;\r
631     first.dir = appData.firstDirectory;\r
632     second.dir = appData.secondDirectory;\r
633     first.other = &second;\r
634     second.other = &first;\r
635     first.initString = appData.initString;\r
636     second.initString = appData.secondInitString;\r
637     first.computerString = appData.firstComputerString;\r
638     second.computerString = appData.secondComputerString;\r
639     first.useSigint = second.useSigint = TRUE;\r
640     first.useSigterm = second.useSigterm = TRUE;\r
641     first.reuse = appData.reuseFirst;\r
642     second.reuse = appData.reuseSecond;\r
643     first.useSetboard = second.useSetboard = FALSE;\r
644     first.useSAN = second.useSAN = FALSE;\r
645     first.usePing = second.usePing = FALSE;\r
646     first.lastPing = second.lastPing = 0;\r
647     first.lastPong = second.lastPong = 0;\r
648     first.usePlayother = second.usePlayother = FALSE;\r
649     first.useColors = second.useColors = TRUE;\r
650     first.useUsermove = second.useUsermove = FALSE;\r
651     first.sendICS = second.sendICS = FALSE;\r
652     first.sendName = second.sendName = appData.icsActive;\r
653     first.sdKludge = second.sdKludge = FALSE;\r
654     first.stKludge = second.stKludge = FALSE;\r
655     TidyProgramName(first.program, first.host, first.tidy);\r
656     TidyProgramName(second.program, second.host, second.tidy);\r
657     first.matchWins = second.matchWins = 0;\r
658     strcpy(first.variants, appData.variant);\r
659     strcpy(second.variants, appData.variant);\r
660     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
661     first.analyzing = second.analyzing = FALSE;\r
662     first.initDone = second.initDone = FALSE;\r
663 \r
664     /* New features added by Tord: */\r
665     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
666     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
667     /* End of new features added by Tord. */\r
668 \r
669     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
670     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
671     first.isUCI = appData.firstIsUCI; /* [AS] */\r
672     second.isUCI = appData.secondIsUCI; /* [AS] */\r
673     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
674     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
675 \r
676     if (appData.firstProtocolVersion > PROTOVER ||\r
677         appData.firstProtocolVersion < 1) {\r
678       char buf[MSG_SIZ];\r
679       sprintf(buf, "protocol version %d not supported",\r
680               appData.firstProtocolVersion);\r
681       DisplayFatalError(buf, 0, 2);\r
682     } else {\r
683       first.protocolVersion = appData.firstProtocolVersion;\r
684     }\r
685 \r
686     if (appData.secondProtocolVersion > PROTOVER ||\r
687         appData.secondProtocolVersion < 1) {\r
688       char buf[MSG_SIZ];\r
689       sprintf(buf, "protocol version %d not supported",\r
690               appData.secondProtocolVersion);\r
691       DisplayFatalError(buf, 0, 2);\r
692     } else {\r
693       second.protocolVersion = appData.secondProtocolVersion;\r
694     }\r
695 \r
696     if (appData.icsActive) {\r
697         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
698     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
699         appData.clockMode = FALSE;\r
700         first.sendTime = second.sendTime = 0;\r
701     }\r
702     \r
703 #if ZIPPY\r
704     /* Override some settings from environment variables, for backward\r
705        compatibility.  Unfortunately it's not feasible to have the env\r
706        vars just set defaults, at least in xboard.  Ugh.\r
707     */\r
708     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
709       ZippyInit();\r
710     }\r
711 #endif\r
712     \r
713     if (appData.noChessProgram) {\r
714         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
715                                         + strlen(PATCHLEVEL));\r
716         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
717     } else {\r
718         char *p, *q;\r
719         q = first.program;\r
720         while (*q != ' ' && *q != NULLCHAR) q++;\r
721         p = q;\r
722         while (p > first.program && *(p-1) != '/') p--;\r
723         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
724                                         + strlen(PATCHLEVEL) + (q - p));\r
725         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
726         strncat(programVersion, p, q - p);\r
727     }\r
728 \r
729     if (!appData.icsActive) {\r
730       char buf[MSG_SIZ];\r
731       /* Check for variants that are supported only in ICS mode,\r
732          or not at all.  Some that are accepted here nevertheless\r
733          have bugs; see comments below.\r
734       */\r
735       VariantClass variant = StringToVariant(appData.variant);\r
736       switch (variant) {\r
737       case VariantBughouse:     /* need four players and two boards */\r
738       case VariantKriegspiel:   /* need to hide pieces and move details */\r
739       /* case VariantFischeRandom: (Fabien: moved below) */\r
740         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
741         DisplayFatalError(buf, 0, 2);\r
742         return;\r
743 \r
744       case VariantUnknown:\r
745       case VariantLoadable:\r
746       case Variant29:\r
747       case Variant30:\r
748       case Variant31:\r
749       case Variant32:\r
750       case Variant33:\r
751       case Variant34:\r
752       case Variant35:\r
753       case Variant36:\r
754       default:\r
755         sprintf(buf, "Unknown variant name %s", appData.variant);\r
756         DisplayFatalError(buf, 0, 2);\r
757         return;\r
758 \r
759       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
760       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
761       case VariantGothic:     /* [HGM] should work */\r
762       case VariantCapablanca: /* [HGM] should work */\r
763       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
764       case VariantShogi:      /* [HGM] drops not tested for legality */\r
765       case VariantShowgi:     /* [HGM] not a valid variant */\r
766       case VariantKnightmate: /* [HGM] should work */\r
767       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
768                                  offboard interposition not understood */\r
769       case VariantNormal:     /* definitely works! */\r
770       case VariantWildCastle: /* pieces not automatically shuffled */\r
771       case VariantNoCastle:   /* pieces not automatically shuffled */\r
772       case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */\r
773       case VariantLosers:     /* should work except for win condition,\r
774                                  and doesn't know captures are mandatory */\r
775       case VariantSuicide:    /* should work except for win condition,\r
776                                  and doesn't know captures are mandatory */\r
777       case VariantGiveaway:   /* should work except for win condition,\r
778                                  and doesn't know captures are mandatory */\r
779       case VariantTwoKings:   /* should work */\r
780       case VariantAtomic:     /* should work except for win condition */\r
781       case Variant3Check:     /* should work except for win condition */\r
782       case VariantShatranj:   /* might work if TestLegality is off */\r
783         break;\r
784       }\r
785     }\r
786 }\r
787 \r
788 int NextIntegerFromString( char ** str, long * value )\r
789 {\r
790     int result = -1;\r
791     char * s = *str;\r
792 \r
793     while( *s == ' ' || *s == '\t' ) {\r
794         s++;\r
795     }\r
796 \r
797     *value = 0;\r
798 \r
799     if( *s >= '0' && *s <= '9' ) {\r
800         while( *s >= '0' && *s <= '9' ) {\r
801             *value = *value * 10 + (*s - '0');\r
802             s++;\r
803         }\r
804 \r
805         result = 0;\r
806     }\r
807 \r
808     *str = s;\r
809 \r
810     return result;\r
811 }\r
812 \r
813 int NextTimeControlFromString( char ** str, long * value )\r
814 {\r
815     long temp;\r
816     int result = NextIntegerFromString( str, &temp );\r
817 \r
818     if( result == 0 ) {\r
819         *value = temp * 60; /* Minutes */\r
820         if( **str == ':' ) {\r
821             (*str)++;\r
822             result = NextIntegerFromString( str, &temp );\r
823             *value += temp; /* Seconds */\r
824         }\r
825     }\r
826 \r
827     return result;\r
828 }\r
829 \r
830 int GetTimeControlForWhite()\r
831 {\r
832     int result = timeControl;\r
833 \r
834     return result;\r
835 }\r
836 \r
837 int GetTimeControlForBlack()\r
838 {\r
839     int result = timeControl;\r
840 \r
841     if( timeControl_2 > 0 ) {\r
842         result = timeControl_2;\r
843     }\r
844 \r
845     return result;\r
846 }\r
847 \r
848 int\r
849 ParseTimeControl(tc, ti, mps)\r
850      char *tc;\r
851      int ti;\r
852      int mps;\r
853 {\r
854 #if 0\r
855     int matched, min, sec;\r
856 \r
857     matched = sscanf(tc, "%d:%d", &min, &sec);\r
858     if (matched == 1) {\r
859         timeControl = min * 60 * 1000;\r
860     } else if (matched == 2) {\r
861         timeControl = (min * 60 + sec) * 1000;\r
862     } else {\r
863         return FALSE;\r
864     }\r
865 #else\r
866     long tc1;\r
867     long tc2;\r
868 \r
869     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
870         return FALSE;\r
871     }\r
872 \r
873     if( *tc == '/' ) {\r
874         /* Parse second time control */\r
875         tc++;\r
876 \r
877         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
878             return FALSE;\r
879         }\r
880 \r
881         if( tc2 == 0 ) {\r
882             return FALSE;\r
883         }\r
884 \r
885         timeControl_2 = tc2 * 1000;\r
886     }\r
887     else {\r
888         timeControl_2 = 0;\r
889     }\r
890 \r
891     if( tc1 == 0 ) {\r
892         return FALSE;\r
893     }\r
894 \r
895     timeControl = tc1 * 1000;\r
896 #endif\r
897 \r
898     if (ti >= 0) {\r
899         timeIncrement = ti * 1000;  /* convert to ms */\r
900         movesPerSession = 0;\r
901     } else {\r
902         timeIncrement = 0;\r
903         movesPerSession = mps;\r
904     }\r
905     return TRUE;\r
906 }\r
907 \r
908 void\r
909 InitBackEnd2()\r
910 {\r
911     if (appData.debugMode) {\r
912         fprintf(debugFP, "%s\n", programVersion);\r
913     }\r
914 \r
915     if (appData.matchGames > 0) {\r
916         appData.matchMode = TRUE;\r
917     } else if (appData.matchMode) {\r
918         appData.matchGames = 1;\r
919     }\r
920     Reset(TRUE, FALSE);\r
921     if (appData.noChessProgram || first.protocolVersion == 1) {\r
922       InitBackEnd3();\r
923     } else {\r
924       /* kludge: allow timeout for initial "feature" commands */\r
925       FreezeUI();\r
926       DisplayMessage("", "Starting chess program");\r
927       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
928     }\r
929 }\r
930 \r
931 void\r
932 InitBackEnd3 P((void))\r
933 {\r
934     GameMode initialMode;\r
935     char buf[MSG_SIZ];\r
936     int err;\r
937 \r
938     InitChessProgram(&first);\r
939 \r
940     if (appData.icsActive) {\r
941         err = establish();\r
942         if (err != 0) {\r
943             if (*appData.icsCommPort != NULLCHAR) {\r
944                 sprintf(buf, "Could not open comm port %s",  \r
945                         appData.icsCommPort);\r
946             } else {\r
947                 sprintf(buf, "Could not connect to host %s, port %s",  \r
948                         appData.icsHost, appData.icsPort);\r
949             }\r
950             DisplayFatalError(buf, err, 1);\r
951             return;\r
952         }\r
953         SetICSMode();\r
954         telnetISR =\r
955           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
956         fromUserISR =\r
957           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
958     } else if (appData.noChessProgram) {\r
959         SetNCPMode();\r
960     } else {\r
961         SetGNUMode();\r
962     }\r
963 \r
964     if (*appData.cmailGameName != NULLCHAR) {\r
965         SetCmailMode();\r
966         OpenLoopback(&cmailPR);\r
967         cmailISR =\r
968           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
969     }\r
970     \r
971     ThawUI();\r
972     DisplayMessage("", "");\r
973     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
974       initialMode = BeginningOfGame;\r
975     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
976       initialMode = TwoMachinesPlay;\r
977     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
978       initialMode = AnalyzeFile; \r
979     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
980       initialMode = AnalyzeMode;\r
981     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
982       initialMode = MachinePlaysWhite;\r
983     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
984       initialMode = MachinePlaysBlack;\r
985     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
986       initialMode = EditGame;\r
987     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
988       initialMode = EditPosition;\r
989     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
990       initialMode = Training;\r
991     } else {\r
992       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
993       DisplayFatalError(buf, 0, 2);\r
994       return;\r
995     }\r
996 \r
997     if (appData.matchMode) {\r
998         /* Set up machine vs. machine match */\r
999         if (appData.noChessProgram) {\r
1000             DisplayFatalError("Can't have a match with no chess programs",\r
1001                               0, 2);\r
1002             return;\r
1003         }\r
1004         matchMode = TRUE;\r
1005         matchGame = 1;\r
1006         if (*appData.loadGameFile != NULLCHAR) {\r
1007             if (!LoadGameFromFile(appData.loadGameFile,\r
1008                                   appData.loadGameIndex,\r
1009                                   appData.loadGameFile, FALSE)) {\r
1010                 DisplayFatalError("Bad game file", 0, 1);\r
1011                 return;\r
1012             }\r
1013         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1014             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1015                                       appData.loadPositionIndex,\r
1016                                       appData.loadPositionFile)) {\r
1017                 DisplayFatalError("Bad position file", 0, 1);\r
1018                 return;\r
1019             }\r
1020         }\r
1021         TwoMachinesEvent();\r
1022     } else if (*appData.cmailGameName != NULLCHAR) {\r
1023         /* Set up cmail mode */\r
1024         ReloadCmailMsgEvent(TRUE);\r
1025     } else {\r
1026         /* Set up other modes */\r
1027         if (initialMode == AnalyzeFile) {\r
1028           if (*appData.loadGameFile == NULLCHAR) {\r
1029             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1030             return;\r
1031           }\r
1032         }\r
1033         if (*appData.loadGameFile != NULLCHAR) {\r
1034             (void) LoadGameFromFile(appData.loadGameFile,\r
1035                                     appData.loadGameIndex,\r
1036                                     appData.loadGameFile, TRUE);\r
1037         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1038             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1039                                         appData.loadPositionIndex,\r
1040                                         appData.loadPositionFile);\r
1041         }\r
1042         if (initialMode == AnalyzeMode) {\r
1043           if (appData.noChessProgram) {\r
1044             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1045             return;\r
1046           }\r
1047           if (appData.icsActive) {\r
1048             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1049             return;\r
1050           }\r
1051           AnalyzeModeEvent();\r
1052         } else if (initialMode == AnalyzeFile) {\r
1053           ShowThinkingEvent(TRUE);\r
1054           AnalyzeFileEvent();\r
1055           AnalysisPeriodicEvent(1);\r
1056         } else if (initialMode == MachinePlaysWhite) {\r
1057           if (appData.noChessProgram) {\r
1058             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1059                               0, 2);\r
1060             return;\r
1061           }\r
1062           if (appData.icsActive) {\r
1063             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1064                               0, 2);\r
1065             return;\r
1066           }\r
1067           MachineWhiteEvent();\r
1068         } else if (initialMode == MachinePlaysBlack) {\r
1069           if (appData.noChessProgram) {\r
1070             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1071                               0, 2);\r
1072             return;\r
1073           }\r
1074           if (appData.icsActive) {\r
1075             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1076                               0, 2);\r
1077             return;\r
1078           }\r
1079           MachineBlackEvent();\r
1080         } else if (initialMode == TwoMachinesPlay) {\r
1081           if (appData.noChessProgram) {\r
1082             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1083                               0, 2);\r
1084             return;\r
1085           }\r
1086           if (appData.icsActive) {\r
1087             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1088                               0, 2);\r
1089             return;\r
1090           }\r
1091           TwoMachinesEvent();\r
1092         } else if (initialMode == EditGame) {\r
1093           EditGameEvent();\r
1094         } else if (initialMode == EditPosition) {\r
1095           EditPositionEvent();\r
1096         } else if (initialMode == Training) {\r
1097           if (*appData.loadGameFile == NULLCHAR) {\r
1098             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1099             return;\r
1100           }\r
1101           TrainingEvent();\r
1102         }\r
1103     }\r
1104 }\r
1105 \r
1106 /*\r
1107  * Establish will establish a contact to a remote host.port.\r
1108  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1109  *  used to talk to the host.\r
1110  * Returns 0 if okay, error code if not.\r
1111  */\r
1112 int\r
1113 establish()\r
1114 {\r
1115     char buf[MSG_SIZ];\r
1116 \r
1117     if (*appData.icsCommPort != NULLCHAR) {\r
1118         /* Talk to the host through a serial comm port */\r
1119         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1120 \r
1121     } else if (*appData.gateway != NULLCHAR) {\r
1122         if (*appData.remoteShell == NULLCHAR) {\r
1123             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1124             sprintf(buf, "%s %s %s",\r
1125                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1126             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1127 \r
1128         } else {\r
1129             /* Use the rsh program to run telnet program on a gateway host */\r
1130             if (*appData.remoteUser == NULLCHAR) {\r
1131                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1132                         appData.gateway, appData.telnetProgram,\r
1133                         appData.icsHost, appData.icsPort);\r
1134             } else {\r
1135                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1136                         appData.remoteShell, appData.gateway, \r
1137                         appData.remoteUser, appData.telnetProgram,\r
1138                         appData.icsHost, appData.icsPort);\r
1139             }\r
1140             return StartChildProcess(buf, "", &icsPR);\r
1141 \r
1142         }\r
1143     } else if (appData.useTelnet) {\r
1144         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1145 \r
1146     } else {\r
1147         /* TCP socket interface differs somewhat between\r
1148            Unix and NT; handle details in the front end.\r
1149            */\r
1150         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1151     }\r
1152 }\r
1153 \r
1154 void\r
1155 show_bytes(fp, buf, count)\r
1156      FILE *fp;\r
1157      char *buf;\r
1158      int count;\r
1159 {\r
1160     while (count--) {\r
1161         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1162             fprintf(fp, "\\%03o", *buf & 0xff);\r
1163         } else {\r
1164             putc(*buf, fp);\r
1165         }\r
1166         buf++;\r
1167     }\r
1168     fflush(fp);\r
1169 }\r
1170 \r
1171 /* Returns an errno value */\r
1172 int\r
1173 OutputMaybeTelnet(pr, message, count, outError)\r
1174      ProcRef pr;\r
1175      char *message;\r
1176      int count;\r
1177      int *outError;\r
1178 {\r
1179     char buf[8192], *p, *q, *buflim;\r
1180     int left, newcount, outcount;\r
1181 \r
1182     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1183         *appData.gateway != NULLCHAR) {\r
1184         if (appData.debugMode) {\r
1185             fprintf(debugFP, ">ICS: ");\r
1186             show_bytes(debugFP, message, count);\r
1187             fprintf(debugFP, "\n");\r
1188         }\r
1189         return OutputToProcess(pr, message, count, outError);\r
1190     }\r
1191 \r
1192     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1193     p = message;\r
1194     q = buf;\r
1195     left = count;\r
1196     newcount = 0;\r
1197     while (left) {\r
1198         if (q >= buflim) {\r
1199             if (appData.debugMode) {\r
1200                 fprintf(debugFP, ">ICS: ");\r
1201                 show_bytes(debugFP, buf, newcount);\r
1202                 fprintf(debugFP, "\n");\r
1203             }\r
1204             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1205             if (outcount < newcount) return -1; /* to be sure */\r
1206             q = buf;\r
1207             newcount = 0;\r
1208         }\r
1209         if (*p == '\n') {\r
1210             *q++ = '\r';\r
1211             newcount++;\r
1212         } else if (((unsigned char) *p) == TN_IAC) {\r
1213             *q++ = (char) TN_IAC;\r
1214             newcount ++;\r
1215         }\r
1216         *q++ = *p++;\r
1217         newcount++;\r
1218         left--;\r
1219     }\r
1220     if (appData.debugMode) {\r
1221         fprintf(debugFP, ">ICS: ");\r
1222         show_bytes(debugFP, buf, newcount);\r
1223         fprintf(debugFP, "\n");\r
1224     }\r
1225     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1226     if (outcount < newcount) return -1; /* to be sure */\r
1227     return count;\r
1228 }\r
1229 \r
1230 void\r
1231 read_from_player(isr, closure, message, count, error)\r
1232      InputSourceRef isr;\r
1233      VOIDSTAR closure;\r
1234      char *message;\r
1235      int count;\r
1236      int error;\r
1237 {\r
1238     int outError, outCount;\r
1239     static int gotEof = 0;\r
1240 \r
1241     /* Pass data read from player on to ICS */\r
1242     if (count > 0) {\r
1243         gotEof = 0;\r
1244         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1245         if (outCount < count) {\r
1246             DisplayFatalError("Error writing to ICS", outError, 1);\r
1247         }\r
1248     } else if (count < 0) {\r
1249         RemoveInputSource(isr);\r
1250         DisplayFatalError("Error reading from keyboard", error, 1);\r
1251     } else if (gotEof++ > 0) {\r
1252         RemoveInputSource(isr);\r
1253         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1254     }\r
1255 }\r
1256 \r
1257 void\r
1258 SendToICS(s)\r
1259      char *s;\r
1260 {\r
1261     int count, outCount, outError;\r
1262 \r
1263     if (icsPR == NULL) return;\r
1264 \r
1265     count = strlen(s);\r
1266     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1267     if (outCount < count) {\r
1268         DisplayFatalError("Error writing to ICS", outError, 1);\r
1269     }\r
1270 }\r
1271 \r
1272 /* This is used for sending logon scripts to the ICS. Sending\r
1273    without a delay causes problems when using timestamp on ICC\r
1274    (at least on my machine). */\r
1275 void\r
1276 SendToICSDelayed(s,msdelay)\r
1277      char *s;\r
1278      long msdelay;\r
1279 {\r
1280     int count, outCount, outError;\r
1281 \r
1282     if (icsPR == NULL) return;\r
1283 \r
1284     count = strlen(s);\r
1285     if (appData.debugMode) {\r
1286         fprintf(debugFP, ">ICS: ");\r
1287         show_bytes(debugFP, s, count);\r
1288         fprintf(debugFP, "\n");\r
1289     }\r
1290     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1291                                       msdelay);\r
1292     if (outCount < count) {\r
1293         DisplayFatalError("Error writing to ICS", outError, 1);\r
1294     }\r
1295 }\r
1296 \r
1297 \r
1298 /* Remove all highlighting escape sequences in s\r
1299    Also deletes any suffix starting with '(' \r
1300    */\r
1301 char *\r
1302 StripHighlightAndTitle(s)\r
1303      char *s;\r
1304 {\r
1305     static char retbuf[MSG_SIZ];\r
1306     char *p = retbuf;\r
1307 \r
1308     while (*s != NULLCHAR) {\r
1309         while (*s == '\033') {\r
1310             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1311             if (*s != NULLCHAR) s++;\r
1312         }\r
1313         while (*s != NULLCHAR && *s != '\033') {\r
1314             if (*s == '(' || *s == '[') {\r
1315                 *p = NULLCHAR;\r
1316                 return retbuf;\r
1317             }\r
1318             *p++ = *s++;\r
1319         }\r
1320     }\r
1321     *p = NULLCHAR;\r
1322     return retbuf;\r
1323 }\r
1324 \r
1325 /* Remove all highlighting escape sequences in s */\r
1326 char *\r
1327 StripHighlight(s)\r
1328      char *s;\r
1329 {\r
1330     static char retbuf[MSG_SIZ];\r
1331     char *p = retbuf;\r
1332 \r
1333     while (*s != NULLCHAR) {\r
1334         while (*s == '\033') {\r
1335             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1336             if (*s != NULLCHAR) s++;\r
1337         }\r
1338         while (*s != NULLCHAR && *s != '\033') {\r
1339             *p++ = *s++;\r
1340         }\r
1341     }\r
1342     *p = NULLCHAR;\r
1343     return retbuf;\r
1344 }\r
1345 \r
1346 char *variantNames[] = VARIANT_NAMES;\r
1347 char *\r
1348 VariantName(v)\r
1349      VariantClass v;\r
1350 {\r
1351     return variantNames[v];\r
1352 }\r
1353 \r
1354 \r
1355 /* Identify a variant from the strings the chess servers use or the\r
1356    PGN Variant tag names we use. */\r
1357 VariantClass\r
1358 StringToVariant(e)\r
1359      char *e;\r
1360 {\r
1361     char *p;\r
1362     int wnum = -1;\r
1363     VariantClass v = VariantNormal;\r
1364     int i, found = FALSE;\r
1365     char buf[MSG_SIZ];\r
1366 \r
1367     if (!e) return v;\r
1368 \r
1369     /* [HGM] skip over optional board-size prefixes */\r
1370     if( sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1371         while( *e++ != '_');\r
1372     } else if( sscanf(e, "%dx%d_", &i, &i) == 2 ) {\r
1373         while( *e++ != '_');\r
1374     }\r
1375 \r
1376     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1377       if (StrCaseStr(e, variantNames[i])) {\r
1378         v = (VariantClass) i;\r
1379         found = TRUE;\r
1380         break;\r
1381       }\r
1382     }\r
1383 \r
1384     if (!found) {\r
1385       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1386           || StrCaseStr(e, "wild/fr")) {\r
1387         v = VariantFischeRandom;\r
1388       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1389                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1390         p += i;\r
1391         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1392         if (isdigit(*p)) {\r
1393           wnum = atoi(p);\r
1394         } else {\r
1395           wnum = -1;\r
1396         }\r
1397         switch (wnum) {\r
1398         case 0: /* FICS only, actually */\r
1399         case 1:\r
1400           /* Castling legal even if K starts on d-file */\r
1401           v = VariantWildCastle;\r
1402           break;\r
1403         case 2:\r
1404         case 3:\r
1405         case 4:\r
1406           /* Castling illegal even if K & R happen to start in\r
1407              normal positions. */\r
1408           v = VariantNoCastle;\r
1409           break;\r
1410         case 5:\r
1411         case 7:\r
1412         case 8:\r
1413         case 10:\r
1414         case 11:\r
1415         case 12:\r
1416         case 13:\r
1417         case 14:\r
1418         case 15:\r
1419         case 18:\r
1420         case 19:\r
1421           /* Castling legal iff K & R start in normal positions */\r
1422           v = VariantNormal;\r
1423           break;\r
1424         case 6:\r
1425         case 20:\r
1426         case 21:\r
1427           /* Special wilds for position setup; unclear what to do here */\r
1428           v = VariantLoadable;\r
1429           break;\r
1430         case 9:\r
1431           /* Bizarre ICC game */\r
1432           v = VariantTwoKings;\r
1433           break;\r
1434         case 16:\r
1435           v = VariantKriegspiel;\r
1436           break;\r
1437         case 17:\r
1438           v = VariantLosers;\r
1439           break;\r
1440         case 22:\r
1441           v = VariantFischeRandom;\r
1442           break;\r
1443         case 23:\r
1444           v = VariantCrazyhouse;\r
1445           break;\r
1446         case 24:\r
1447           v = VariantBughouse;\r
1448           break;\r
1449         case 25:\r
1450           v = Variant3Check;\r
1451           break;\r
1452         case 26:\r
1453           /* Not quite the same as FICS suicide! */\r
1454           v = VariantGiveaway;\r
1455           break;\r
1456         case 27:\r
1457           v = VariantAtomic;\r
1458           break;\r
1459         case 28:\r
1460           v = VariantShatranj;\r
1461           break;\r
1462 \r
1463         /* Temporary names for future ICC types.  The name *will* change in \r
1464            the next xboard/WinBoard release after ICC defines it. */\r
1465         case 29:\r
1466           v = Variant29;\r
1467           break;\r
1468         case 30:\r
1469           v = Variant30;\r
1470           break;\r
1471         case 31:\r
1472           v = Variant31;\r
1473           break;\r
1474         case 32:\r
1475           v = Variant32;\r
1476           break;\r
1477         case 33:\r
1478           v = Variant33;\r
1479           break;\r
1480         case 34:\r
1481           v = Variant34;\r
1482           break;\r
1483         case 35:\r
1484           v = Variant35;\r
1485           break;\r
1486         case 36:\r
1487           v = Variant36;\r
1488           break;\r
1489         case 37:\r
1490           v = VariantShogi;\r
1491           break;\r
1492         case 38:\r
1493           v = VariantXiangqi;\r
1494           break;\r
1495         case 39:\r
1496           v = VariantCourier;\r
1497           break;\r
1498         case 40:\r
1499           v = VariantGothic;\r
1500           break;\r
1501         case 41:\r
1502           v = VariantCapablanca;\r
1503           break;\r
1504         case 42:\r
1505           v = VariantKnightmate;\r
1506           break;\r
1507         case 43:\r
1508           v = VariantFairy;\r
1509           break;\r
1510         case 44:\r
1511           v = VariantShowgi;\r
1512           break;\r
1513 \r
1514         case -1:\r
1515           /* Found "wild" or "w" in the string but no number;\r
1516              must assume it's normal chess. */\r
1517           v = VariantNormal;\r
1518           break;\r
1519         default:\r
1520           sprintf(buf, "Unknown wild type %d", wnum);\r
1521           DisplayError(buf, 0);\r
1522           v = VariantUnknown;\r
1523           break;\r
1524         }\r
1525       }\r
1526     }\r
1527     if (appData.debugMode) {\r
1528       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1529               e, wnum, VariantName(v));\r
1530     }\r
1531     return v;\r
1532 }\r
1533 \r
1534 static int leftover_start = 0, leftover_len = 0;\r
1535 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1536 \r
1537 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1538    advance *index beyond it, and set leftover_start to the new value of\r
1539    *index; else return FALSE.  If pattern contains the character '*', it\r
1540    matches any sequence of characters not containing '\r', '\n', or the\r
1541    character following the '*' (if any), and the matched sequence(s) are\r
1542    copied into star_match.\r
1543    */\r
1544 int\r
1545 looking_at(buf, index, pattern)\r
1546      char *buf;\r
1547      int *index;\r
1548      char *pattern;\r
1549 {\r
1550     char *bufp = &buf[*index], *patternp = pattern;\r
1551     int star_count = 0;\r
1552     char *matchp = star_match[0];\r
1553     \r
1554     for (;;) {\r
1555         if (*patternp == NULLCHAR) {\r
1556             *index = leftover_start = bufp - buf;\r
1557             *matchp = NULLCHAR;\r
1558             return TRUE;\r
1559         }\r
1560         if (*bufp == NULLCHAR) return FALSE;\r
1561         if (*patternp == '*') {\r
1562             if (*bufp == *(patternp + 1)) {\r
1563                 *matchp = NULLCHAR;\r
1564                 matchp = star_match[++star_count];\r
1565                 patternp += 2;\r
1566                 bufp++;\r
1567                 continue;\r
1568             } else if (*bufp == '\n' || *bufp == '\r') {\r
1569                 patternp++;\r
1570                 if (*patternp == NULLCHAR)\r
1571                   continue;\r
1572                 else\r
1573                   return FALSE;\r
1574             } else {\r
1575                 *matchp++ = *bufp++;\r
1576                 continue;\r
1577             }\r
1578         }\r
1579         if (*patternp != *bufp) return FALSE;\r
1580         patternp++;\r
1581         bufp++;\r
1582     }\r
1583 }\r
1584 \r
1585 void\r
1586 SendToPlayer(data, length)\r
1587      char *data;\r
1588      int length;\r
1589 {\r
1590     int error, outCount;\r
1591     outCount = OutputToProcess(NoProc, data, length, &error);\r
1592     if (outCount < length) {\r
1593         DisplayFatalError("Error writing to display", error, 1);\r
1594     }\r
1595 }\r
1596 \r
1597 void\r
1598 PackHolding(packed, holding)\r
1599      char packed[];\r
1600      char *holding;\r
1601 {\r
1602     char *p = holding;\r
1603     char *q = packed;\r
1604     int runlength = 0;\r
1605     int curr = 9999;\r
1606     do {\r
1607         if (*p == curr) {\r
1608             runlength++;\r
1609         } else {\r
1610             switch (runlength) {\r
1611               case 0:\r
1612                 break;\r
1613               case 1:\r
1614                 *q++ = curr;\r
1615                 break;\r
1616               case 2:\r
1617                 *q++ = curr;\r
1618                 *q++ = curr;\r
1619                 break;\r
1620               default:\r
1621                 sprintf(q, "%d", runlength);\r
1622                 while (*q) q++;\r
1623                 *q++ = curr;\r
1624                 break;\r
1625             }\r
1626             runlength = 1;\r
1627             curr = *p;\r
1628         }\r
1629     } while (*p++);\r
1630     *q = NULLCHAR;\r
1631 }\r
1632 \r
1633 /* Telnet protocol requests from the front end */\r
1634 void\r
1635 TelnetRequest(ddww, option)\r
1636      unsigned char ddww, option;\r
1637 {\r
1638     unsigned char msg[3];\r
1639     int outCount, outError;\r
1640 \r
1641     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1642 \r
1643     if (appData.debugMode) {\r
1644         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1645         switch (ddww) {\r
1646           case TN_DO:\r
1647             ddwwStr = "DO";\r
1648             break;\r
1649           case TN_DONT:\r
1650             ddwwStr = "DONT";\r
1651             break;\r
1652           case TN_WILL:\r
1653             ddwwStr = "WILL";\r
1654             break;\r
1655           case TN_WONT:\r
1656             ddwwStr = "WONT";\r
1657             break;\r
1658           default:\r
1659             ddwwStr = buf1;\r
1660             sprintf(buf1, "%d", ddww);\r
1661             break;\r
1662         }\r
1663         switch (option) {\r
1664           case TN_ECHO:\r
1665             optionStr = "ECHO";\r
1666             break;\r
1667           default:\r
1668             optionStr = buf2;\r
1669             sprintf(buf2, "%d", option);\r
1670             break;\r
1671         }\r
1672         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1673     }\r
1674     msg[0] = TN_IAC;\r
1675     msg[1] = ddww;\r
1676     msg[2] = option;\r
1677     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1678     if (outCount < 3) {\r
1679         DisplayFatalError("Error writing to ICS", outError, 1);\r
1680     }\r
1681 }\r
1682 \r
1683 void\r
1684 DoEcho()\r
1685 {\r
1686     if (!appData.icsActive) return;\r
1687     TelnetRequest(TN_DO, TN_ECHO);\r
1688 }\r
1689 \r
1690 void\r
1691 DontEcho()\r
1692 {\r
1693     if (!appData.icsActive) return;\r
1694     TelnetRequest(TN_DONT, TN_ECHO);\r
1695 }\r
1696 \r
1697 void\r
1698 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1699 {\r
1700     /* put the holdings sent to us by the server on the board holdings area */\r
1701     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1702     char p;\r
1703     ChessSquare piece;\r
1704 \r
1705     if(gameInfo.holdingsWidth < 1)  return;\r
1706 \r
1707     if( (int)lowestPiece >= BlackPawn ) {\r
1708         holdingsColumn = 0;\r
1709         countsColumn = 1;\r
1710         holdingsStartRow = BOARD_HEIGHT-1;\r
1711         direction = 1;\r
1712     } else {\r
1713         holdingsColumn = BOARD_WIDTH-1;\r
1714         countsColumn = BOARD_WIDTH-2;\r
1715         holdingsStartRow = 0;\r
1716         direction = 1;\r
1717     }\r
1718 \r
1719     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1720         board[i][holdingsColumn] = EmptySquare;\r
1721         board[i][countsColumn]   = (ChessSquare) 0;\r
1722     }\r
1723     while( (p=*holdings++) != NULLCHAR ) {\r
1724         piece = CharToPiece( ToUpper(p) );\r
1725         if(piece == EmptySquare) continue;\r
1726         j = (int) piece - (int) WhitePawn;\r
1727         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1728         if(j < 0) continue;               /* should not happen */\r
1729         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1730         board[holdingsStartRow+i*direction][holdingsColumn] = piece;\r
1731         board[holdingsStartRow+i*direction][countsColumn]++;\r
1732     }\r
1733 \r
1734 }\r
1735 \r
1736 static int loggedOn = FALSE;\r
1737 \r
1738 /*-- Game start info cache: --*/\r
1739 int gs_gamenum;\r
1740 char gs_kind[MSG_SIZ];\r
1741 static char player1Name[128] = "";\r
1742 static char player2Name[128] = "";\r
1743 static int player1Rating = -1;\r
1744 static int player2Rating = -1;\r
1745 /*----------------------------*/\r
1746 \r
1747 ColorClass curColor = ColorNormal;\r
1748 \r
1749 void\r
1750 read_from_ics(isr, closure, data, count, error)\r
1751      InputSourceRef isr;\r
1752      VOIDSTAR closure;\r
1753      char *data;\r
1754      int count;\r
1755      int error;\r
1756 {\r
1757 #define BUF_SIZE 8192\r
1758 #define STARTED_NONE 0\r
1759 #define STARTED_MOVES 1\r
1760 #define STARTED_BOARD 2\r
1761 #define STARTED_OBSERVE 3\r
1762 #define STARTED_HOLDINGS 4\r
1763 #define STARTED_CHATTER 5\r
1764 #define STARTED_COMMENT 6\r
1765 #define STARTED_MOVES_NOHIDE 7\r
1766     \r
1767     static int started = STARTED_NONE;\r
1768     static char parse[20000];\r
1769     static int parse_pos = 0;\r
1770     static char buf[BUF_SIZE + 1];\r
1771     static int firstTime = TRUE, intfSet = FALSE;\r
1772     static ColorClass prevColor = ColorNormal;\r
1773     static int savingComment = FALSE;\r
1774     char str[500];\r
1775     int i, oldi;\r
1776     int buf_len;\r
1777     int next_out;\r
1778     int tkind;\r
1779     char *p;\r
1780 \r
1781 #ifdef WIN32\r
1782     if (appData.debugMode) {\r
1783       if (!error) {\r
1784         fprintf(debugFP, "<ICS: ");\r
1785         show_bytes(debugFP, data, count);\r
1786         fprintf(debugFP, "\n");\r
1787       }\r
1788     }\r
1789 #endif\r
1790 \r
1791     if (count > 0) {\r
1792         /* If last read ended with a partial line that we couldn't parse,\r
1793            prepend it to the new read and try again. */\r
1794         if (leftover_len > 0) {\r
1795             for (i=0; i<leftover_len; i++)\r
1796               buf[i] = buf[leftover_start + i];\r
1797         }\r
1798 \r
1799         /* Copy in new characters, removing nulls and \r's */\r
1800         buf_len = leftover_len;\r
1801         for (i = 0; i < count; i++) {\r
1802             if (data[i] != NULLCHAR && data[i] != '\r')\r
1803               buf[buf_len++] = data[i];\r
1804         }\r
1805 \r
1806         buf[buf_len] = NULLCHAR;\r
1807         next_out = leftover_len;\r
1808         leftover_start = 0;\r
1809         \r
1810         i = 0;\r
1811         while (i < buf_len) {\r
1812             /* Deal with part of the TELNET option negotiation\r
1813                protocol.  We refuse to do anything beyond the\r
1814                defaults, except that we allow the WILL ECHO option,\r
1815                which ICS uses to turn off password echoing when we are\r
1816                directly connected to it.  We reject this option\r
1817                if localLineEditing mode is on (always on in xboard)\r
1818                and we are talking to port 23, which might be a real\r
1819                telnet server that will try to keep WILL ECHO on permanently.\r
1820              */\r
1821             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
1822                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
1823                 unsigned char option;\r
1824                 oldi = i;\r
1825                 switch ((unsigned char) buf[++i]) {\r
1826                   case TN_WILL:\r
1827                     if (appData.debugMode)\r
1828                       fprintf(debugFP, "\n<WILL ");\r
1829                     switch (option = (unsigned char) buf[++i]) {\r
1830                       case TN_ECHO:\r
1831                         if (appData.debugMode)\r
1832                           fprintf(debugFP, "ECHO ");\r
1833                         /* Reply only if this is a change, according\r
1834                            to the protocol rules. */\r
1835                         if (remoteEchoOption) break;\r
1836                         if (appData.localLineEditing &&\r
1837                             atoi(appData.icsPort) == TN_PORT) {\r
1838                             TelnetRequest(TN_DONT, TN_ECHO);\r
1839                         } else {\r
1840                             EchoOff();\r
1841                             TelnetRequest(TN_DO, TN_ECHO);\r
1842                             remoteEchoOption = TRUE;\r
1843                         }\r
1844                         break;\r
1845                       default:\r
1846                         if (appData.debugMode)\r
1847                           fprintf(debugFP, "%d ", option);\r
1848                         /* Whatever this is, we don't want it. */\r
1849                         TelnetRequest(TN_DONT, option);\r
1850                         break;\r
1851                     }\r
1852                     break;\r
1853                   case TN_WONT:\r
1854                     if (appData.debugMode)\r
1855                       fprintf(debugFP, "\n<WONT ");\r
1856                     switch (option = (unsigned char) buf[++i]) {\r
1857                       case TN_ECHO:\r
1858                         if (appData.debugMode)\r
1859                           fprintf(debugFP, "ECHO ");\r
1860                         /* Reply only if this is a change, according\r
1861                            to the protocol rules. */\r
1862                         if (!remoteEchoOption) break;\r
1863                         EchoOn();\r
1864                         TelnetRequest(TN_DONT, TN_ECHO);\r
1865                         remoteEchoOption = FALSE;\r
1866                         break;\r
1867                       default:\r
1868                         if (appData.debugMode)\r
1869                           fprintf(debugFP, "%d ", (unsigned char) option);\r
1870                         /* Whatever this is, it must already be turned\r
1871                            off, because we never agree to turn on\r
1872                            anything non-default, so according to the\r
1873                            protocol rules, we don't reply. */\r
1874                         break;\r
1875                     }\r
1876                     break;\r
1877                   case TN_DO:\r
1878                     if (appData.debugMode)\r
1879                       fprintf(debugFP, "\n<DO ");\r
1880                     switch (option = (unsigned char) buf[++i]) {\r
1881                       default:\r
1882                         /* Whatever this is, we refuse to do it. */\r
1883                         if (appData.debugMode)\r
1884                           fprintf(debugFP, "%d ", option);\r
1885                         TelnetRequest(TN_WONT, option);\r
1886                         break;\r
1887                     }\r
1888                     break;\r
1889                   case TN_DONT:\r
1890                     if (appData.debugMode)\r
1891                       fprintf(debugFP, "\n<DONT ");\r
1892                     switch (option = (unsigned char) buf[++i]) {\r
1893                       default:\r
1894                         if (appData.debugMode)\r
1895                           fprintf(debugFP, "%d ", option);\r
1896                         /* Whatever this is, we are already not doing\r
1897                            it, because we never agree to do anything\r
1898                            non-default, so according to the protocol\r
1899                            rules, we don't reply. */\r
1900                         break;\r
1901                     }\r
1902                     break;\r
1903                   case TN_IAC:\r
1904                     if (appData.debugMode)\r
1905                       fprintf(debugFP, "\n<IAC ");\r
1906                     /* Doubled IAC; pass it through */\r
1907                     i--;\r
1908                     break;\r
1909                   default:\r
1910                     if (appData.debugMode)\r
1911                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
1912                     /* Drop all other telnet commands on the floor */\r
1913                     break;\r
1914                 }\r
1915                 if (oldi > next_out)\r
1916                   SendToPlayer(&buf[next_out], oldi - next_out);\r
1917                 if (++i > next_out)\r
1918                   next_out = i;\r
1919                 continue;\r
1920             }\r
1921                 \r
1922             /* OK, this at least will *usually* work */\r
1923             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
1924                 loggedOn = TRUE;\r
1925             }\r
1926             \r
1927             if (loggedOn && !intfSet) {\r
1928                 if (ics_type == ICS_ICC) {\r
1929                   sprintf(str,\r
1930                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
1931                           programVersion);\r
1932 \r
1933                 } else if (ics_type == ICS_CHESSNET) {\r
1934                   sprintf(str, "/style 12\n");\r
1935                 } else {\r
1936                   strcpy(str, "alias $ @\n$set interface ");\r
1937                   strcat(str, programVersion);\r
1938                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
1939 #ifdef WIN32\r
1940                   strcat(str, "$iset nohighlight 1\n");\r
1941 #endif\r
1942                   strcat(str, "$iset lock 1\n$style 12\n");\r
1943                 }\r
1944                 SendToICS(str);\r
1945                 intfSet = TRUE;\r
1946             }\r
1947 \r
1948             if (started == STARTED_COMMENT) {\r
1949                 /* Accumulate characters in comment */\r
1950                 parse[parse_pos++] = buf[i];\r
1951                 if (buf[i] == '\n') {\r
1952                     parse[parse_pos] = NULLCHAR;\r
1953                     AppendComment(forwardMostMove, StripHighlight(parse));\r
1954                     started = STARTED_NONE;\r
1955                 } else {\r
1956                     /* Don't match patterns against characters in chatter */\r
1957                     i++;\r
1958                     continue;\r
1959                 }\r
1960             }\r
1961             if (started == STARTED_CHATTER) {\r
1962                 if (buf[i] != '\n') {\r
1963                     /* Don't match patterns against characters in chatter */\r
1964                     i++;\r
1965                     continue;\r
1966                 }\r
1967                 started = STARTED_NONE;\r
1968             }\r
1969 \r
1970             /* Kludge to deal with rcmd protocol */\r
1971             if (firstTime && looking_at(buf, &i, "\001*")) {\r
1972                 DisplayFatalError(&buf[1], 0, 1);\r
1973                 continue;\r
1974             } else {\r
1975                 firstTime = FALSE;\r
1976             }\r
1977 \r
1978             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
1979                 ics_type = ICS_ICC;\r
1980                 ics_prefix = "/";\r
1981                 if (appData.debugMode)\r
1982                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1983                 continue;\r
1984             }\r
1985             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
1986                 ics_type = ICS_FICS;\r
1987                 ics_prefix = "$";\r
1988                 if (appData.debugMode)\r
1989                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1990                 continue;\r
1991             }\r
1992             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
1993                 ics_type = ICS_CHESSNET;\r
1994                 ics_prefix = "/";\r
1995                 if (appData.debugMode)\r
1996                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1997                 continue;\r
1998             }\r
1999 \r
2000             if (!loggedOn &&\r
2001                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2002                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2003                  looking_at(buf, &i, "will be \"*\""))) {\r
2004               strcpy(ics_handle, star_match[0]);\r
2005               continue;\r
2006             }\r
2007 \r
2008             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2009               char buf[MSG_SIZ];\r
2010               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2011               DisplayIcsInteractionTitle(buf);\r
2012               have_set_title = TRUE;\r
2013             }\r
2014 \r
2015             /* skip finger notes */\r
2016             if (started == STARTED_NONE &&\r
2017                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2018                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2019                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2020               started = STARTED_CHATTER;\r
2021               i += 3;\r
2022               continue;\r
2023             }\r
2024 \r
2025             /* skip formula vars */\r
2026             if (started == STARTED_NONE &&\r
2027                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2028               started = STARTED_CHATTER;\r
2029               i += 3;\r
2030               continue;\r
2031             }\r
2032 \r
2033             oldi = i;\r
2034             if (appData.zippyTalk || appData.zippyPlay) {\r
2035 #if ZIPPY\r
2036                 if (ZippyControl(buf, &i) ||\r
2037                     ZippyConverse(buf, &i) ||\r
2038                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2039                     loggedOn = TRUE;\r
2040                     continue;\r
2041                 }\r
2042 #endif\r
2043             } else {\r
2044                 if (/* Don't color "message" or "messages" output */\r
2045                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2046                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2047                     looking_at(buf, &i, "--* (*:*): ") ||\r
2048                     /* Regular tells and says */\r
2049                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2050                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2051                     looking_at(buf, &i, "* says: ") ||\r
2052                     /* Message notifications (same color as tells) */\r
2053                     looking_at(buf, &i, "* has left a message ") ||\r
2054                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2055                     /* Whispers and kibitzes */\r
2056                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2057                     looking_at(buf, &i, "* kibitzes: ") ||\r
2058                     /* Channel tells */\r
2059                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2060 \r
2061                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2062                       /* Avoid "tells you:" spoofs in channels */\r
2063                      tkind = 3;\r
2064                   }\r
2065                   if (star_match[0][0] == NULLCHAR ||\r
2066                       strchr(star_match[0], ' ') ||\r
2067                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2068                     /* Reject bogus matches */\r
2069                     i = oldi;\r
2070                   } else {\r
2071                     if (appData.colorize) {\r
2072                       if (oldi > next_out) {\r
2073                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2074                         next_out = oldi;\r
2075                       }\r
2076                       switch (tkind) {\r
2077                       case 1:\r
2078                         Colorize(ColorTell, FALSE);\r
2079                         curColor = ColorTell;\r
2080                         break;\r
2081                       case 2:\r
2082                         Colorize(ColorKibitz, FALSE);\r
2083                         curColor = ColorKibitz;\r
2084                         break;\r
2085                       case 3:\r
2086                         p = strrchr(star_match[1], '(');\r
2087                         if (p == NULL) {\r
2088                           p = star_match[1];\r
2089                         } else {\r
2090                           p++;\r
2091                         }\r
2092                         if (atoi(p) == 1) {\r
2093                           Colorize(ColorChannel1, FALSE);\r
2094                           curColor = ColorChannel1;\r
2095                         } else {\r
2096                           Colorize(ColorChannel, FALSE);\r
2097                           curColor = ColorChannel;\r
2098                         }\r
2099                         break;\r
2100                       case 5:\r
2101                         curColor = ColorNormal;\r
2102                         break;\r
2103                       }\r
2104                     }\r
2105                     if (started == STARTED_NONE && appData.autoComment &&\r
2106                         (gameMode == IcsObserving ||\r
2107                          gameMode == IcsPlayingWhite ||\r
2108                          gameMode == IcsPlayingBlack)) {\r
2109                       parse_pos = i - oldi;\r
2110                       memcpy(parse, &buf[oldi], parse_pos);\r
2111                       parse[parse_pos] = NULLCHAR;\r
2112                       started = STARTED_COMMENT;\r
2113                       savingComment = TRUE;\r
2114                     } else {\r
2115                       started = STARTED_CHATTER;\r
2116                       savingComment = FALSE;\r
2117                     }\r
2118                     loggedOn = TRUE;\r
2119                     continue;\r
2120                   }\r
2121                 }\r
2122 \r
2123                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2124                     looking_at(buf, &i, "* c-shouts: ")) {\r
2125                     if (appData.colorize) {\r
2126                         if (oldi > next_out) {\r
2127                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2128                             next_out = oldi;\r
2129                         }\r
2130                         Colorize(ColorSShout, FALSE);\r
2131                         curColor = ColorSShout;\r
2132                     }\r
2133                     loggedOn = TRUE;\r
2134                     started = STARTED_CHATTER;\r
2135                     continue;\r
2136                 }\r
2137 \r
2138                 if (looking_at(buf, &i, "--->")) {\r
2139                     loggedOn = TRUE;\r
2140                     continue;\r
2141                 }\r
2142 \r
2143                 if (looking_at(buf, &i, "* shouts: ") ||\r
2144                     looking_at(buf, &i, "--> ")) {\r
2145                     if (appData.colorize) {\r
2146                         if (oldi > next_out) {\r
2147                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2148                             next_out = oldi;\r
2149                         }\r
2150                         Colorize(ColorShout, FALSE);\r
2151                         curColor = ColorShout;\r
2152                     }\r
2153                     loggedOn = TRUE;\r
2154                     started = STARTED_CHATTER;\r
2155                     continue;\r
2156                 }\r
2157 \r
2158                 if (looking_at( buf, &i, "Challenge:")) {\r
2159                     if (appData.colorize) {\r
2160                         if (oldi > next_out) {\r
2161                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2162                             next_out = oldi;\r
2163                         }\r
2164                         Colorize(ColorChallenge, FALSE);\r
2165                         curColor = ColorChallenge;\r
2166                     }\r
2167                     loggedOn = TRUE;\r
2168                     continue;\r
2169                 }\r
2170 \r
2171                 if (looking_at(buf, &i, "* offers you") ||\r
2172                     looking_at(buf, &i, "* offers to be") ||\r
2173                     looking_at(buf, &i, "* would like to") ||\r
2174                     looking_at(buf, &i, "* requests to") ||\r
2175                     looking_at(buf, &i, "Your opponent offers") ||\r
2176                     looking_at(buf, &i, "Your opponent requests")) {\r
2177 \r
2178                     if (appData.colorize) {\r
2179                         if (oldi > next_out) {\r
2180                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2181                             next_out = oldi;\r
2182                         }\r
2183                         Colorize(ColorRequest, FALSE);\r
2184                         curColor = ColorRequest;\r
2185                     }\r
2186                     continue;\r
2187                 }\r
2188 \r
2189                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2190                     if (appData.colorize) {\r
2191                         if (oldi > next_out) {\r
2192                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2193                             next_out = oldi;\r
2194                         }\r
2195                         Colorize(ColorSeek, FALSE);\r
2196                         curColor = ColorSeek;\r
2197                     }\r
2198                     continue;\r
2199                 }\r
2200             }\r
2201 \r
2202             if (looking_at(buf, &i, "\\   ")) {\r
2203                 if (prevColor != ColorNormal) {\r
2204                     if (oldi > next_out) {\r
2205                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2206                         next_out = oldi;\r
2207                     }\r
2208                     Colorize(prevColor, TRUE);\r
2209                     curColor = prevColor;\r
2210                 }\r
2211                 if (savingComment) {\r
2212                     parse_pos = i - oldi;\r
2213                     memcpy(parse, &buf[oldi], parse_pos);\r
2214                     parse[parse_pos] = NULLCHAR;\r
2215                     started = STARTED_COMMENT;\r
2216                 } else {\r
2217                     started = STARTED_CHATTER;\r
2218                 }\r
2219                 continue;\r
2220             }\r
2221 \r
2222             if (looking_at(buf, &i, "Black Strength :") ||\r
2223                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2224                 looking_at(buf, &i, "<10>") ||\r
2225                 looking_at(buf, &i, "#@#")) {\r
2226                 /* Wrong board style */\r
2227                 loggedOn = TRUE;\r
2228                 SendToICS(ics_prefix);\r
2229                 SendToICS("set style 12\n");\r
2230                 SendToICS(ics_prefix);\r
2231                 SendToICS("refresh\n");\r
2232                 continue;\r
2233             }\r
2234             \r
2235             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2236                 ICSInitScript();\r
2237                 have_sent_ICS_logon = 1;\r
2238                 continue;\r
2239             }\r
2240               \r
2241             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2242                 (looking_at(buf, &i, "\n<12> ") ||\r
2243                  looking_at(buf, &i, "<12> "))) {\r
2244                 loggedOn = TRUE;\r
2245                 if (oldi > next_out) {\r
2246                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2247                 }\r
2248                 next_out = i;\r
2249                 started = STARTED_BOARD;\r
2250                 parse_pos = 0;\r
2251                 continue;\r
2252             }\r
2253 \r
2254             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2255                 looking_at(buf, &i, "<b1> ")) {\r
2256                 if (oldi > next_out) {\r
2257                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2258                 }\r
2259                 next_out = i;\r
2260                 started = STARTED_HOLDINGS;\r
2261                 parse_pos = 0;\r
2262                 continue;\r
2263             }\r
2264 \r
2265             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2266                 loggedOn = TRUE;\r
2267                 /* Header for a move list -- first line */\r
2268 \r
2269                 switch (ics_getting_history) {\r
2270                   case H_FALSE:\r
2271                     switch (gameMode) {\r
2272                       case IcsIdle:\r
2273                       case BeginningOfGame:\r
2274                         /* User typed "moves" or "oldmoves" while we\r
2275                            were idle.  Pretend we asked for these\r
2276                            moves and soak them up so user can step\r
2277                            through them and/or save them.\r
2278                            */\r
2279                         Reset(FALSE, TRUE);\r
2280                         gameMode = IcsObserving;\r
2281                         ModeHighlight();\r
2282                         ics_gamenum = -1;\r
2283                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2284                         break;\r
2285                       case EditGame: /*?*/\r
2286                       case EditPosition: /*?*/\r
2287                         /* Should above feature work in these modes too? */\r
2288                         /* For now it doesn't */\r
2289                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2290                         break;\r
2291                       default:\r
2292                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2293                         break;\r
2294                     }\r
2295                     break;\r
2296                   case H_REQUESTED:\r
2297                     /* Is this the right one? */\r
2298                     if (gameInfo.white && gameInfo.black &&\r
2299                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2300                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2301                         /* All is well */\r
2302                         ics_getting_history = H_GOT_REQ_HEADER;\r
2303                     }\r
2304                     break;\r
2305                   case H_GOT_REQ_HEADER:\r
2306                   case H_GOT_UNREQ_HEADER:\r
2307                   case H_GOT_UNWANTED_HEADER:\r
2308                   case H_GETTING_MOVES:\r
2309                     /* Should not happen */\r
2310                     DisplayError("Error gathering move list: two headers", 0);\r
2311                     ics_getting_history = H_FALSE;\r
2312                     break;\r
2313                 }\r
2314 \r
2315                 /* Save player ratings into gameInfo if needed */\r
2316                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2317                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2318                     (gameInfo.whiteRating == -1 ||\r
2319                      gameInfo.blackRating == -1)) {\r
2320 \r
2321                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2322                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2323                     if (appData.debugMode)\r
2324                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2325                               gameInfo.whiteRating, gameInfo.blackRating);\r
2326                 }\r
2327                 continue;\r
2328             }\r
2329 \r
2330             if (looking_at(buf, &i,\r
2331               "* * match, initial time: * minute*, increment: * second")) {\r
2332                 /* Header for a move list -- second line */\r
2333                 /* Initial board will follow if this is a wild game */\r
2334 \r
2335                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2336                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2337                 gameInfo.event = StrSave(str);\r
2338                 gameInfo.variant = StringToVariant(gameInfo.event);\r
2339                 continue;\r
2340             }\r
2341 \r
2342             if (looking_at(buf, &i, "Move  ")) {\r
2343                 /* Beginning of a move list */\r
2344                 switch (ics_getting_history) {\r
2345                   case H_FALSE:\r
2346                     /* Normally should not happen */\r
2347                     /* Maybe user hit reset while we were parsing */\r
2348                     break;\r
2349                   case H_REQUESTED:\r
2350                     /* Happens if we are ignoring a move list that is not\r
2351                      * the one we just requested.  Common if the user\r
2352                      * tries to observe two games without turning off\r
2353                      * getMoveList */\r
2354                     break;\r
2355                   case H_GETTING_MOVES:\r
2356                     /* Should not happen */\r
2357                     DisplayError("Error gathering move list: nested", 0);\r
2358                     ics_getting_history = H_FALSE;\r
2359                     break;\r
2360                   case H_GOT_REQ_HEADER:\r
2361                     ics_getting_history = H_GETTING_MOVES;\r
2362                     started = STARTED_MOVES;\r
2363                     parse_pos = 0;\r
2364                     if (oldi > next_out) {\r
2365                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2366                     }\r
2367                     break;\r
2368                   case H_GOT_UNREQ_HEADER:\r
2369                     ics_getting_history = H_GETTING_MOVES;\r
2370                     started = STARTED_MOVES_NOHIDE;\r
2371                     parse_pos = 0;\r
2372                     break;\r
2373                   case H_GOT_UNWANTED_HEADER:\r
2374                     ics_getting_history = H_FALSE;\r
2375                     break;\r
2376                 }\r
2377                 continue;\r
2378             }                           \r
2379             \r
2380             if (looking_at(buf, &i, "% ") ||\r
2381                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2382                  && looking_at(buf, &i, "}*"))) {\r
2383                 savingComment = FALSE;\r
2384                 switch (started) {\r
2385                   case STARTED_MOVES:\r
2386                   case STARTED_MOVES_NOHIDE:\r
2387                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2388                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2389                     ParseGameHistory(parse);\r
2390 #if ZIPPY\r
2391                     if (appData.zippyPlay && first.initDone) {\r
2392                         FeedMovesToProgram(&first, forwardMostMove);\r
2393                         if (gameMode == IcsPlayingWhite) {\r
2394                             if (WhiteOnMove(forwardMostMove)) {\r
2395                                 if (first.sendTime) {\r
2396                                   if (first.useColors) {\r
2397                                     SendToProgram("black\n", &first); \r
2398                                   }\r
2399                                   SendTimeRemaining(&first, TRUE);\r
2400                                 }\r
2401                                 if (first.useColors) {\r
2402                                   SendToProgram("white\ngo\n", &first);\r
2403                                 } else {\r
2404                                   SendToProgram("go\n", &first);\r
2405                                 }\r
2406                                 first.maybeThinking = TRUE;\r
2407                             } else {\r
2408                                 if (first.usePlayother) {\r
2409                                   if (first.sendTime) {\r
2410                                     SendTimeRemaining(&first, TRUE);\r
2411                                   }\r
2412                                   SendToProgram("playother\n", &first);\r
2413                                   firstMove = FALSE;\r
2414                                 } else {\r
2415                                   firstMove = TRUE;\r
2416                                 }\r
2417                             }\r
2418                         } else if (gameMode == IcsPlayingBlack) {\r
2419                             if (!WhiteOnMove(forwardMostMove)) {\r
2420                                 if (first.sendTime) {\r
2421                                   if (first.useColors) {\r
2422                                     SendToProgram("white\n", &first);\r
2423                                   }\r
2424                                   SendTimeRemaining(&first, FALSE);\r
2425                                 }\r
2426                                 if (first.useColors) {\r
2427                                   SendToProgram("black\ngo\n", &first);\r
2428                                 } else {\r
2429                                   SendToProgram("go\n", &first);\r
2430                                 }\r
2431                                 first.maybeThinking = TRUE;\r
2432                             } else {\r
2433                                 if (first.usePlayother) {\r
2434                                   if (first.sendTime) {\r
2435                                     SendTimeRemaining(&first, FALSE);\r
2436                                   }\r
2437                                   SendToProgram("playother\n", &first);\r
2438                                   firstMove = FALSE;\r
2439                                 } else {\r
2440                                   firstMove = TRUE;\r
2441                                 }\r
2442                             }\r
2443                         }                       \r
2444                     }\r
2445 #endif\r
2446                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2447                         /* Moves came from oldmoves or moves command\r
2448                            while we weren't doing anything else.\r
2449                            */\r
2450                         currentMove = forwardMostMove;\r
2451                         ClearHighlights();/*!!could figure this out*/\r
2452                         flipView = appData.flipView;\r
2453                         DrawPosition(FALSE, boards[currentMove]);\r
2454                         DisplayBothClocks();\r
2455                         sprintf(str, "%s vs. %s",\r
2456                                 gameInfo.white, gameInfo.black);\r
2457                         DisplayTitle(str);\r
2458                         gameMode = IcsIdle;\r
2459                     } else {\r
2460                         /* Moves were history of an active game */\r
2461                         if (gameInfo.resultDetails != NULL) {\r
2462                             free(gameInfo.resultDetails);\r
2463                             gameInfo.resultDetails = NULL;\r
2464                         }\r
2465                     }\r
2466                     HistorySet(parseList, backwardMostMove,\r
2467                                forwardMostMove, currentMove-1);\r
2468                     DisplayMove(currentMove - 1);\r
2469                     if (started == STARTED_MOVES) next_out = i;\r
2470                     started = STARTED_NONE;\r
2471                     ics_getting_history = H_FALSE;\r
2472                     break;\r
2473 \r
2474                   case STARTED_OBSERVE:\r
2475                     started = STARTED_NONE;\r
2476                     SendToICS(ics_prefix);\r
2477                     SendToICS("refresh\n");\r
2478                     break;\r
2479 \r
2480                   default:\r
2481                     break;\r
2482                 }\r
2483                 continue;\r
2484             }\r
2485             \r
2486             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2487                  started == STARTED_HOLDINGS ||\r
2488                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2489                 /* Accumulate characters in move list or board */\r
2490                 parse[parse_pos++] = buf[i];\r
2491             }\r
2492             \r
2493             /* Start of game messages.  Mostly we detect start of game\r
2494                when the first board image arrives.  On some versions\r
2495                of the ICS, though, we need to do a "refresh" after starting\r
2496                to observe in order to get the current board right away. */\r
2497             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2498                 started = STARTED_OBSERVE;\r
2499                 continue;\r
2500             }\r
2501 \r
2502             /* Handle auto-observe */\r
2503             if (appData.autoObserve &&\r
2504                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2505                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2506                 char *player;\r
2507                 /* Choose the player that was highlighted, if any. */\r
2508                 if (star_match[0][0] == '\033' ||\r
2509                     star_match[1][0] != '\033') {\r
2510                     player = star_match[0];\r
2511                 } else {\r
2512                     player = star_match[2];\r
2513                 }\r
2514                 sprintf(str, "%sobserve %s\n",\r
2515                         ics_prefix, StripHighlightAndTitle(player));\r
2516                 SendToICS(str);\r
2517 \r
2518                 /* Save ratings from notify string */\r
2519                 strcpy(player1Name, star_match[0]);\r
2520                 player1Rating = string_to_rating(star_match[1]);\r
2521                 strcpy(player2Name, star_match[2]);\r
2522                 player2Rating = string_to_rating(star_match[3]);\r
2523 \r
2524                 if (appData.debugMode)\r
2525                   fprintf(debugFP, \r
2526                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2527                           player1Name, player1Rating,\r
2528                           player2Name, player2Rating);\r
2529 \r
2530                 continue;\r
2531             }\r
2532 \r
2533             /* Deal with automatic examine mode after a game,\r
2534                and with IcsObserving -> IcsExamining transition */\r
2535             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2536                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2537 \r
2538                 int gamenum = atoi(star_match[0]);\r
2539                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2540                     gamenum == ics_gamenum) {\r
2541                     /* We were already playing or observing this game;\r
2542                        no need to refetch history */\r
2543                     gameMode = IcsExamining;\r
2544                     if (pausing) {\r
2545                         pauseExamForwardMostMove = forwardMostMove;\r
2546                     } else if (currentMove < forwardMostMove) {\r
2547                         ForwardInner(forwardMostMove);\r
2548                     }\r
2549                 } else {\r
2550                     /* I don't think this case really can happen */\r
2551                     SendToICS(ics_prefix);\r
2552                     SendToICS("refresh\n");\r
2553                 }\r
2554                 continue;\r
2555             }    \r
2556             \r
2557             /* Error messages */\r
2558             if (ics_user_moved) {\r
2559                 if (looking_at(buf, &i, "Illegal move") ||\r
2560                     looking_at(buf, &i, "Not a legal move") ||\r
2561                     looking_at(buf, &i, "Your king is in check") ||\r
2562                     looking_at(buf, &i, "It isn't your turn") ||\r
2563                     looking_at(buf, &i, "It is not your move")) {\r
2564                     /* Illegal move */\r
2565                     ics_user_moved = 0;\r
2566                     if (forwardMostMove > backwardMostMove) {\r
2567                         currentMove = --forwardMostMove;\r
2568                         DisplayMove(currentMove - 1); /* before DMError */\r
2569                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2570                         DrawPosition(FALSE, boards[currentMove]);\r
2571                         SwitchClocks();\r
2572                         DisplayBothClocks();\r
2573                     }\r
2574                     continue;\r
2575                 }\r
2576             }\r
2577 \r
2578             if (looking_at(buf, &i, "still have time") ||\r
2579                 looking_at(buf, &i, "not out of time") ||\r
2580                 looking_at(buf, &i, "either player is out of time") ||\r
2581                 looking_at(buf, &i, "has timeseal; checking")) {\r
2582                 /* We must have called his flag a little too soon */\r
2583                 whiteFlag = blackFlag = FALSE;\r
2584                 continue;\r
2585             }\r
2586 \r
2587             if (looking_at(buf, &i, "added * seconds to") ||\r
2588                 looking_at(buf, &i, "seconds were added to")) {\r
2589                 /* Update the clocks */\r
2590                 SendToICS(ics_prefix);\r
2591                 SendToICS("refresh\n");\r
2592                 continue;\r
2593             }\r
2594 \r
2595             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2596                 ics_clock_paused = TRUE;\r
2597                 StopClocks();\r
2598                 continue;\r
2599             }\r
2600 \r
2601             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2602                 ics_clock_paused = FALSE;\r
2603                 StartClocks();\r
2604                 continue;\r
2605             }\r
2606 \r
2607             /* Grab player ratings from the Creating: message.\r
2608                Note we have to check for the special case when\r
2609                the ICS inserts things like [white] or [black]. */\r
2610             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2611                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2612                 /* star_matches:\r
2613                    0    player 1 name (not necessarily white)\r
2614                    1    player 1 rating\r
2615                    2    empty, white, or black (IGNORED)\r
2616                    3    player 2 name (not necessarily black)\r
2617                    4    player 2 rating\r
2618                    \r
2619                    The names/ratings are sorted out when the game\r
2620                    actually starts (below).\r
2621                 */\r
2622                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2623                 player1Rating = string_to_rating(star_match[1]);\r
2624                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2625                 player2Rating = string_to_rating(star_match[4]);\r
2626 \r
2627                 if (appData.debugMode)\r
2628                   fprintf(debugFP, \r
2629                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2630                           player1Name, player1Rating,\r
2631                           player2Name, player2Rating);\r
2632 \r
2633                 continue;\r
2634             }\r
2635             \r
2636             /* Improved generic start/end-of-game messages */\r
2637             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2638                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2639                 /* If tkind == 0: */\r
2640                 /* star_match[0] is the game number */\r
2641                 /*           [1] is the white player's name */\r
2642                 /*           [2] is the black player's name */\r
2643                 /* For end-of-game: */\r
2644                 /*           [3] is the reason for the game end */\r
2645                 /*           [4] is a PGN end game-token, preceded by " " */\r
2646                 /* For start-of-game: */\r
2647                 /*           [3] begins with "Creating" or "Continuing" */\r
2648                 /*           [4] is " *" or empty (don't care). */\r
2649                 int gamenum = atoi(star_match[0]);\r
2650                 char *whitename, *blackname, *why, *endtoken;\r
2651                 ChessMove endtype = (ChessMove) 0;\r
2652 \r
2653                 if (tkind == 0) {\r
2654                   whitename = star_match[1];\r
2655                   blackname = star_match[2];\r
2656                   why = star_match[3];\r
2657                   endtoken = star_match[4];\r
2658                 } else {\r
2659                   whitename = star_match[1];\r
2660                   blackname = star_match[3];\r
2661                   why = star_match[5];\r
2662                   endtoken = star_match[6];\r
2663                 }\r
2664 \r
2665                 /* Game start messages */\r
2666                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2667                     strncmp(why, "Continuing ", 11) == 0) {\r
2668                     gs_gamenum = gamenum;\r
2669                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2670 #if ZIPPY\r
2671                     if (appData.zippyPlay) {\r
2672                         ZippyGameStart(whitename, blackname);\r
2673                     }\r
2674 #endif /*ZIPPY*/\r
2675                     continue;\r
2676                 }\r
2677 \r
2678                 /* Game end messages */\r
2679                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2680                     ics_gamenum != gamenum) {\r
2681                     continue;\r
2682                 }\r
2683                 while (endtoken[0] == ' ') endtoken++;\r
2684                 switch (endtoken[0]) {\r
2685                   case '*':\r
2686                   default:\r
2687                     endtype = GameUnfinished;\r
2688                     break;\r
2689                   case '0':\r
2690                     endtype = BlackWins;\r
2691                     break;\r
2692                   case '1':\r
2693                     if (endtoken[1] == '/')\r
2694                       endtype = GameIsDrawn;\r
2695                     else\r
2696                       endtype = WhiteWins;\r
2697                     break;\r
2698                 }\r
2699                 GameEnds(endtype, why, GE_ICS);\r
2700 #if ZIPPY\r
2701                 if (appData.zippyPlay && first.initDone) {\r
2702                     ZippyGameEnd(endtype, why);\r
2703                     if (first.pr == NULL) {\r
2704                       /* Start the next process early so that we'll\r
2705                          be ready for the next challenge */\r
2706                       StartChessProgram(&first);\r
2707                     }\r
2708                     /* Send "new" early, in case this command takes\r
2709                        a long time to finish, so that we'll be ready\r
2710                        for the next challenge. */\r
2711                     Reset(TRUE, TRUE);\r
2712                 }\r
2713 #endif /*ZIPPY*/\r
2714                 continue;\r
2715             }\r
2716 \r
2717             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2718                 looking_at(buf, &i, "no longer observing game *") ||\r
2719                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2720                 if (gameMode == IcsObserving &&\r
2721                     atoi(star_match[0]) == ics_gamenum)\r
2722                   {\r
2723                       StopClocks();\r
2724                       gameMode = IcsIdle;\r
2725                       ics_gamenum = -1;\r
2726                       ics_user_moved = FALSE;\r
2727                   }\r
2728                 continue;\r
2729             }\r
2730 \r
2731             if (looking_at(buf, &i, "no longer examining game *")) {\r
2732                 if (gameMode == IcsExamining &&\r
2733                     atoi(star_match[0]) == ics_gamenum)\r
2734                   {\r
2735                       gameMode = IcsIdle;\r
2736                       ics_gamenum = -1;\r
2737                       ics_user_moved = FALSE;\r
2738                   }\r
2739                 continue;\r
2740             }\r
2741 \r
2742             /* Advance leftover_start past any newlines we find,\r
2743                so only partial lines can get reparsed */\r
2744             if (looking_at(buf, &i, "\n")) {\r
2745                 prevColor = curColor;\r
2746                 if (curColor != ColorNormal) {\r
2747                     if (oldi > next_out) {\r
2748                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2749                         next_out = oldi;\r
2750                     }\r
2751                     Colorize(ColorNormal, FALSE);\r
2752                     curColor = ColorNormal;\r
2753                 }\r
2754                 if (started == STARTED_BOARD) {\r
2755                     started = STARTED_NONE;\r
2756                     parse[parse_pos] = NULLCHAR;\r
2757                     ParseBoard12(parse);\r
2758                     ics_user_moved = 0;\r
2759 \r
2760                     /* Send premove here */\r
2761                     if (appData.premove) {\r
2762                       char str[MSG_SIZ];\r
2763                       if (currentMove == 0 &&\r
2764                           gameMode == IcsPlayingWhite &&\r
2765                           appData.premoveWhite) {\r
2766                         sprintf(str, "%s%s\n", ics_prefix,\r
2767                                 appData.premoveWhiteText);\r
2768                         if (appData.debugMode)\r
2769                           fprintf(debugFP, "Sending premove:\n");\r
2770                         SendToICS(str);\r
2771                       } else if (currentMove == 1 &&\r
2772                                  gameMode == IcsPlayingBlack &&\r
2773                                  appData.premoveBlack) {\r
2774                         sprintf(str, "%s%s\n", ics_prefix,\r
2775                                 appData.premoveBlackText);\r
2776                         if (appData.debugMode)\r
2777                           fprintf(debugFP, "Sending premove:\n");\r
2778                         SendToICS(str);\r
2779                       } else if (gotPremove) {\r
2780                         gotPremove = 0;\r
2781                         ClearPremoveHighlights();\r
2782                         if (appData.debugMode)\r
2783                           fprintf(debugFP, "Sending premove:\n");\r
2784                           UserMoveEvent(premoveFromX, premoveFromY, \r
2785                                         premoveToX, premoveToY, \r
2786                                         premovePromoChar);\r
2787                       }\r
2788                     }\r
2789 \r
2790                     /* Usually suppress following prompt */\r
2791                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2792                         if (looking_at(buf, &i, "*% ")) {\r
2793                             savingComment = FALSE;\r
2794                         }\r
2795                     }\r
2796                     next_out = i;\r
2797                 } else if (started == STARTED_HOLDINGS) {\r
2798                     int gamenum;\r
2799                     char new_piece[MSG_SIZ];\r
2800                     started = STARTED_NONE;\r
2801                     parse[parse_pos] = NULLCHAR;\r
2802                     if (appData.debugMode)\r
2803                       fprintf(debugFP, "Parsing holdings: %s\n", parse);\r
2804                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
2805                         gamenum == ics_gamenum) {\r
2806                         if (gameInfo.variant == VariantNormal) {\r
2807                           gameInfo.variant = VariantCrazyhouse; /*temp guess*/\r
2808                           /* Get a move list just to see the header, which\r
2809                              will tell us whether this is really bug or zh */\r
2810                           if (ics_getting_history == H_FALSE) {\r
2811                             ics_getting_history = H_REQUESTED;\r
2812                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2813                             SendToICS(str);\r
2814                           }\r
2815                         }\r
2816                         new_piece[0] = NULLCHAR;\r
2817                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
2818                                &gamenum, white_holding, black_holding,\r
2819                                new_piece);\r
2820                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
2821                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
2822 #if ZIPPY\r
2823                         if (appData.zippyPlay && first.initDone) {\r
2824                             ZippyHoldings(white_holding, black_holding,\r
2825                                           new_piece);\r
2826                         }\r
2827 #endif /*ZIPPY*/\r
2828                         if (tinyLayout || smallLayout) {\r
2829                             char wh[16], bh[16];\r
2830                             PackHolding(wh, white_holding);\r
2831                             PackHolding(bh, black_holding);\r
2832                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
2833                                     gameInfo.white, gameInfo.black);\r
2834                         } else {\r
2835                             sprintf(str, "%s [%s] vs. %s [%s]",\r
2836                                     gameInfo.white, white_holding,\r
2837                                     gameInfo.black, black_holding);\r
2838                         }\r
2839 \r
2840                         /* [HGM] copy holdings to board holdings area */\r
2841                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
2842                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
2843                         DrawPosition(FALSE, NULL);\r
2844                         DisplayTitle(str);\r
2845                     }\r
2846                     /* Suppress following prompt */\r
2847                     if (looking_at(buf, &i, "*% ")) {\r
2848                         savingComment = FALSE;\r
2849                     }\r
2850                     next_out = i;\r
2851                 }\r
2852                 continue;\r
2853             }\r
2854 \r
2855             i++;                /* skip unparsed character and loop back */\r
2856         }\r
2857         \r
2858         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
2859             started != STARTED_HOLDINGS && i > next_out) {\r
2860             SendToPlayer(&buf[next_out], i - next_out);\r
2861             next_out = i;\r
2862         }\r
2863         \r
2864         leftover_len = buf_len - leftover_start;\r
2865         /* if buffer ends with something we couldn't parse,\r
2866            reparse it after appending the next read */\r
2867         \r
2868     } else if (count == 0) {\r
2869         RemoveInputSource(isr);\r
2870         DisplayFatalError("Connection closed by ICS", 0, 0);\r
2871     } else {\r
2872         DisplayFatalError("Error reading from ICS", error, 1);\r
2873     }\r
2874 }\r
2875 \r
2876 \r
2877 /* Board style 12 looks like this:\r
2878    \r
2879    <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
2880    \r
2881  * The "<12> " is stripped before it gets to this routine.  The two\r
2882  * trailing 0's (flip state and clock ticking) are later addition, and\r
2883  * some chess servers may not have them, or may have only the first.\r
2884  * Additional trailing fields may be added in the future.  \r
2885  */\r
2886 \r
2887 #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
2888 \r
2889 #define RELATION_OBSERVING_PLAYED    0\r
2890 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
2891 #define RELATION_PLAYING_MYMOVE      1\r
2892 #define RELATION_PLAYING_NOTMYMOVE  -1\r
2893 #define RELATION_EXAMINING           2\r
2894 #define RELATION_ISOLATED_BOARD     -3\r
2895 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
2896 \r
2897 void\r
2898 ParseBoard12(string)\r
2899      char *string;\r
2900\r
2901     GameMode newGameMode;\r
2902     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
2903     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
2904     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
2905     char to_play, board_chars[72];\r
2906     char move_str[500], str[500], elapsed_time[500];\r
2907     char black[32], white[32];\r
2908     Board board;\r
2909     int prevMove = currentMove;\r
2910     int ticking = 2;\r
2911     ChessMove moveType;\r
2912     int fromX, fromY, toX, toY;\r
2913     char promoChar;\r
2914 \r
2915     fromX = fromY = toX = toY = -1;\r
2916     \r
2917     newGame = FALSE;\r
2918 \r
2919     if (appData.debugMode)\r
2920       fprintf(debugFP, "Parsing board: %s\n", string);\r
2921 \r
2922     move_str[0] = NULLCHAR;\r
2923     elapsed_time[0] = NULLCHAR;\r
2924     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
2925                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
2926                &gamenum, white, black, &relation, &basetime, &increment,\r
2927                &white_stren, &black_stren, &white_time, &black_time,\r
2928                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
2929                &ticking);\r
2930 \r
2931     if (n < 22) {\r
2932         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
2933         DisplayError(str, 0);\r
2934         return;\r
2935     }\r
2936 \r
2937     /* Convert the move number to internal form */\r
2938     moveNum = (moveNum - 1) * 2;\r
2939     if (to_play == 'B') moveNum++;\r
2940     if (moveNum >= MAX_MOVES) {\r
2941       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
2942                         0, 1);\r
2943       return;\r
2944     }\r
2945     \r
2946     switch (relation) {\r
2947       case RELATION_OBSERVING_PLAYED:\r
2948       case RELATION_OBSERVING_STATIC:\r
2949         if (gamenum == -1) {\r
2950             /* Old ICC buglet */\r
2951             relation = RELATION_OBSERVING_STATIC;\r
2952         }\r
2953         newGameMode = IcsObserving;\r
2954         break;\r
2955       case RELATION_PLAYING_MYMOVE:\r
2956       case RELATION_PLAYING_NOTMYMOVE:\r
2957         newGameMode =\r
2958           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
2959             IcsPlayingWhite : IcsPlayingBlack;\r
2960         break;\r
2961       case RELATION_EXAMINING:\r
2962         newGameMode = IcsExamining;\r
2963         break;\r
2964       case RELATION_ISOLATED_BOARD:\r
2965       default:\r
2966         /* Just display this board.  If user was doing something else,\r
2967            we will forget about it until the next board comes. */ \r
2968         newGameMode = IcsIdle;\r
2969         break;\r
2970       case RELATION_STARTING_POSITION:\r
2971         newGameMode = gameMode;\r
2972         break;\r
2973     }\r
2974     \r
2975     /* Modify behavior for initial board display on move listing\r
2976        of wild games.\r
2977        */\r
2978     switch (ics_getting_history) {\r
2979       case H_FALSE:\r
2980       case H_REQUESTED:\r
2981         break;\r
2982       case H_GOT_REQ_HEADER:\r
2983       case H_GOT_UNREQ_HEADER:\r
2984         /* This is the initial position of the current game */\r
2985         gamenum = ics_gamenum;\r
2986         moveNum = 0;            /* old ICS bug workaround */\r
2987         if (to_play == 'B') {\r
2988           startedFromSetupPosition = TRUE;\r
2989           blackPlaysFirst = TRUE;\r
2990           moveNum = 1;\r
2991           if (forwardMostMove == 0) forwardMostMove = 1;\r
2992           if (backwardMostMove == 0) backwardMostMove = 1;\r
2993           if (currentMove == 0) currentMove = 1;\r
2994         }\r
2995         newGameMode = gameMode;\r
2996         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
2997         break;\r
2998       case H_GOT_UNWANTED_HEADER:\r
2999         /* This is an initial board that we don't want */\r
3000         return;\r
3001       case H_GETTING_MOVES:\r
3002         /* Should not happen */\r
3003         DisplayError("Error gathering move list: extra board", 0);\r
3004         ics_getting_history = H_FALSE;\r
3005         return;\r
3006     }\r
3007     \r
3008     /* Take action if this is the first board of a new game, or of a\r
3009        different game than is currently being displayed.  */\r
3010     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3011         relation == RELATION_ISOLATED_BOARD) {\r
3012         \r
3013         /* Forget the old game and get the history (if any) of the new one */\r
3014         if (gameMode != BeginningOfGame) {\r
3015           Reset(FALSE, TRUE);\r
3016         }\r
3017         newGame = TRUE;\r
3018         if (appData.autoRaiseBoard) BoardToTop();\r
3019         prevMove = -3;\r
3020         if (gamenum == -1) {\r
3021             newGameMode = IcsIdle;\r
3022         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3023                    appData.getMoveList) {\r
3024             /* Need to get game history */\r
3025             ics_getting_history = H_REQUESTED;\r
3026             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3027             SendToICS(str);\r
3028         }\r
3029         \r
3030         /* Initially flip the board to have black on the bottom if playing\r
3031            black or if the ICS flip flag is set, but let the user change\r
3032            it with the Flip View button. */\r
3033         flipView = appData.autoFlipView ? \r
3034           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3035           appData.flipView;\r
3036         \r
3037         /* Done with values from previous mode; copy in new ones */\r
3038         gameMode = newGameMode;\r
3039         ModeHighlight();\r
3040         ics_gamenum = gamenum;\r
3041         if (gamenum == gs_gamenum) {\r
3042             int klen = strlen(gs_kind);\r
3043             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3044             sprintf(str, "ICS %s", gs_kind);\r
3045             gameInfo.event = StrSave(str);\r
3046         } else {\r
3047             gameInfo.event = StrSave("ICS game");\r
3048         }\r
3049         gameInfo.site = StrSave(appData.icsHost);\r
3050         gameInfo.date = PGNDate();\r
3051         gameInfo.round = StrSave("-");\r
3052         gameInfo.white = StrSave(white);\r
3053         gameInfo.black = StrSave(black);\r
3054         timeControl = basetime * 60 * 1000;\r
3055         timeControl_2 = 0;\r
3056         timeIncrement = increment * 1000;\r
3057         movesPerSession = 0;\r
3058         gameInfo.timeControl = TimeControlTagValue();\r
3059         gameInfo.variant = StringToVariant(gameInfo.event);\r
3060         gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
3061         switch(gameInfo.variant) {\r
3062             case VariantShogi:\r
3063             case VariantShowgi:\r
3064               gameInfo.boardWidth = gameInfo.boardHeight = 9;\r
3065               gameInfo.holdingsSize += 2;\r
3066             case VariantBughouse:\r
3067             case VariantCrazyhouse:\r
3068               gameInfo.boardWidth = gameInfo.boardHeight = 8;\r
3069               gameInfo.holdingsWidth = 2; break;\r
3070             default:\r
3071               gameInfo.boardWidth = gameInfo.boardHeight = 8;\r
3072               gameInfo.holdingsWidth = 0;\r
3073         }\r
3074         gameInfo.outOfBook = NULL;\r
3075         \r
3076         /* Do we have the ratings? */\r
3077         if (strcmp(player1Name, white) == 0 &&\r
3078             strcmp(player2Name, black) == 0) {\r
3079             if (appData.debugMode)\r
3080               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3081                       player1Rating, player2Rating);\r
3082             gameInfo.whiteRating = player1Rating;\r
3083             gameInfo.blackRating = player2Rating;\r
3084         } else if (strcmp(player2Name, white) == 0 &&\r
3085                    strcmp(player1Name, black) == 0) {\r
3086             if (appData.debugMode)\r
3087               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3088                       player2Rating, player1Rating);\r
3089             gameInfo.whiteRating = player2Rating;\r
3090             gameInfo.blackRating = player1Rating;\r
3091         }\r
3092         player1Name[0] = player2Name[0] = NULLCHAR;\r
3093 \r
3094         /* Silence shouts if requested */\r
3095         if (appData.quietPlay &&\r
3096             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3097             SendToICS(ics_prefix);\r
3098             SendToICS("set shout 0\n");\r
3099         }\r
3100     }\r
3101     \r
3102     /* Deal with midgame name changes */\r
3103     if (!newGame) {\r
3104         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3105             if (gameInfo.white) free(gameInfo.white);\r
3106             gameInfo.white = StrSave(white);\r
3107         }\r
3108         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3109             if (gameInfo.black) free(gameInfo.black);\r
3110             gameInfo.black = StrSave(black);\r
3111         }\r
3112     }\r
3113     \r
3114     /* Throw away game result if anything actually changes in examine mode */\r
3115     if (gameMode == IcsExamining && !newGame) {\r
3116         gameInfo.result = GameUnfinished;\r
3117         if (gameInfo.resultDetails != NULL) {\r
3118             free(gameInfo.resultDetails);\r
3119             gameInfo.resultDetails = NULL;\r
3120         }\r
3121     }\r
3122     \r
3123     /* In pausing && IcsExamining mode, we ignore boards coming\r
3124        in if they are in a different variation than we are. */\r
3125     if (pauseExamInvalid) return;\r
3126     if (pausing && gameMode == IcsExamining) {\r
3127         if (moveNum <= pauseExamForwardMostMove) {\r
3128             pauseExamInvalid = TRUE;\r
3129             forwardMostMove = pauseExamForwardMostMove;\r
3130             return;\r
3131         }\r
3132     }\r
3133     \r
3134     /* Parse the board */\r
3135     for (k = 0; k < 8; k++)\r
3136       for (j = 0; j < 8; j++)\r
3137         board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3138     CopyBoard(boards[moveNum], board);\r
3139     if (moveNum == 0) {\r
3140         startedFromSetupPosition =\r
3141           !CompareBoards(board, initialPosition);\r
3142     }\r
3143     \r
3144     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3145         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3146         /* This was an initial position from a move list, not\r
3147            the current position */\r
3148         return;\r
3149     }\r
3150     \r
3151     /* Update currentMove and known move number limits */\r
3152     newMove = newGame || moveNum > forwardMostMove;\r
3153     if (newGame) {\r
3154         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3155         if (gameMode == IcsExamining && moveNum == 0) {\r
3156           /* Workaround for ICS limitation: we are not told the wild\r
3157              type when starting to examine a game.  But if we ask for\r
3158              the move list, the move list header will tell us */\r
3159             ics_getting_history = H_REQUESTED;\r
3160             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3161             SendToICS(str);\r
3162         }\r
3163     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3164                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3165         forwardMostMove = moveNum;\r
3166         if (!pausing || currentMove > forwardMostMove)\r
3167           currentMove = forwardMostMove;\r
3168     } else {\r
3169         /* New part of history that is not contiguous with old part */ \r
3170         if (pausing && gameMode == IcsExamining) {\r
3171             pauseExamInvalid = TRUE;\r
3172             forwardMostMove = pauseExamForwardMostMove;\r
3173             return;\r
3174         }\r
3175         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3176         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3177             ics_getting_history = H_REQUESTED;\r
3178             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3179             SendToICS(str);\r
3180         }\r
3181     }\r
3182     \r
3183     /* Update the clocks */\r
3184     if (strchr(elapsed_time, '.')) {\r
3185       /* Time is in ms */\r
3186       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3187       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3188     } else {\r
3189       /* Time is in seconds */\r
3190       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3191       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3192     }\r
3193       \r
3194 \r
3195 #if ZIPPY\r
3196     if (appData.zippyPlay && newGame &&\r
3197         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3198         gameMode != IcsExamining)\r
3199       ZippyFirstBoard(moveNum, basetime, increment);\r
3200 #endif\r
3201     \r
3202     /* Put the move on the move list, first converting\r
3203        to canonical algebraic form. */\r
3204     if (moveNum > 0) {\r
3205         if (moveNum <= backwardMostMove) {\r
3206             /* We don't know what the board looked like before\r
3207                this move.  Punt. */\r
3208             strcpy(parseList[moveNum - 1], move_str);\r
3209             strcat(parseList[moveNum - 1], " ");\r
3210             strcat(parseList[moveNum - 1], elapsed_time);\r
3211             moveList[moveNum - 1][0] = NULLCHAR;\r
3212         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3213                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3214             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3215                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3216                                      fromY, fromX, toY, toX, promoChar,\r
3217                                      parseList[moveNum-1]);\r
3218             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3219                              castlingRights[moveNum]) ) {\r
3220               case MT_NONE:\r
3221               case MT_STALEMATE:\r
3222               default:\r
3223                 break;\r
3224               case MT_CHECK:\r
3225                 strcat(parseList[moveNum - 1], "+");\r
3226                 break;\r
3227               case MT_CHECKMATE:\r
3228                 strcat(parseList[moveNum - 1], "#");\r
3229                 break;\r
3230             }\r
3231             strcat(parseList[moveNum - 1], " ");\r
3232             strcat(parseList[moveNum - 1], elapsed_time);\r
3233             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3234             strcpy(moveList[moveNum - 1], currentMoveString);\r
3235             strcat(moveList[moveNum - 1], "\n");\r
3236         } else if (strcmp(move_str, "none") == 0) {\r
3237             /* Again, we don't know what the board looked like;\r
3238                this is really the start of the game. */\r
3239             parseList[moveNum - 1][0] = NULLCHAR;\r
3240             moveList[moveNum - 1][0] = NULLCHAR;\r
3241             backwardMostMove = moveNum;\r
3242             startedFromSetupPosition = TRUE;\r
3243             fromX = fromY = toX = toY = -1;\r
3244         } else {\r
3245             /* Move from ICS was illegal!?  Punt. */\r
3246 #if 0\r
3247             if (appData.testLegality && appData.debugMode) {\r
3248                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3249                 DisplayError(str, 0);\r
3250             }\r
3251 #endif\r
3252             strcpy(parseList[moveNum - 1], move_str);\r
3253             strcat(parseList[moveNum - 1], " ");\r
3254             strcat(parseList[moveNum - 1], elapsed_time);\r
3255             moveList[moveNum - 1][0] = NULLCHAR;\r
3256             fromX = fromY = toX = toY = -1;\r
3257         }\r
3258 \r
3259 #if ZIPPY\r
3260         /* Send move to chess program (BEFORE animating it). */\r
3261         if (appData.zippyPlay && !newGame && newMove && \r
3262            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3263 \r
3264             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3265                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3266                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3267                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3268                             move_str);\r
3269                     DisplayError(str, 0);\r
3270                 } else {\r
3271                     if (first.sendTime) {\r
3272                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3273                     }\r
3274                     SendMoveToProgram(moveNum - 1, &first);\r
3275                     if (firstMove) {\r
3276                         firstMove = FALSE;\r
3277                         if (first.useColors) {\r
3278                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3279                                         "white\ngo\n" :\r
3280                                         "black\ngo\n", &first);\r
3281                         } else {\r
3282                           SendToProgram("go\n", &first);\r
3283                         }\r
3284                         first.maybeThinking = TRUE;\r
3285                     }\r
3286                 }\r
3287             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3288               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3289                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3290                 DisplayError(str, 0);\r
3291               } else {\r
3292                 SendMoveToProgram(moveNum - 1, &first);\r
3293               }\r
3294             }\r
3295         }\r
3296 #endif\r
3297     }\r
3298 \r
3299     if (moveNum > 0 && !gotPremove) {\r
3300         /* If move comes from a remote source, animate it.  If it\r
3301            isn't remote, it will have already been animated. */\r
3302         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3303             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3304         }\r
3305         if (!pausing && appData.highlightLastMove) {\r
3306             SetHighlights(fromX, fromY, toX, toY);\r
3307         }\r
3308     }\r
3309     \r
3310     /* Start the clocks */\r
3311     whiteFlag = blackFlag = FALSE;\r
3312     appData.clockMode = !(basetime == 0 && increment == 0);\r
3313     if (ticking == 0) {\r
3314       ics_clock_paused = TRUE;\r
3315       StopClocks();\r
3316     } else if (ticking == 1) {\r
3317       ics_clock_paused = FALSE;\r
3318     }\r
3319     if (gameMode == IcsIdle ||\r
3320         relation == RELATION_OBSERVING_STATIC ||\r
3321         relation == RELATION_EXAMINING ||\r
3322         ics_clock_paused)\r
3323       DisplayBothClocks();\r
3324     else\r
3325       StartClocks();\r
3326     \r
3327     /* Display opponents and material strengths */\r
3328     if (gameInfo.variant != VariantBughouse &&\r
3329         gameInfo.variant != VariantCrazyhouse) {\r
3330         if (tinyLayout || smallLayout) {\r
3331             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3332                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3333                     basetime, increment);\r
3334         } else {\r
3335             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3336                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3337                     basetime, increment);\r
3338         }\r
3339         DisplayTitle(str);\r
3340     }\r
3341 \r
3342    \r
3343     /* Display the board */\r
3344     if (!pausing) {\r
3345       \r
3346       if (appData.premove)\r
3347           if (!gotPremove || \r
3348              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3349              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3350               ClearPremoveHighlights();\r
3351 \r
3352       DrawPosition(FALSE, boards[currentMove]);\r
3353       DisplayMove(moveNum - 1);\r
3354       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3355         RingBell();\r
3356     }\r
3357 \r
3358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3359 }\r
3360 \r
3361 void\r
3362 GetMoveListEvent()\r
3363 {\r
3364     char buf[MSG_SIZ];\r
3365     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3366         ics_getting_history = H_REQUESTED;\r
3367         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3368         SendToICS(buf);\r
3369     }\r
3370 }\r
3371 \r
3372 void\r
3373 AnalysisPeriodicEvent(force)\r
3374      int force;\r
3375 {\r
3376     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3377          && !force) || !appData.periodicUpdates)\r
3378       return;\r
3379 \r
3380     /* Send . command to Crafty to collect stats */\r
3381     SendToProgram(".\n", &first);\r
3382 \r
3383     /* Don't send another until we get a response (this makes\r
3384        us stop sending to old Crafty's which don't understand\r
3385        the "." command (sending illegal cmds resets node count & time,\r
3386        which looks bad)) */\r
3387     programStats.ok_to_send = 0;\r
3388 }\r
3389 \r
3390 void\r
3391 SendMoveToProgram(moveNum, cps)\r
3392      int moveNum;\r
3393      ChessProgramState *cps;\r
3394 {\r
3395     char buf[MSG_SIZ];\r
3396     if (cps->useUsermove) {\r
3397       SendToProgram("usermove ", cps);\r
3398     }\r
3399     if (cps->useSAN) {\r
3400       char *space;\r
3401       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3402         int len = space - parseList[moveNum];\r
3403         memcpy(buf, parseList[moveNum], len);\r
3404         buf[len++] = '\n';\r
3405         buf[len] = NULLCHAR;\r
3406       } else {\r
3407         sprintf(buf, "%s\n", parseList[moveNum]);\r
3408       }\r
3409       /* [HGM] decrement all digits to code ranks starting from 0 */\r
3410       if(BOARD_HEIGHT>8) {\r
3411           char *p = buf;\r
3412           while(*p) { if(*p < 'A') (*p)--; p++; }\r
3413       }\r
3414       SendToProgram(buf, cps);\r
3415     } else {\r
3416       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3417        * the engine. It would be nice to have a better way to identify castle \r
3418        * moves here. */\r
3419       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3420         int fromX = moveList[moveNum][0] - AAA; \r
3421         int fromY = moveList[moveNum][1] - ONE;\r
3422         int toX = moveList[moveNum][2] - AAA; \r
3423         int toY = moveList[moveNum][3] - ONE;\r
3424         if((boards[currentMove][fromY][fromX] == WhiteKing \r
3425             && boards[currentMove][toY][toX] == WhiteRook)\r
3426            || (boards[currentMove][fromY][fromX] == BlackKing \r
3427                && boards[currentMove][toY][toX] == BlackRook)) {\r
3428           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3429           else SendToProgram("O-O-O\n", cps);\r
3430         }\r
3431         else SendToProgram(moveList[moveNum], cps);\r
3432       }\r
3433       else SendToProgram(moveList[moveNum], cps);\r
3434       /* End of additions by Tord */\r
3435     }\r
3436 }\r
3437 \r
3438 void\r
3439 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3440      ChessMove moveType;\r
3441      int fromX, fromY, toX, toY;\r
3442 {\r
3443     char user_move[MSG_SIZ];\r
3444 \r
3445     switch (moveType) {\r
3446       default:\r
3447         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3448                 (int)moveType, fromX, fromY, toX, toY);\r
3449         DisplayError(user_move + strlen("say "), 0);\r
3450         break;\r
3451       case WhiteKingSideCastle:\r
3452       case BlackKingSideCastle:\r
3453       case WhiteQueenSideCastleWild:\r
3454       case BlackQueenSideCastleWild:\r
3455       /* PUSH Fabien */\r
3456       case WhiteHSideCastleFR:\r
3457       case BlackHSideCastleFR:\r
3458       /* POP Fabien */\r
3459         sprintf(user_move, "o-o\n");\r
3460         break;\r
3461       case WhiteQueenSideCastle:\r
3462       case BlackQueenSideCastle:\r
3463       case WhiteKingSideCastleWild:\r
3464       case BlackKingSideCastleWild:\r
3465       /* PUSH Fabien */\r
3466       case WhiteASideCastleFR:\r
3467       case BlackASideCastleFR:\r
3468       /* POP Fabien */\r
3469         sprintf(user_move, "o-o-o\n");\r
3470         break;\r
3471       case WhitePromotionQueen:\r
3472       case BlackPromotionQueen:\r
3473       case WhitePromotionRook:\r
3474       case BlackPromotionRook:\r
3475       case WhitePromotionBishop:\r
3476       case BlackPromotionBishop:\r
3477       case WhitePromotionKnight:\r
3478       case BlackPromotionKnight:\r
3479       case WhitePromotionKing:\r
3480       case BlackPromotionKing:\r
3481 #ifdef FAIRY\r
3482       case WhitePromotionChancellor:\r
3483       case BlackPromotionChancellor:\r
3484       case WhitePromotionArchbishop:\r
3485       case BlackPromotionArchbishop:\r
3486 #endif\r
3487         sprintf(user_move, "%c%c%c%c=%c\n",\r
3488                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3489                 PieceToChar(PromoPiece(moveType)));\r
3490         break;\r
3491       case WhiteDrop:\r
3492       case BlackDrop:\r
3493         sprintf(user_move, "%c@%c%c\n",\r
3494                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3495                 AAA + toX, ONE + toY);\r
3496         break;\r
3497       case NormalMove:\r
3498       case WhiteCapturesEnPassant:\r
3499       case BlackCapturesEnPassant:\r
3500       case IllegalMove:  /* could be a variant we don't quite understand */\r
3501         sprintf(user_move, "%c%c%c%c\n",\r
3502                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3503         break;\r
3504     }\r
3505     SendToICS(user_move);\r
3506 }\r
3507 \r
3508 void\r
3509 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3510      int rf, ff, rt, ft;\r
3511      char promoChar;\r
3512      char move[7];\r
3513 {\r
3514     if (rf == DROP_RANK) {\r
3515         sprintf(move, "%c@%c%c\n",\r
3516                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3517     } else {\r
3518         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3519             sprintf(move, "%c%c%c%c\n",\r
3520                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3521         } else {\r
3522             sprintf(move, "%c%c%c%c%c\n",\r
3523                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3524         }\r
3525     }\r
3526     AlphaRank(move, 4);\r
3527 }\r
3528 \r
3529 void\r
3530 ProcessICSInitScript(f)\r
3531      FILE *f;\r
3532 {\r
3533     char buf[MSG_SIZ];\r
3534 \r
3535     while (fgets(buf, MSG_SIZ, f)) {\r
3536         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3537     }\r
3538 \r
3539     fclose(f);\r
3540 }\r
3541 \r
3542 \r
3543 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3544 void\r
3545 AlphaRank(char *move, int n)\r
3546 {\r
3547     char *p = move, c;\r
3548 \r
3549     if( !appData.alphaRank ) return;\r
3550 \r
3551     while(c = *p) {\r
3552         if(c>='0' && c<='9') *p += 'a'-'0'; else\r
3553         if(c>='a' && c<='z') *p -= 'a'-'0';\r
3554         p++;\r
3555         if(--n < 1) break;\r
3556     }\r
3557 }\r
3558 \r
3559 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3560 Boolean\r
3561 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3562      char *move;\r
3563      int moveNum;\r
3564      ChessMove *moveType;\r
3565      int *fromX, *fromY, *toX, *toY;\r
3566      char *promoChar;\r
3567 {       \r
3568     if (appData.debugMode) {\r
3569         fprintf(debugFP, "move to parse: %s\n", move);\r
3570     }\r
3571     AlphaRank(move, 10);\r
3572     *moveType = yylexstr(moveNum, move);\r
3573 \r
3574     switch (*moveType) {\r
3575 #ifdef FAIRY\r
3576       case WhitePromotionChancellor:\r
3577       case BlackPromotionChancellor:\r
3578       case WhitePromotionArchbishop:\r
3579       case BlackPromotionArchbishop:\r
3580 #endif\r
3581       case WhitePromotionQueen:\r
3582       case BlackPromotionQueen:\r
3583       case WhitePromotionRook:\r
3584       case BlackPromotionRook:\r
3585       case WhitePromotionBishop:\r
3586       case BlackPromotionBishop:\r
3587       case WhitePromotionKnight:\r
3588       case BlackPromotionKnight:\r
3589       case WhitePromotionKing:\r
3590       case BlackPromotionKing:\r
3591       case NormalMove:\r
3592       case WhiteCapturesEnPassant:\r
3593       case BlackCapturesEnPassant:\r
3594       case WhiteKingSideCastle:\r
3595       case WhiteQueenSideCastle:\r
3596       case BlackKingSideCastle:\r
3597       case BlackQueenSideCastle:\r
3598       case WhiteKingSideCastleWild:\r
3599       case WhiteQueenSideCastleWild:\r
3600       case BlackKingSideCastleWild:\r
3601       case BlackQueenSideCastleWild:\r
3602       /* Code added by Tord: */\r
3603       case WhiteHSideCastleFR:\r
3604       case WhiteASideCastleFR:\r
3605       case BlackHSideCastleFR:\r
3606       case BlackASideCastleFR:\r
3607       /* End of code added by Tord */\r
3608       case IllegalMove:         /* bug or odd chess variant */\r
3609         *fromX = currentMoveString[0] - AAA;\r
3610         *fromY = currentMoveString[1] - ONE;\r
3611         *toX = currentMoveString[2] - AAA;\r
3612         *toY = currentMoveString[3] - ONE;\r
3613         *promoChar = currentMoveString[4];\r
3614         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3615             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3616             *fromX = *fromY = *toX = *toY = 0;\r
3617             return FALSE;\r
3618         }\r
3619         if (appData.testLegality) {\r
3620           return (*moveType != IllegalMove);\r
3621         } else {\r
3622           return !(fromX == fromY && toX == toY);\r
3623         }\r
3624 \r
3625       case WhiteDrop:\r
3626       case BlackDrop:\r
3627         *fromX = *moveType == WhiteDrop ?\r
3628           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3629         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3630         *fromY = DROP_RANK;\r
3631         *toX = currentMoveString[2] - AAA;\r
3632         *toY = currentMoveString[3] - ONE;\r
3633         *promoChar = NULLCHAR;\r
3634         return TRUE;\r
3635 \r
3636       case AmbiguousMove:\r
3637       case ImpossibleMove:\r
3638       case (ChessMove) 0:       /* end of file */\r
3639       case ElapsedTime:\r
3640       case Comment:\r
3641       case PGNTag:\r
3642       case NAG:\r
3643       case WhiteWins:\r
3644       case BlackWins:\r
3645       case GameIsDrawn:\r
3646       default:\r
3647         /* bug? */\r
3648         *fromX = *fromY = *toX = *toY = 0;\r
3649         *promoChar = NULLCHAR;\r
3650         return FALSE;\r
3651     }\r
3652 }\r
3653 \r
3654 /* [AS] FRC game initialization */\r
3655 static int FindEmptySquare( Board board, int n )\r
3656 {\r
3657     int i = 0;\r
3658 \r
3659     while( 1 ) {\r
3660         while( board[0][i] != EmptySquare ) i++;\r
3661         if( n == 0 )\r
3662             break;\r
3663         n--;\r
3664         i++;\r
3665     }\r
3666 \r
3667     return i;\r
3668 }\r
3669 \r
3670 static void ShuffleFRC( Board board )\r
3671 {\r
3672     int i;\r
3673 \r
3674     srand( time(0) );\r
3675     \r
3676     for( i=0; i<8; i++ ) {\r
3677         board[0][i] = EmptySquare;\r
3678     }\r
3679 \r
3680     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
3681     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
3682     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
3683     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
3684     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
3685     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3686     board[0][FindEmptySquare(board, 0)] = WhiteKing;\r
3687     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3688 \r
3689     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3690         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3691     }\r
3692 }\r
3693 \r
3694 static unsigned char FRC_KnightTable[10] = {\r
3695     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
3696 };\r
3697 \r
3698 static void SetupFRC( Board board, int pos_index )\r
3699 {\r
3700     int i;\r
3701     unsigned char knights;\r
3702 \r
3703     /* Bring the position index into a safe range (just in case...) */\r
3704     if( pos_index < 0 ) pos_index = 0;\r
3705 \r
3706     pos_index %= 960;\r
3707 \r
3708     /* Clear the board */\r
3709     for( i=0; i<8; i++ ) {\r
3710         board[0][i] = EmptySquare;\r
3711     }\r
3712 \r
3713     /* Place bishops and queen */\r
3714     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
3715     pos_index /= 4;\r
3716     \r
3717     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
3718     pos_index /= 4;\r
3719 \r
3720     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
3721     pos_index /= 6;\r
3722 \r
3723     /* Place knigths */\r
3724     knights = FRC_KnightTable[ pos_index ];\r
3725 \r
3726     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
3727     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
3728 \r
3729     /* Place rooks and king */\r
3730     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3731     board[0][ FindEmptySquare(board, 0) ] = WhiteKing;\r
3732     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3733 \r
3734     /* Mirror piece placement for black */\r
3735     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3736         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3737     }\r
3738 }\r
3739 \r
3740 void\r
3741 InitPosition(redraw)\r
3742      int redraw;\r
3743 {\r
3744     ChessSquare (* pieces)[BOARD_SIZE];\r
3745     int i, j, pawnRow, overrule,\r
3746     oldx = gameInfo.boardWidth,\r
3747     oldy = gameInfo.boardHeight,\r
3748     oldh = gameInfo.holdingsWidth;\r
3749 \r
3750     currentMove = forwardMostMove = backwardMostMove = 0;\r
3751 \r
3752     /* [AS] Initialize pv info list [HGM] and game status */\r
3753     {\r
3754         for( i=0; i<MAX_MOVES; i++ ) {\r
3755             pvInfoList[i].depth = 0;\r
3756             epStatus[i]=EP_NONE;\r
3757             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
3758         }\r
3759 \r
3760         /* [HGM] Build normal castling rights */\r
3761         for( j=0; j<BOARD_SIZE; j++ ) initialRights[j] = -1;\r
3762         nrCastlingRights = 6;\r
3763         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
3764         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
3765         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
3766         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
3767         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
3768         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
3769 \r
3770         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
3771         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
3772 \r
3773         initialRulePlies = 0; /* 50-move counter start */\r
3774     }\r
3775 \r
3776     \r
3777     /* [HGM] logic here is completely changed. In stead of full positions */\r
3778     /* the initialized data only consist of the two backranks. The switch */\r
3779     /* selects which one we will use, which is than copied to the Board   */\r
3780     /* initialPosition, which for the rest is initialized by Pawns and    */\r
3781     /* empty squares. This initial position is then copied to boards[0],  */\r
3782     /* possibly after shuffling, so that it remains available.            */\r
3783 \r
3784     gameInfo.holdingsWidth = 0; /* default board sizes */\r
3785     gameInfo.boardWidth    = 8;\r
3786     gameInfo.boardHeight   = 8;\r
3787     gameInfo.holdingsSize  = 0;\r
3788 \r
3789     switch (gameInfo.variant) {\r
3790     default:\r
3791       pieces = FIDEArray;\r
3792       break;\r
3793     case VariantShatranj:\r
3794       pieces = ShatranjArray;\r
3795       nrCastlingRights = 0;\r
3796       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3797       break;\r
3798     case VariantTwoKings:\r
3799       pieces = twoKingsArray;\r
3800       nrCastlingRights = 8;                 /* add rights for second King */\r
3801       castlingRights[0][6] = initialRights[2] = 5;\r
3802       castlingRights[0][7] = initialRights[5] = 5;\r
3803       castlingRank[6] = 0;\r
3804       castlingRank[6] = BOARD_HEIGHT-1;\r
3805       startedFromSetupPosition = TRUE;\r
3806       break;\r
3807     case VariantCapablanca:\r
3808       pieces = CapablancaArray;\r
3809       gameInfo.boardWidth = 10;\r
3810       break;\r
3811     case VariantGothic:\r
3812       pieces = GothicArray;\r
3813       gameInfo.boardWidth = 10;\r
3814       break;\r
3815     case VariantXiangqi:\r
3816       pieces = XiangqiArray;\r
3817       gameInfo.boardWidth  = 9;\r
3818       gameInfo.boardHeight = 10;\r
3819       nrCastlingRights = 0;\r
3820       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3821       strcpy(pieceToChar, "PN.R.MKE...C....pn.r.mke...c...."); \r
3822       break;\r
3823     case VariantShogi:\r
3824       pieces = ShogiArray;\r
3825       gameInfo.boardWidth  = 9;\r
3826       gameInfo.boardHeight = 9;\r
3827       gameInfo.holdingsSize = 7;\r
3828       nrCastlingRights = 0;\r
3829       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3830       strcpy(pieceToChar, "PNBRLSGPNBRLS..Kpnbrlsgpnbrls..k"); \r
3831       break;\r
3832     case VariantShowgi:\r
3833       pieces = ShogiArray;\r
3834       gameInfo.boardWidth  = 9;\r
3835       gameInfo.boardHeight = 9;\r
3836       gameInfo.holdingsSize = 7;\r
3837       nrCastlingRights = 0;\r
3838       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3839       strcpy(pieceToChar, "PNBRQFWEHACGOUMKpnbrlsgpnbrls..k"); \r
3840       break;\r
3841     case VariantCourier:\r
3842       pieces = CourierArray;\r
3843       gameInfo.boardWidth  = 12;\r
3844       nrCastlingRights = 0;\r
3845       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3846       break;\r
3847     case VariantKnightmate:\r
3848       pieces = KnightmateArray;\r
3849       strcpy(pieceToChar, "PNBRQFWEHACGOMK.pnbrqfwehacgomK."); \r
3850       break;\r
3851     case VariantFairy:\r
3852       pieces = fairyArray;\r
3853       strcpy(pieceToChar, "PNBRQFWEHACGOMUKpnbrqfwehacgomuk"); \r
3854       startedFromSetupPosition = TRUE;\r
3855       break;\r
3856     case VariantCrazyhouse:\r
3857     case VariantBughouse:\r
3858       pieces = FIDEArray;\r
3859       gameInfo.holdingsSize = 5;\r
3860       strcpy(pieceToChar, "PNBRQ...NBRQ...Kpnbrq...nbrq...k"); \r
3861       break;\r
3862     case VariantWildCastle:\r
3863       pieces = FIDEArray;\r
3864       /* !!?shuffle with kings guaranteed to be on d or e file */\r
3865       break;\r
3866     case VariantNoCastle:\r
3867       pieces = FIDEArray;\r
3868       nrCastlingRights = 0;\r
3869       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3870       /* !!?unconstrained back-rank shuffle */\r
3871       break;\r
3872     }\r
3873 \r
3874     overrule = 0;\r
3875     if(appData.NrFiles >= 0) {\r
3876         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
3877         gameInfo.boardWidth = appData.NrFiles;\r
3878     }\r
3879     if(appData.NrRanks >= 0) {\r
3880         if(gameInfo.boardHeight != appData.NrRanks) overrule++;\r
3881         gameInfo.boardHeight = appData.NrRanks;\r
3882     }\r
3883     if(appData.holdingsSize >= 0) {\r
3884         i = appData.holdingsSize;\r
3885         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
3886         gameInfo.holdingsSize = i;\r
3887     }\r
3888     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
3889     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
3890         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
3891 \r
3892     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all variants */\r
3893 \r
3894     /* User pieceToChar list overrules defaults */\r
3895     if(appData.pieceToCharTable != NULL)\r
3896         strcpy(pieceToChar, appData.pieceToCharTable);\r
3897 \r
3898     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
3899 \r
3900         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
3901             s = (ChessSquare) 0; /* account holding counts in guard band */\r
3902         for( i=0; i<BOARD_HEIGHT; i++ )\r
3903             initialPosition[i][j] = s;\r
3904 \r
3905         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
3906         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
3907         initialPosition[pawnRow][j] = WhitePawn;\r
3908         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
3909         if(gameInfo.variant == VariantXiangqi) {\r
3910             if(j&1) {\r
3911                 initialPosition[pawnRow][j] = \r
3912                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
3913                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
3914                    initialPosition[2][j] = WhiteCannon;\r
3915                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
3916                 }\r
3917             }\r
3918         }\r
3919         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
3920     }\r
3921     if( (gameInfo.variant == VariantShogi\r
3922        ||gameInfo.variant == VariantShowgi\r
3923                                          ) && !overrule ) {\r
3924             j=BOARD_LEFT+1;\r
3925             initialPosition[1][j] = WhiteBishop;\r
3926             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
3927             j=BOARD_RGHT-2;\r
3928             initialPosition[1][j] = WhiteRook;\r
3929             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
3930     }\r
3931 \r
3932     if(gameInfo.variant == VariantFischeRandom) {\r
3933       if( appData.defaultFrcPosition < 0 ) {\r
3934         ShuffleFRC( initialPosition );\r
3935       }\r
3936       else {\r
3937         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
3938       }\r
3939     }\r
3940 \r
3941     CopyBoard(boards[0], initialPosition);\r
3942 \r
3943     if(oldx != gameInfo.boardWidth ||\r
3944        oldy != gameInfo.boardHeight ||\r
3945        oldh != gameInfo.holdingsWidth )\r
3946             InitDrawingSizes(-1 ,0);\r
3947 \r
3948     if (redraw)\r
3949       DrawPosition(TRUE, boards[currentMove]);\r
3950 }\r
3951 \r
3952 void\r
3953 SendBoard(cps, moveNum)\r
3954      ChessProgramState *cps;\r
3955      int moveNum;\r
3956 {\r
3957     char message[MSG_SIZ];\r
3958     \r
3959     if (cps->useSetboard) {\r
3960       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
3961       sprintf(message, "setboard %s\n", fen);\r
3962       SendToProgram(message, cps);\r
3963       free(fen);\r
3964 \r
3965     } else {\r
3966       ChessSquare *bp;\r
3967       int i, j;\r
3968       /* Kludge to set black to move, avoiding the troublesome and now\r
3969        * deprecated "black" command.\r
3970        */\r
3971       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
3972 \r
3973       SendToProgram("edit\n", cps);\r
3974       SendToProgram("#\n", cps);\r
3975       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
3976         bp = &boards[moveNum][i][0];\r
3977         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
3978           if ((int) *bp < (int) BlackPawn) {\r
3979             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
3980                     AAA + j, ONE + i);\r
3981             SendToProgram(message, cps);\r
3982           }\r
3983         }\r
3984       }\r
3985     \r
3986       SendToProgram("c\n", cps);\r
3987       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
3988         bp = &boards[moveNum][i][0];\r
3989         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
3990           if (((int) *bp != (int) EmptySquare)\r
3991               && ((int) *bp >= (int) BlackPawn)) {\r
3992             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
3993                     AAA + j, ONE + i);\r
3994             SendToProgram(message, cps);\r
3995           }\r
3996         }\r
3997       }\r
3998     \r
3999       SendToProgram(".\n", cps);\r
4000     }\r
4001 }\r
4002 \r
4003 int\r
4004 IsPromotion(fromX, fromY, toX, toY)\r
4005      int fromX, fromY, toX, toY;\r
4006 {\r
4007     /* [HGM] add Shogi promotions */\r
4008     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4009     ChessSquare piece;\r
4010 \r
4011     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4012       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4013    /* [HGM] Note to self: line above also weeds out drops */\r
4014     piece = boards[currentMove][fromY][fromX];\r
4015     if(gameInfo.variant == VariantShogi) {\r
4016         promotionZoneSize = 3;\r
4017         highestPromotingPiece = (int)WhiteFerz; /* Silver */\r
4018     }\r
4019     if((int)piece >= BlackPawn) {\r
4020         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4021              return FALSE;\r
4022         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4023     } else {\r
4024         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4025            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4026     }\r
4027     return ( (int)piece <= highestPromotingPiece );\r
4028 }\r
4029 \r
4030 int\r
4031 InPalace(row, column)\r
4032      int row, column;\r
4033 {   /* [HGM] for Xiangqi */\r
4034     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4035          column < (BOARD_WIDTH + 4)/2 &&\r
4036          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4037     return FALSE;\r
4038 }\r
4039 \r
4040 int\r
4041 PieceForSquare (x, y)\r
4042      int x;\r
4043      int y;\r
4044 {\r
4045   if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)\r
4046      return -1;\r
4047   else\r
4048      return boards[currentMove][y][x];\r
4049 }\r
4050 \r
4051 int\r
4052 OKToStartUserMove(x, y)\r
4053      int x, y;\r
4054 {\r
4055     ChessSquare from_piece;\r
4056     int white_piece;\r
4057 \r
4058     if (matchMode) return FALSE;\r
4059     if (gameMode == EditPosition) return TRUE;\r
4060 \r
4061     if (x >= 0 && y >= 0)\r
4062       from_piece = boards[currentMove][y][x];\r
4063     else\r
4064       from_piece = EmptySquare;\r
4065 \r
4066     if (from_piece == EmptySquare) return FALSE;\r
4067 \r
4068     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4069       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4070 \r
4071     switch (gameMode) {\r
4072       case PlayFromGameFile:\r
4073       case AnalyzeFile:\r
4074       case TwoMachinesPlay:\r
4075       case EndOfGame:\r
4076         return FALSE;\r
4077 \r
4078       case IcsObserving:\r
4079       case IcsIdle:\r
4080         return FALSE;\r
4081 \r
4082       case MachinePlaysWhite:\r
4083       case IcsPlayingBlack:\r
4084         if (appData.zippyPlay) return FALSE;\r
4085         if (white_piece) {\r
4086             DisplayMoveError("You are playing Black");\r
4087             return FALSE;\r
4088         }\r
4089         break;\r
4090 \r
4091       case MachinePlaysBlack:\r
4092       case IcsPlayingWhite:\r
4093         if (appData.zippyPlay) return FALSE;\r
4094         if (!white_piece) {\r
4095             DisplayMoveError("You are playing White");\r
4096             return FALSE;\r
4097         }\r
4098         break;\r
4099 \r
4100       case EditGame:\r
4101         if (!white_piece && WhiteOnMove(currentMove)) {\r
4102             DisplayMoveError("It is White's turn");\r
4103             return FALSE;\r
4104         }           \r
4105         if (white_piece && !WhiteOnMove(currentMove)) {\r
4106             DisplayMoveError("It is Black's turn");\r
4107             return FALSE;\r
4108         }           \r
4109         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4110             /* Editing correspondence game history */\r
4111             /* Could disallow this or prompt for confirmation */\r
4112             cmailOldMove = -1;\r
4113         }\r
4114         if (currentMove < forwardMostMove) {\r
4115             /* Discarding moves */\r
4116             /* Could prompt for confirmation here,\r
4117                but I don't think that's such a good idea */\r
4118             forwardMostMove = currentMove;\r
4119         }\r
4120         break;\r
4121 \r
4122       case BeginningOfGame:\r
4123         if (appData.icsActive) return FALSE;\r
4124         if (!appData.noChessProgram) {\r
4125             if (!white_piece) {\r
4126                 DisplayMoveError("You are playing White");\r
4127                 return FALSE;\r
4128             }\r
4129         }\r
4130         break;\r
4131         \r
4132       case Training:\r
4133         if (!white_piece && WhiteOnMove(currentMove)) {\r
4134             DisplayMoveError("It is White's turn");\r
4135             return FALSE;\r
4136         }           \r
4137         if (white_piece && !WhiteOnMove(currentMove)) {\r
4138             DisplayMoveError("It is Black's turn");\r
4139             return FALSE;\r
4140         }           \r
4141         break;\r
4142 \r
4143       default:\r
4144       case IcsExamining:\r
4145         break;\r
4146     }\r
4147     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4148         && gameMode != AnalyzeFile && gameMode != Training) {\r
4149         DisplayMoveError("Displayed position is not current");\r
4150         return FALSE;\r
4151     }\r
4152     return TRUE;\r
4153 }\r
4154 \r
4155 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4156 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4157 int lastLoadGameUseList = FALSE;\r
4158 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4159 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4160 \r
4161 \r
4162 ChessMove\r
4163 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4164      int fromX, fromY, toX, toY;\r
4165      int promoChar;\r
4166 {\r
4167     ChessMove moveType;\r
4168 \r
4169     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4170     if ((fromX == toX) && (fromY == toY)) {\r
4171         return ImpossibleMove;\r
4172     }\r
4173     /* [HGM] suppress all moves into holdings area and guard band */\r
4174     if( toX < BOARD_LEFT || toX >= BOARD_RGHT ) return ImpossibleMove;\r
4175         \r
4176     /* Check if the user is playing in turn.  This is complicated because we\r
4177        let the user "pick up" a piece before it is his turn.  So the piece he\r
4178        tried to pick up may have been captured by the time he puts it down!\r
4179        Therefore we use the color the user is supposed to be playing in this\r
4180        test, not the color of the piece that is currently on the starting\r
4181        square---except in EditGame mode, where the user is playing both\r
4182        sides; fortunately there the capture race can't happen.  (It can\r
4183        now happen in IcsExamining mode, but that's just too bad.  The user\r
4184        will get a somewhat confusing message in that case.)\r
4185        */\r
4186 \r
4187     switch (gameMode) {\r
4188       case PlayFromGameFile:\r
4189       case AnalyzeFile:\r
4190       case TwoMachinesPlay:\r
4191       case EndOfGame:\r
4192       case IcsObserving:\r
4193       case IcsIdle:\r
4194         /* We switched into a game mode where moves are not accepted,\r
4195            perhaps while the mouse button was down. */\r
4196         return ImpossibleMove;\r
4197 \r
4198       case MachinePlaysWhite:\r
4199         /* User is moving for Black */\r
4200         if (WhiteOnMove(currentMove)) {\r
4201             DisplayMoveError("It is White's turn");\r
4202             return ImpossibleMove;\r
4203         }\r
4204         break;\r
4205 \r
4206       case MachinePlaysBlack:\r
4207         /* User is moving for White */\r
4208         if (!WhiteOnMove(currentMove)) {\r
4209             DisplayMoveError("It is Black's turn");\r
4210             return ImpossibleMove;\r
4211         }\r
4212         break;\r
4213 \r
4214       case EditGame:\r
4215       case IcsExamining:\r
4216       case BeginningOfGame:\r
4217       case AnalyzeMode:\r
4218       case Training:\r
4219         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4220             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4221             /* User is moving for Black */\r
4222             if (WhiteOnMove(currentMove)) {\r
4223                 DisplayMoveError("It is White's turn");\r
4224                 return ImpossibleMove;\r
4225             }\r
4226         } else {\r
4227             /* User is moving for White */\r
4228             if (!WhiteOnMove(currentMove)) {\r
4229                 DisplayMoveError("It is Black's turn");\r
4230                 return ImpossibleMove;\r
4231             }\r
4232         }\r
4233         break;\r
4234 \r
4235       case IcsPlayingBlack:\r
4236         /* User is moving for Black */\r
4237         if (WhiteOnMove(currentMove)) {\r
4238             if (!appData.premove) {\r
4239                 DisplayMoveError("It is White's turn");\r
4240             } else if (toX >= 0 && toY >= 0) {\r
4241                 premoveToX = toX;\r
4242                 premoveToY = toY;\r
4243                 premoveFromX = fromX;\r
4244                 premoveFromY = fromY;\r
4245                 premovePromoChar = promoChar;\r
4246                 gotPremove = 1;\r
4247                 if (appData.debugMode) \r
4248                     fprintf(debugFP, "Got premove: fromX %d,"\r
4249                             "fromY %d, toX %d, toY %d\n",\r
4250                             fromX, fromY, toX, toY);\r
4251             }\r
4252             return ImpossibleMove;\r
4253         }\r
4254         break;\r
4255 \r
4256       case IcsPlayingWhite:\r
4257         /* User is moving for White */\r
4258         if (!WhiteOnMove(currentMove)) {\r
4259             if (!appData.premove) {\r
4260                 DisplayMoveError("It is Black's turn");\r
4261             } else if (toX >= 0 && toY >= 0) {\r
4262                 premoveToX = toX;\r
4263                 premoveToY = toY;\r
4264                 premoveFromX = fromX;\r
4265                 premoveFromY = fromY;\r
4266                 premovePromoChar = promoChar;\r
4267                 gotPremove = 1;\r
4268                 if (appData.debugMode) \r
4269                     fprintf(debugFP, "Got premove: fromX %d,"\r
4270                             "fromY %d, toX %d, toY %d\n",\r
4271                             fromX, fromY, toX, toY);\r
4272             }\r
4273             return ImpossibleMove;\r
4274         }\r
4275         break;\r
4276 \r
4277       default:\r
4278         break;\r
4279 \r
4280       case EditPosition:\r
4281         if (toX == -2 || toY == -2) {\r
4282             boards[0][fromY][fromX] = EmptySquare;\r
4283             DrawPosition(FALSE, boards[currentMove]);\r
4284         } else if (toX >= 0 && toY >= 0) {\r
4285             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4286             boards[0][fromY][fromX] = EmptySquare;\r
4287             DrawPosition(FALSE, boards[currentMove]);\r
4288         }\r
4289         return ImpossibleMove;\r
4290     }\r
4291 \r
4292     if (toX < 0 || toY < 0) return ImpossibleMove;\r
4293 \r
4294     /* [HGM] If move started in holdings, it means a drop */\r
4295     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4296          if( boards[currentMove][toY][toX] != EmptySquare ) return ImpossibleMove;\r
4297          return WhiteDrop; /* Not needed to specify white or black yet */\r
4298     }\r
4299 \r
4300     userOfferedDraw = FALSE;\r
4301         \r
4302     if (appData.testLegality) {\r
4303         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4304                                 EP_UNKNOWN, castlingRights[currentMove],\r
4305                                          fromY, fromX, toY, toX, promoChar);\r
4306         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4307             DisplayMoveError("Illegal move");\r
4308             return ImpossibleMove;\r
4309         }\r
4310     } else {\r
4311         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4312     }\r
4313 \r
4314     return moveType;\r
4315     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4316        function is made into one that returns an OK move type if FinishMove\r
4317        should be called. This to give the calling driver routine the\r
4318        opportunity to finish the userMove input with a promotion popup,\r
4319        without bothering the user with this for invalid or illegal moves */\r
4320 \r
4321 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4322 }\r
4323 \r
4324 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4325 void\r
4326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4327      ChessMove moveType;\r
4328      int fromX, fromY, toX, toY;\r
4329      /*char*/int promoChar;\r
4330 {\r
4331     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4332        move type in caller when we know the move is a legal promotion */\r
4333     if(moveType == NormalMove)\r
4334         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4335 \r
4336     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4337     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4338          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4339          fromX = boards[currentMove][fromY][fromX];\r
4340          fromY = DROP_RANK;\r
4341     }\r
4342 \r
4343     /* [HGM] <popupFix> The following if has been moved here from\r
4344        UserMoveEnevt(). Because it seemed to belon here (why not allow\r
4345        piece drops in training games?), and because it can only be\r
4346        performed after it is known to what we promote. */\r
4347     if (gameMode == Training) {\r
4348       /* compare the move played on the board to the next move in the\r
4349        * game. If they match, display the move and the opponent's response. \r
4350        * If they don't match, display an error message.\r
4351        */\r
4352       int saveAnimate;\r
4353       Board testBoard;\r
4354       CopyBoard(testBoard, boards[currentMove]);\r
4355       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4356 \r
4357       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4358         ForwardInner(currentMove+1);\r
4359 \r
4360         /* Autoplay the opponent's response.\r
4361          * if appData.animate was TRUE when Training mode was entered,\r
4362          * the response will be animated.\r
4363          */\r
4364         saveAnimate = appData.animate;\r
4365         appData.animate = animateTraining;\r
4366         ForwardInner(currentMove+1);\r
4367         appData.animate = saveAnimate;\r
4368 \r
4369         /* check for the end of the game */\r
4370         if (currentMove >= forwardMostMove) {\r
4371           gameMode = PlayFromGameFile;\r
4372           ModeHighlight();\r
4373           SetTrainingModeOff();\r
4374           DisplayInformation("End of game");\r
4375         }\r
4376       } else {\r
4377         DisplayError("Incorrect move", 0);\r
4378       }\r
4379       return;\r
4380     }\r
4381 \r
4382   /* Ok, now we know that the move is good, so we can kill\r
4383      the previous line in Analysis Mode */\r
4384   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4385     forwardMostMove = currentMove;\r
4386   }\r
4387 \r
4388   /* If we need the chess program but it's dead, restart it */\r
4389   ResurrectChessProgram();\r
4390 \r
4391   /* A user move restarts a paused game*/\r
4392   if (pausing)\r
4393     PauseEvent();\r
4394 \r
4395   thinkOutput[0] = NULLCHAR;\r
4396 \r
4397   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4398 \r
4399   if (gameMode == BeginningOfGame) {\r
4400     if (appData.noChessProgram) {\r
4401       gameMode = EditGame;\r
4402       SetGameInfo();\r
4403     } else {\r
4404       char buf[MSG_SIZ];\r
4405       gameMode = MachinePlaysBlack;\r
4406       SetGameInfo();\r
4407       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4408       DisplayTitle(buf);\r
4409       if (first.sendName) {\r
4410         sprintf(buf, "name %s\n", gameInfo.white);\r
4411         SendToProgram(buf, &first);\r
4412       }\r
4413     }\r
4414     ModeHighlight();\r
4415   }\r
4416 \r
4417   /* Relay move to ICS or chess engine */\r
4418   if (appData.icsActive) {\r
4419     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4420         gameMode == IcsExamining) {\r
4421       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4422       ics_user_moved = 1;\r
4423     }\r
4424   } else {\r
4425     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4426                            gameMode == MachinePlaysWhite ||\r
4427                            gameMode == MachinePlaysBlack)) {\r
4428       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4429     }\r
4430     SendMoveToProgram(forwardMostMove-1, &first);\r
4431     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4432       first.maybeThinking = TRUE;\r
4433     }\r
4434     if (currentMove == cmailOldMove + 1) {\r
4435       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4436     }\r
4437   }\r
4438 \r
4439   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4440 \r
4441   switch (gameMode) {\r
4442   case EditGame:\r
4443     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4444                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4445     case MT_NONE:\r
4446     case MT_CHECK:\r
4447       break;\r
4448     case MT_CHECKMATE:\r
4449       if (WhiteOnMove(currentMove)) {\r
4450         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4451       } else {\r
4452         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4453       }\r
4454       break;\r
4455     case MT_STALEMATE:\r
4456       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4457       break;\r
4458     }\r
4459     break;\r
4460     \r
4461   case MachinePlaysBlack:\r
4462   case MachinePlaysWhite:\r
4463     /* disable certain menu options while machine is thinking */\r
4464     SetMachineThinkingEnables();\r
4465     break;\r
4466 \r
4467   default:\r
4468     break;\r
4469   }\r
4470 }\r
4471 \r
4472 void\r
4473 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
4474      int fromX, fromY, toX, toY;\r
4475      int promoChar;\r
4476 {\r
4477     /* [HGM] This routine was added to allow calling of its two logical\r
4478        parts from other modules in the old way. Before, UserMoveEvent()\r
4479        automatically called FinishMove() if the move was OK, and returned\r
4480        otherwise. I separated the two, in order to make it possible to\r
4481        slip a promotion popup in between. But that it always needs two\r
4482        calls, to the first part, (now called UserMoveTest() ), and to\r
4483        FinishMove if the first part succeeded. Calls that do not need\r
4484        to do anything in between, can call this routine the old way. \r
4485     */\r
4486     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
4487 \r
4488     if(moveType != ImpossibleMove)\r
4489         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4490 }\r
4491 \r
4492 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4493 {\r
4494     char * hint = lastHint;\r
4495     FrontEndProgramStats stats;\r
4496 \r
4497     stats.which = cps == &first ? 0 : 1;\r
4498     stats.depth = cpstats->depth;\r
4499     stats.nodes = cpstats->nodes;\r
4500     stats.score = cpstats->score;\r
4501     stats.time = cpstats->time;\r
4502     stats.pv = cpstats->movelist;\r
4503     stats.hint = lastHint;\r
4504     stats.an_move_index = 0;\r
4505     stats.an_move_count = 0;\r
4506 \r
4507     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4508         stats.hint = cpstats->move_name;\r
4509         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4510         stats.an_move_count = cpstats->nr_moves;\r
4511     }\r
4512 \r
4513     SetProgramStats( &stats );\r
4514 }\r
4515 \r
4516 void\r
4517 HandleMachineMove(message, cps)\r
4518      char *message;\r
4519      ChessProgramState *cps;\r
4520 {\r
4521     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4522     char realname[MSG_SIZ];\r
4523     int fromX, fromY, toX, toY;\r
4524     ChessMove moveType;\r
4525     char promoChar;\r
4526     char *p;\r
4527     int machineWhite;\r
4528 \r
4529     /*\r
4530      * Kludge to ignore BEL characters\r
4531      */\r
4532     while (*message == '\007') message++;\r
4533 \r
4534     /*\r
4535      * Look for book output\r
4536      */\r
4537     if (cps == &first && bookRequested) {\r
4538         if (message[0] == '\t' || message[0] == ' ') {\r
4539             /* Part of the book output is here; append it */\r
4540             strcat(bookOutput, message);\r
4541             strcat(bookOutput, "  \n");\r
4542             return;\r
4543         } else if (bookOutput[0] != NULLCHAR) {\r
4544             /* All of book output has arrived; display it */\r
4545             char *p = bookOutput;\r
4546             while (*p != NULLCHAR) {\r
4547                 if (*p == '\t') *p = ' ';\r
4548                 p++;\r
4549             }\r
4550             DisplayInformation(bookOutput);\r
4551             bookRequested = FALSE;\r
4552             /* Fall through to parse the current output */\r
4553         }\r
4554     }\r
4555 \r
4556     /*\r
4557      * Look for machine move.\r
4558      */\r
4559     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4560         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4561     {\r
4562         /* This method is only useful on engines that support ping */\r
4563         if (cps->lastPing != cps->lastPong) {\r
4564           if (gameMode == BeginningOfGame) {\r
4565             /* Extra move from before last new; ignore */\r
4566             if (appData.debugMode) {\r
4567                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4568             }\r
4569           } else {\r
4570             if (appData.debugMode) {\r
4571                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4572                         cps->which, gameMode);\r
4573             }\r
4574 \r
4575             SendToProgram("undo\n", cps);\r
4576           }\r
4577           return;\r
4578         }\r
4579 \r
4580         switch (gameMode) {\r
4581           case BeginningOfGame:\r
4582             /* Extra move from before last reset; ignore */\r
4583             if (appData.debugMode) {\r
4584                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4585             }\r
4586             return;\r
4587 \r
4588           case EndOfGame:\r
4589           case IcsIdle:\r
4590           default:\r
4591             /* Extra move after we tried to stop.  The mode test is\r
4592                not a reliable way of detecting this problem, but it's\r
4593                the best we can do on engines that don't support ping.\r
4594             */\r
4595             if (appData.debugMode) {\r
4596                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4597                         cps->which, gameMode);\r
4598             }\r
4599             SendToProgram("undo\n", cps);\r
4600             return;\r
4601 \r
4602           case MachinePlaysWhite:\r
4603           case IcsPlayingWhite:\r
4604             machineWhite = TRUE;\r
4605             break;\r
4606 \r
4607           case MachinePlaysBlack:\r
4608           case IcsPlayingBlack:\r
4609             machineWhite = FALSE;\r
4610             break;\r
4611 \r
4612           case TwoMachinesPlay:\r
4613             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
4614             break;\r
4615         }\r
4616         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
4617             if (appData.debugMode) {\r
4618                 fprintf(debugFP,\r
4619                         "Ignoring move out of turn by %s, gameMode %d"\r
4620                         ", forwardMost %d\n",\r
4621                         cps->which, gameMode, forwardMostMove);\r
4622             }\r
4623             return;\r
4624         }\r
4625 \r
4626         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
4627                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4628             /* Machine move could not be parsed; ignore it. */\r
4629             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
4630                     machineMove, cps->which);\r
4631             DisplayError(buf1, 0);\r
4632             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
4633                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4634             if (gameMode == TwoMachinesPlay) {\r
4635               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4636                        buf1, GE_XBOARD);\r
4637             }\r
4638             return;\r
4639         }\r
4640 \r
4641         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
4642         /* So we have to redo legality test with true e.p. status here,  */\r
4643         /* to make sure an illegal e.p. capture does not slip through,   */\r
4644         /* to cause a forfeit on a justified illegal-move complaint      */\r
4645         /* of the opponent.                                              */\r
4646         if(gameMode==TwoMachinesPlay && appData.testLegality &&\r
4647            fromY != DROP_RANK && /* [HGM] temporary; should still add legality test for drops */\r
4648            LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
4649                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
4650                              fromY, fromX, toY, toX, promoChar) == IllegalMove)\r
4651            {\r
4652             if (appData.debugMode) {\r
4653                 int i;\r
4654                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
4655                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
4656                 fprintf(debugFP, "castling rights\n");\r
4657             }\r
4658             sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
4659                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4660               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4661                        buf1, GE_XBOARD);\r
4662            }\r
4663         hintRequested = FALSE;\r
4664         lastHint[0] = NULLCHAR;\r
4665         bookRequested = FALSE;\r
4666         /* Program may be pondering now */\r
4667         cps->maybeThinking = TRUE;\r
4668         if (cps->sendTime == 2) cps->sendTime = 1;\r
4669         if (cps->offeredDraw) cps->offeredDraw--;\r
4670 \r
4671 #if ZIPPY\r
4672         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
4673             first.initDone) {\r
4674           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4675           ics_user_moved = 1;\r
4676         }\r
4677 #endif\r
4678         /* currentMoveString is set as a side-effect of ParseOneMove */\r
4679         strcpy(machineMove, currentMoveString);\r
4680         strcat(machineMove, "\n");\r
4681         strcpy(moveList[forwardMostMove], machineMove);\r
4682 \r
4683         /* [AS] Save move info and clear stats for next move */\r
4684         pvInfoList[ forwardMostMove ].score = programStats.score;\r
4685         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
4686         pvInfoList[ forwardMostMove ].time = -1;\r
4687         ClearProgramStats();\r
4688         thinkOutput[0] = NULLCHAR;\r
4689         hiddenThinkOutputState = 0;\r
4690 \r
4691         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
4692 \r
4693         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
4694         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
4695             int count = 0;\r
4696 \r
4697             while( count < adjudicateLossPlies ) {\r
4698                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
4699 \r
4700                 if( count & 1 ) {\r
4701                     score = -score; /* Flip score for winning side */\r
4702                 }\r
4703 \r
4704                 if( score > adjudicateLossThreshold ) {\r
4705                     break;\r
4706                 }\r
4707 \r
4708                 count++;\r
4709             }\r
4710 \r
4711             if( count >= adjudicateLossPlies ) {\r
4712                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4713 \r
4714                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
4715                     "Xboard adjudication", \r
4716                     GE_XBOARD );\r
4717 \r
4718                 return;\r
4719             }\r
4720         }\r
4721 \r
4722 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
4723 \r
4724         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
4725             int count = 0, epFile = epStatus[forwardMostMove];\r
4726 \r
4727             if(appData.testLegality && appData.checkMates) \r
4728             // don't wait for engine to announce game end if we can judge ourselves\r
4729             switch (MateTest(boards[forwardMostMove],\r
4730                                  PosFlags(forwardMostMove), epFile,\r
4731                                        castlingRights[forwardMostMove]) ) {\r
4732               case MT_NONE:\r
4733               case MT_CHECK:\r
4734               default:\r
4735                 break;\r
4736               case MT_STALEMATE:\r
4737                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4738                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
4739                     GE_XBOARD );\r
4740                 break;\r
4741               case MT_CHECKMATE:\r
4742                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4743                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
4744                     "Xboard adjudication: Checkmate", \r
4745                     GE_XBOARD );\r
4746                 break;\r
4747             }\r
4748 \r
4749             if( appData.testLegality )\r
4750             {   /* [HGM] Some more adjudications for obstinate engines */\r
4751                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
4752                     NrWQ=0, NrBQ=0,\r
4753                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
4754                 static int moveCount;\r
4755 \r
4756                 /* First absolutely insufficient mating material. Count what is on board. */\r
4757                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
4758                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
4759                     int m=i;\r
4760 \r
4761                     switch((int) p)\r
4762                     {   /* count B,N,R and other of each side */\r
4763                         case WhiteKnight:\r
4764                              NrWN++; break;\r
4765                         case WhiteBishop:\r
4766                              NrWB++; break;\r
4767                         case BlackKnight:\r
4768                              NrWN++; break;\r
4769                         case BlackBishop:\r
4770                              NrBB++; break;\r
4771                         case WhiteRook:\r
4772                              NrWR++; break;\r
4773                         case BlackRook:\r
4774                              NrBR++; break;\r
4775                         case WhiteQueen:\r
4776                              NrWR++; break;\r
4777                         case BlackQueen:\r
4778                              NrBR++; break;\r
4779                         case EmptySquare: \r
4780                              break;\r
4781                         case BlackPawn:\r
4782                              m = 7-i;\r
4783                         case WhitePawn:\r
4784                              PawnAdvance += m; NrPawns++;\r
4785                     }\r
4786                     NrPieces += (p != EmptySquare);\r
4787                 }\r
4788 \r
4789                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )\r
4790                 {    /* KBK, KNK or KK */\r
4791 \r
4792                      /* always flag draws, for judging claims */\r
4793                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
4794 \r
4795                      if(appData.materialDraws) {\r
4796                          /* but only adjudicate them if adjudication enabled */\r
4797                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4798                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
4799                          return;\r
4800                      }\r
4801                 }\r
4802 \r
4803                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
4804                 if(NrPieces == 4 && \r
4805                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
4806                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
4807                    || NrWN==2 || NrBN==2     /* KNNK */\r
4808                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
4809                   ) ) {\r
4810                      if(--moveCount < 0 && appData.trivialDraws)\r
4811                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
4812                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4813                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
4814                           return;\r
4815                      }\r
4816                 } else moveCount = 6;\r
4817 \r
4818     if (appData.debugMode) { int i;\r
4819       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
4820               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
4821               appData.drawRepeats);\r
4822       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
4823            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
4824 \r
4825     }\r
4826                 /* Check for rep-draws */\r
4827                 count = 0;\r
4828                 for(k = forwardMostMove-2;\r
4829                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
4830                         epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
4831                     k-=2)\r
4832                 {   int rights=0;\r
4833     if (appData.debugMode) {\r
4834       fprintf(debugFP, " loop\n");\r
4835     }\r
4836                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
4837     if (appData.debugMode) {\r
4838       fprintf(debugFP, "match\n");\r
4839     }\r
4840                         /* compare castling rights */\r
4841                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
4842                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
4843                                 rights++; /* King lost rights, while rook still had them */\r
4844                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
4845                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
4846                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
4847                                    rights++; /* but at least one rook lost them */\r
4848                         }\r
4849                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
4850                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
4851                                 rights++; \r
4852                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
4853                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
4854                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
4855                                    rights++;\r
4856                         }\r
4857     if (appData.debugMode) {\r
4858       for(i=0; i<nrCastlingRights; i++)\r
4859       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
4860     }\r
4861 \r
4862     if (appData.debugMode) {\r
4863       fprintf(debugFP, " %d %d\n", rights, k);\r
4864     }\r
4865                         if( rights == 0 && ++count > appData.drawRepeats-2\r
4866                             && appData.drawRepeats > 1) {\r
4867                              /* adjudicate after user-specified nr of repeats */\r
4868                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4869                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
4870                              return;\r
4871                         }\r
4872                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
4873                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
4874                     }\r
4875                 }\r
4876 \r
4877                 /* Now we test for 50-move draws. Determine ply count */\r
4878                 count = forwardMostMove;\r
4879                 /* look for last irreversble move */\r
4880                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
4881                     count--;\r
4882                 /* if we hit starting position, add initial plies */\r
4883                 if( count == backwardMostMove )\r
4884                     count -= initialRulePlies;\r
4885                 count = forwardMostMove - count; \r
4886                 if( count >= 100)\r
4887                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
4888                          /* this is used to judge if draw claims are legal */\r
4889                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
4890                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4891                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
4892                          return;\r
4893                  }\r
4894             }\r
4895 \r
4896 \r
4897         }\r
4898 #endif\r
4899         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
4900             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4901 \r
4902             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
4903 \r
4904             return;\r
4905         }\r
4906 \r
4907         if (gameMode == TwoMachinesPlay) {\r
4908             if (cps->other->sendTime) {\r
4909                 SendTimeRemaining(cps->other,\r
4910                                   cps->other->twoMachinesColor[0] == 'w');\r
4911             }\r
4912             SendMoveToProgram(forwardMostMove-1, cps->other);\r
4913             if (firstMove) {\r
4914                 firstMove = FALSE;\r
4915                 if (cps->other->useColors) {\r
4916                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
4917                 }\r
4918                 SendToProgram("go\n", cps->other);\r
4919             }\r
4920             cps->other->maybeThinking = TRUE;\r
4921         }\r
4922 \r
4923         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4924         \r
4925         if (!pausing && appData.ringBellAfterMoves) {\r
4926             RingBell();\r
4927         }\r
4928 \r
4929         /* \r
4930          * Reenable menu items that were disabled while\r
4931          * machine was thinking\r
4932          */\r
4933         if (gameMode != TwoMachinesPlay)\r
4934             SetUserThinkingEnables();\r
4935 \r
4936         return;\r
4937     }\r
4938 \r
4939     /* Set special modes for chess engines.  Later something general\r
4940      *  could be added here; for now there is just one kludge feature,\r
4941      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
4942      *  when "xboard" is given as an interactive command.\r
4943      */\r
4944     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
4945         cps->useSigint = FALSE;\r
4946         cps->useSigterm = FALSE;\r
4947     }\r
4948 \r
4949     /*\r
4950      * Look for communication commands\r
4951      */\r
4952     if (!strncmp(message, "telluser ", 9)) {\r
4953         DisplayNote(message + 9);\r
4954         return;\r
4955     }\r
4956     if (!strncmp(message, "tellusererror ", 14)) {\r
4957         DisplayError(message + 14, 0);\r
4958         return;\r
4959     }\r
4960     if (!strncmp(message, "tellopponent ", 13)) {\r
4961       if (appData.icsActive) {\r
4962         if (loggedOn) {\r
4963           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
4964           SendToICS(buf1);\r
4965         }\r
4966       } else {\r
4967         DisplayNote(message + 13);\r
4968       }\r
4969       return;\r
4970     }\r
4971     if (!strncmp(message, "tellothers ", 11)) {\r
4972       if (appData.icsActive) {\r
4973         if (loggedOn) {\r
4974           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
4975           SendToICS(buf1);\r
4976         }\r
4977       }\r
4978       return;\r
4979     }\r
4980     if (!strncmp(message, "tellall ", 8)) {\r
4981       if (appData.icsActive) {\r
4982         if (loggedOn) {\r
4983           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
4984           SendToICS(buf1);\r
4985         }\r
4986       } else {\r
4987         DisplayNote(message + 8);\r
4988       }\r
4989       return;\r
4990     }\r
4991     if (strncmp(message, "warning", 7) == 0) {\r
4992         /* Undocumented feature, use tellusererror in new code */\r
4993         DisplayError(message, 0);\r
4994         return;\r
4995     }\r
4996     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
4997         strcpy(realname, cps->tidy);\r
4998         strcat(realname, " query");\r
4999         AskQuestion(realname, buf2, buf1, cps->pr);\r
5000         return;\r
5001     }\r
5002     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5003      *  sent until we are logged on. Crafty kibitzes have been known to \r
5004      *  interfere with the login process.\r
5005      */\r
5006     if (loggedOn) {\r
5007         if (!strncmp(message, "tellics ", 8)) {\r
5008             SendToICS(message + 8);\r
5009             SendToICS("\n");\r
5010             return;\r
5011         }\r
5012         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5013             SendToICS(ics_prefix);\r
5014             SendToICS(message + 15);\r
5015             SendToICS("\n");\r
5016             return;\r
5017         }\r
5018         /* The following are for backward compatibility only */\r
5019         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5020             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5021             SendToICS(ics_prefix);\r
5022             SendToICS(message);\r
5023             SendToICS("\n");\r
5024             return;\r
5025         }\r
5026     }\r
5027     if (strncmp(message, "feature ", 8) == 0) {\r
5028       ParseFeatures(message+8, cps);\r
5029     }\r
5030     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5031       return;\r
5032     }\r
5033     /*\r
5034      * If the move is illegal, cancel it and redraw the board.\r
5035      * Also deal with other error cases.  Matching is rather loose\r
5036      * here to accommodate engines written before the spec.\r
5037      */\r
5038     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5039         strncmp(message, "Error", 5) == 0) {\r
5040         if (StrStr(message, "name") || \r
5041             StrStr(message, "rating") || StrStr(message, "?") ||\r
5042             StrStr(message, "result") || StrStr(message, "board") ||\r
5043             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5044             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5045             StrStr(message, "random") || StrStr(message, "depth") ||\r
5046             StrStr(message, "accepted")) {\r
5047             return;\r
5048         }\r
5049         if (StrStr(message, "protover")) {\r
5050           /* Program is responding to input, so it's apparently done\r
5051              initializing, and this error message indicates it is\r
5052              protocol version 1.  So we don't need to wait any longer\r
5053              for it to initialize and send feature commands. */\r
5054           FeatureDone(cps, 1);\r
5055           cps->protocolVersion = 1;\r
5056           return;\r
5057         }\r
5058         cps->maybeThinking = FALSE;\r
5059 \r
5060         if (StrStr(message, "draw")) {\r
5061             /* Program doesn't have "draw" command */\r
5062             cps->sendDrawOffers = 0;\r
5063             return;\r
5064         }\r
5065         if (cps->sendTime != 1 &&\r
5066             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5067           /* Program apparently doesn't have "time" or "otim" command */\r
5068           cps->sendTime = 0;\r
5069           return;\r
5070         }\r
5071         if (StrStr(message, "analyze")) {\r
5072             cps->analysisSupport = FALSE;\r
5073             cps->analyzing = FALSE;\r
5074             Reset(FALSE, TRUE);\r
5075             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5076             DisplayError(buf2, 0);\r
5077             return;\r
5078         }\r
5079         if (StrStr(message, "(no matching move)st")) {\r
5080           /* Special kludge for GNU Chess 4 only */\r
5081           cps->stKludge = TRUE;\r
5082           SendTimeControl(cps, movesPerSession, timeControl,\r
5083                           timeIncrement, appData.searchDepth,\r
5084                           searchTime);\r
5085           return;\r
5086         }\r
5087         if (StrStr(message, "(no matching move)sd")) {\r
5088           /* Special kludge for GNU Chess 4 only */\r
5089           cps->sdKludge = TRUE;\r
5090           SendTimeControl(cps, movesPerSession, timeControl,\r
5091                           timeIncrement, appData.searchDepth,\r
5092                           searchTime);\r
5093           return;\r
5094         }\r
5095         if (!StrStr(message, "llegal")) {\r
5096             return;\r
5097         }\r
5098         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5099             gameMode == IcsIdle) return;\r
5100         if (forwardMostMove <= backwardMostMove) return;\r
5101 #if 0\r
5102         /* Following removed: it caused a bug where a real illegal move\r
5103            message in analyze mored would be ignored. */\r
5104         if (cps == &first && programStats.ok_to_send == 0) {\r
5105             /* Bogus message from Crafty responding to "."  This filtering\r
5106                can miss some of the bad messages, but fortunately the bug \r
5107                is fixed in current Crafty versions, so it doesn't matter. */\r
5108             return;\r
5109         }\r
5110 #endif\r
5111         if (pausing) PauseEvent();\r
5112         if (gameMode == PlayFromGameFile) {\r
5113             /* Stop reading this game file */\r
5114             gameMode = EditGame;\r
5115             ModeHighlight();\r
5116         }\r
5117         currentMove = --forwardMostMove;\r
5118         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5119         SwitchClocks();\r
5120         DisplayBothClocks();\r
5121         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5122                 parseList[currentMove], cps->which);\r
5123         DisplayMoveError(buf1);\r
5124         DrawPosition(FALSE, boards[currentMove]);\r
5125 \r
5126         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5127         /* only passes fully legal moves                            */\r
5128         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5129             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5130                                 "False illegal-move claim", GE_XBOARD );\r
5131         }\r
5132         return;\r
5133     }\r
5134     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5135         /* Program has a broken "time" command that\r
5136            outputs a string not ending in newline.\r
5137            Don't use it. */\r
5138         cps->sendTime = 0;\r
5139     }\r
5140     \r
5141     /*\r
5142      * If chess program startup fails, exit with an error message.\r
5143      * Attempts to recover here are futile.\r
5144      */\r
5145     if ((StrStr(message, "unknown host") != NULL)\r
5146         || (StrStr(message, "No remote directory") != NULL)\r
5147         || (StrStr(message, "not found") != NULL)\r
5148         || (StrStr(message, "No such file") != NULL)\r
5149         || (StrStr(message, "can't alloc") != NULL)\r
5150         || (StrStr(message, "Permission denied") != NULL)) {\r
5151 \r
5152         cps->maybeThinking = FALSE;\r
5153         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5154                 cps->which, cps->program, cps->host, message);\r
5155         RemoveInputSource(cps->isr);\r
5156         DisplayFatalError(buf1, 0, 1);\r
5157         return;\r
5158     }\r
5159     \r
5160     /* \r
5161      * Look for hint output\r
5162      */\r
5163     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5164         if (cps == &first && hintRequested) {\r
5165             hintRequested = FALSE;\r
5166             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5167                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5168                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5169                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5170                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5171                 sprintf(buf2, "Hint: %s", buf1);\r
5172                 DisplayInformation(buf2);\r
5173             } else {\r
5174                 /* Hint move could not be parsed!? */\r
5175                 sprintf(buf2,\r
5176                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5177                         buf1, cps->which);\r
5178                 DisplayError(buf2, 0);\r
5179             }\r
5180         } else {\r
5181             strcpy(lastHint, buf1);\r
5182         }\r
5183         return;\r
5184     }\r
5185 \r
5186     /*\r
5187      * Ignore other messages if game is not in progress\r
5188      */\r
5189     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5190         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5191 \r
5192     /*\r
5193      * look for win, lose, draw, or draw offer\r
5194      */\r
5195     if (strncmp(message, "1-0", 3) == 0) {\r
5196         char *p, *q, *r = "";\r
5197         p = strchr(message, '{');\r
5198         if (p) {\r
5199             q = strchr(p, '}');\r
5200             if (q) {\r
5201                 *q = NULLCHAR;\r
5202                 r = p + 1;\r
5203             }\r
5204         }\r
5205         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5206         return;\r
5207     } else if (strncmp(message, "0-1", 3) == 0) {\r
5208         char *p, *q, *r = "";\r
5209         p = strchr(message, '{');\r
5210         if (p) {\r
5211             q = strchr(p, '}');\r
5212             if (q) {\r
5213                 *q = NULLCHAR;\r
5214                 r = p + 1;\r
5215             }\r
5216         }\r
5217         /* Kludge for Arasan 4.1 bug */\r
5218         if (strcmp(r, "Black resigns") == 0) {\r
5219             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5220             return;\r
5221         }\r
5222         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5223         return;\r
5224     } else if (strncmp(message, "1/2", 3) == 0) {\r
5225         char *p, *q, *r = "";\r
5226         p = strchr(message, '{');\r
5227         if (p) {\r
5228             q = strchr(p, '}');\r
5229             if (q) {\r
5230                 *q = NULLCHAR;\r
5231                 r = p + 1;\r
5232             }\r
5233         }\r
5234             \r
5235         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
5236         return;\r
5237 \r
5238     } else if (strncmp(message, "White resign", 12) == 0) {\r
5239         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5240         return;\r
5241     } else if (strncmp(message, "Black resign", 12) == 0) {\r
5242         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5243         return;\r
5244     } else if (strncmp(message, "White", 5) == 0 &&\r
5245                message[5] != '(' &&\r
5246                StrStr(message, "Black") == NULL) {\r
5247         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5248         return;\r
5249     } else if (strncmp(message, "Black", 5) == 0 &&\r
5250                message[5] != '(') {\r
5251         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5252         return;\r
5253     } else if (strcmp(message, "resign") == 0 ||\r
5254                strcmp(message, "computer resigns") == 0) {\r
5255         switch (gameMode) {\r
5256           case MachinePlaysBlack:\r
5257           case IcsPlayingBlack:\r
5258             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5259             break;\r
5260           case MachinePlaysWhite:\r
5261           case IcsPlayingWhite:\r
5262             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5263             break;\r
5264           case TwoMachinesPlay:\r
5265             if (cps->twoMachinesColor[0] == 'w')\r
5266               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5267             else\r
5268               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5269             break;\r
5270           default:\r
5271             /* can't happen */\r
5272             break;\r
5273         }\r
5274         return;\r
5275     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5276         switch (gameMode) {\r
5277           case MachinePlaysBlack:\r
5278           case IcsPlayingBlack:\r
5279             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5280             break;\r
5281           case MachinePlaysWhite:\r
5282           case IcsPlayingWhite:\r
5283             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5284             break;\r
5285           case TwoMachinesPlay:\r
5286             if (cps->twoMachinesColor[0] == 'w')\r
5287               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5288             else\r
5289               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5290             break;\r
5291           default:\r
5292             /* can't happen */\r
5293             break;\r
5294         }\r
5295         return;\r
5296     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5297         switch (gameMode) {\r
5298           case MachinePlaysBlack:\r
5299           case IcsPlayingBlack:\r
5300             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5301             break;\r
5302           case MachinePlaysWhite:\r
5303           case IcsPlayingWhite:\r
5304             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5305             break;\r
5306           case TwoMachinesPlay:\r
5307             if (cps->twoMachinesColor[0] == 'w')\r
5308               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5309             else\r
5310               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5311             break;\r
5312           default:\r
5313             /* can't happen */\r
5314             break;\r
5315         }\r
5316         return;\r
5317     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5318         if (WhiteOnMove(forwardMostMove)) {\r
5319             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5320         } else {\r
5321             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5322         }\r
5323         return;\r
5324     } else if (strstr(message, "Draw") != NULL ||\r
5325                strstr(message, "game is a draw") != NULL) {\r
5326         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5327         return;\r
5328     } else if (strstr(message, "offer") != NULL &&\r
5329                strstr(message, "draw") != NULL) {\r
5330 #if ZIPPY\r
5331         if (appData.zippyPlay && first.initDone) {\r
5332             /* Relay offer to ICS */\r
5333             SendToICS(ics_prefix);\r
5334             SendToICS("draw\n");\r
5335         }\r
5336 #endif\r
5337         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5338         if (gameMode == TwoMachinesPlay) {\r
5339             if (cps->other->offeredDraw) {\r
5340                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5341             } else {\r
5342                 if (cps->other->sendDrawOffers) {\r
5343                     SendToProgram("draw\n", cps->other);\r
5344                 }\r
5345             }\r
5346         } else if (gameMode == MachinePlaysWhite ||\r
5347                    gameMode == MachinePlaysBlack) {\r
5348           if (userOfferedDraw) {\r
5349             DisplayInformation("Machine accepts your draw offer");\r
5350             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5351           } else {\r
5352             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5353           }\r
5354         }\r
5355     }\r
5356 \r
5357     \r
5358     /*\r
5359      * Look for thinking output\r
5360      */\r
5361     if ( appData.showThinking) {\r
5362         int plylev, mvleft, mvtot, curscore, time;\r
5363         char mvname[MOVE_LEN];\r
5364         unsigned long nodes;\r
5365         char plyext;\r
5366         int ignore = FALSE;\r
5367         int prefixHint = FALSE;\r
5368         mvname[0] = NULLCHAR;\r
5369 \r
5370         switch (gameMode) {\r
5371           case MachinePlaysBlack:\r
5372           case IcsPlayingBlack:\r
5373             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5374             break;\r
5375           case MachinePlaysWhite:\r
5376           case IcsPlayingWhite:\r
5377             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5378             break;\r
5379           case AnalyzeMode:\r
5380           case AnalyzeFile:\r
5381             break;\r
5382           case TwoMachinesPlay:\r
5383             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5384                 ignore = TRUE;\r
5385             }\r
5386             break;\r
5387           default:\r
5388             ignore = TRUE;\r
5389             break;\r
5390         }\r
5391 \r
5392         if (!ignore) {\r
5393             buf1[0] = NULLCHAR;\r
5394             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5395                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5396 \r
5397                 if (plyext != ' ' && plyext != '\t') {\r
5398                     time *= 100;\r
5399                 }\r
5400 \r
5401                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5402                 if( cps->scoreIsAbsolute && \r
5403                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5404                 {\r
5405                     curscore = -curscore;\r
5406                 }\r
5407 \r
5408 \r
5409                 programStats.depth = plylev;\r
5410                 programStats.nodes = nodes;\r
5411                 programStats.time = time;\r
5412                 programStats.score = curscore;\r
5413                 programStats.got_only_move = 0;\r
5414 \r
5415                 /* Buffer overflow protection */\r
5416                 if (buf1[0] != NULLCHAR) {\r
5417                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5418                         && appData.debugMode) {\r
5419                         fprintf(debugFP,\r
5420                                 "PV is too long; using the first %d bytes.\n",\r
5421                                 sizeof(programStats.movelist) - 1);\r
5422                     }\r
5423 \r
5424                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5425                 } else {\r
5426                     sprintf(programStats.movelist, " no PV\n");\r
5427                 }\r
5428 \r
5429                 if (programStats.seen_stat) {\r
5430                     programStats.ok_to_send = 1;\r
5431                 }\r
5432 \r
5433                 if (strchr(programStats.movelist, '(') != NULL) {\r
5434                     programStats.line_is_book = 1;\r
5435                     programStats.nr_moves = 0;\r
5436                     programStats.moves_left = 0;\r
5437                 } else {\r
5438                     programStats.line_is_book = 0;\r
5439                 }\r
5440 \r
5441                 SendProgramStatsToFrontend( cps, &programStats );\r
5442 \r
5443                 /* \r
5444                     [AS] Protect the thinkOutput buffer from overflow... this\r
5445                     is only useful if buf1 hasn't overflowed first!\r
5446                 */\r
5447                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5448                         plylev, \r
5449                         (gameMode == TwoMachinesPlay ?\r
5450                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5451                         ((double) curscore) / 100.0,\r
5452                         prefixHint ? lastHint : "",\r
5453                         prefixHint ? " " : "" );\r
5454 \r
5455                 if( buf1[0] != NULLCHAR ) {\r
5456                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5457 \r
5458                     if( strlen(buf1) > max_len ) {\r
5459                         if( appData.debugMode) {\r
5460                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5461                         }\r
5462                         buf1[max_len+1] = '\0';\r
5463                     }\r
5464 \r
5465                     strcat( thinkOutput, buf1 );\r
5466                 }\r
5467 \r
5468                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5469                     DisplayMove(currentMove - 1);\r
5470                     DisplayAnalysis();\r
5471                 }\r
5472                 return;\r
5473 \r
5474             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
5475                 /* crafty (9.25+) says "(only move) <move>"\r
5476                  * if there is only 1 legal move\r
5477                  */\r
5478                 sscanf(p, "(only move) %s", buf1);\r
5479                 sprintf(thinkOutput, "%s (only move)", buf1);\r
5480                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
5481                 programStats.depth = 1;\r
5482                 programStats.nr_moves = 1;\r
5483                 programStats.moves_left = 1;\r
5484                 programStats.nodes = 1;\r
5485                 programStats.time = 1;\r
5486                 programStats.got_only_move = 1;\r
5487 \r
5488                 /* Not really, but we also use this member to\r
5489                    mean "line isn't going to change" (Crafty\r
5490                    isn't searching, so stats won't change) */\r
5491                 programStats.line_is_book = 1;\r
5492 \r
5493                 SendProgramStatsToFrontend( cps, &programStats );\r
5494                 \r
5495                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5496                     DisplayMove(currentMove - 1);\r
5497                     DisplayAnalysis();\r
5498                 }\r
5499                 return;\r
5500             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
5501                               &time, &nodes, &plylev, &mvleft,\r
5502                               &mvtot, mvname) >= 5) {\r
5503                 /* The stat01: line is from Crafty (9.29+) in response\r
5504                    to the "." command */\r
5505                 programStats.seen_stat = 1;\r
5506                 cps->maybeThinking = TRUE;\r
5507 \r
5508                 if (programStats.got_only_move || !appData.periodicUpdates)\r
5509                   return;\r
5510 \r
5511                 programStats.depth = plylev;\r
5512                 programStats.time = time;\r
5513                 programStats.nodes = nodes;\r
5514                 programStats.moves_left = mvleft;\r
5515                 programStats.nr_moves = mvtot;\r
5516                 strcpy(programStats.move_name, mvname);\r
5517                 programStats.ok_to_send = 1;\r
5518                 programStats.movelist[0] = '\0';\r
5519 \r
5520                 SendProgramStatsToFrontend( cps, &programStats );\r
5521 \r
5522                 DisplayAnalysis();\r
5523                 return;\r
5524 \r
5525             } else if (strncmp(message,"++",2) == 0) {\r
5526                 /* Crafty 9.29+ outputs this */\r
5527                 programStats.got_fail = 2;\r
5528                 return;\r
5529 \r
5530             } else if (strncmp(message,"--",2) == 0) {\r
5531                 /* Crafty 9.29+ outputs this */\r
5532                 programStats.got_fail = 1;\r
5533                 return;\r
5534 \r
5535             } else if (thinkOutput[0] != NULLCHAR &&\r
5536                        strncmp(message, "    ", 4) == 0) {\r
5537                 unsigned message_len;\r
5538 \r
5539                 p = message;\r
5540                 while (*p && *p == ' ') p++;\r
5541 \r
5542                 message_len = strlen( p );\r
5543 \r
5544                 /* [AS] Avoid buffer overflow */\r
5545                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
5546                     strcat(thinkOutput, " ");\r
5547                     strcat(thinkOutput, p);\r
5548                 }\r
5549 \r
5550                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
5551                     strcat(programStats.movelist, " ");\r
5552                     strcat(programStats.movelist, p);\r
5553                 }\r
5554 \r
5555                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5556                     DisplayMove(currentMove - 1);\r
5557                     DisplayAnalysis();\r
5558                 }\r
5559                 return;\r
5560             }\r
5561         }\r
5562         else {\r
5563             buf1[0] = NULLCHAR;\r
5564 \r
5565             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5566                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
5567             {\r
5568                 ChessProgramStats cpstats;\r
5569 \r
5570                 if (plyext != ' ' && plyext != '\t') {\r
5571                     time *= 100;\r
5572                 }\r
5573 \r
5574                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5575                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
5576                     curscore = -curscore;\r
5577                 }\r
5578 \r
5579                 cpstats.depth = plylev;\r
5580                 cpstats.nodes = nodes;\r
5581                 cpstats.time = time;\r
5582                 cpstats.score = curscore;\r
5583                 cpstats.got_only_move = 0;\r
5584                 cpstats.movelist[0] = '\0';\r
5585 \r
5586                 if (buf1[0] != NULLCHAR) {\r
5587                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
5588                 }\r
5589 \r
5590                 cpstats.ok_to_send = 0;\r
5591                 cpstats.line_is_book = 0;\r
5592                 cpstats.nr_moves = 0;\r
5593                 cpstats.moves_left = 0;\r
5594 \r
5595                 SendProgramStatsToFrontend( cps, &cpstats );\r
5596             }\r
5597         }\r
5598     }\r
5599 }\r
5600 \r
5601 \r
5602 /* Parse a game score from the character string "game", and\r
5603    record it as the history of the current game.  The game\r
5604    score is NOT assumed to start from the standard position. \r
5605    The display is not updated in any way.\r
5606    */\r
5607 void\r
5608 ParseGameHistory(game)\r
5609      char *game;\r
5610 {\r
5611     ChessMove moveType;\r
5612     int fromX, fromY, toX, toY, boardIndex;\r
5613     char promoChar;\r
5614     char *p, *q;\r
5615     char buf[MSG_SIZ];\r
5616 \r
5617     if (appData.debugMode)\r
5618       fprintf(debugFP, "Parsing game history: %s\n", game);\r
5619 \r
5620     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
5621     gameInfo.site = StrSave(appData.icsHost);\r
5622     gameInfo.date = PGNDate();\r
5623     gameInfo.round = StrSave("-");\r
5624 \r
5625     /* Parse out names of players */\r
5626     while (*game == ' ') game++;\r
5627     p = buf;\r
5628     while (*game != ' ') *p++ = *game++;\r
5629     *p = NULLCHAR;\r
5630     gameInfo.white = StrSave(buf);\r
5631     while (*game == ' ') game++;\r
5632     p = buf;\r
5633     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
5634     *p = NULLCHAR;\r
5635     gameInfo.black = StrSave(buf);\r
5636 \r
5637     /* Parse moves */\r
5638     boardIndex = blackPlaysFirst ? 1 : 0;\r
5639     yynewstr(game);\r
5640     for (;;) {\r
5641         yyboardindex = boardIndex;\r
5642         moveType = (ChessMove) yylex();\r
5643         switch (moveType) {\r
5644 #ifdef FAIRY\r
5645           case WhitePromotionChancellor:\r
5646           case BlackPromotionChancellor:\r
5647           case WhitePromotionArchbishop:\r
5648           case BlackPromotionArchbishop:\r
5649 #endif\r
5650           case WhitePromotionQueen:\r
5651           case BlackPromotionQueen:\r
5652           case WhitePromotionRook:\r
5653           case BlackPromotionRook:\r
5654           case WhitePromotionBishop:\r
5655           case BlackPromotionBishop:\r
5656           case WhitePromotionKnight:\r
5657           case BlackPromotionKnight:\r
5658           case WhitePromotionKing:\r
5659           case BlackPromotionKing:\r
5660           case NormalMove:\r
5661           case WhiteCapturesEnPassant:\r
5662           case BlackCapturesEnPassant:\r
5663           case WhiteKingSideCastle:\r
5664           case WhiteQueenSideCastle:\r
5665           case BlackKingSideCastle:\r
5666           case BlackQueenSideCastle:\r
5667           case WhiteKingSideCastleWild:\r
5668           case WhiteQueenSideCastleWild:\r
5669           case BlackKingSideCastleWild:\r
5670           case BlackQueenSideCastleWild:\r
5671           /* PUSH Fabien */\r
5672           case WhiteHSideCastleFR:\r
5673           case WhiteASideCastleFR:\r
5674           case BlackHSideCastleFR:\r
5675           case BlackASideCastleFR:\r
5676           /* POP Fabien */\r
5677           case IllegalMove:             /* maybe suicide chess, etc. */\r
5678             fromX = currentMoveString[0] - AAA;\r
5679             fromY = currentMoveString[1] - ONE;\r
5680             toX = currentMoveString[2] - AAA;\r
5681             toY = currentMoveString[3] - ONE;\r
5682             promoChar = currentMoveString[4];\r
5683             break;\r
5684           case WhiteDrop:\r
5685           case BlackDrop:\r
5686             fromX = moveType == WhiteDrop ?\r
5687               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
5688             (int) CharToPiece(ToLower(currentMoveString[0]));\r
5689             fromY = DROP_RANK;\r
5690             toX = currentMoveString[2] - AAA;\r
5691             toY = currentMoveString[3] - ONE;\r
5692             promoChar = NULLCHAR;\r
5693             break;\r
5694           case AmbiguousMove:\r
5695             /* bug? */\r
5696             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
5697             DisplayError(buf, 0);\r
5698             return;\r
5699           case ImpossibleMove:\r
5700             /* bug? */\r
5701             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
5702             DisplayError(buf, 0);\r
5703             return;\r
5704           case (ChessMove) 0:   /* end of file */\r
5705             if (boardIndex < backwardMostMove) {\r
5706                 /* Oops, gap.  How did that happen? */\r
5707                 DisplayError("Gap in move list", 0);\r
5708                 return;\r
5709             }\r
5710             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
5711             if (boardIndex > forwardMostMove) {\r
5712                 forwardMostMove = boardIndex;\r
5713             }\r
5714             return;\r
5715           case ElapsedTime:\r
5716             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
5717                 strcat(parseList[boardIndex-1], " ");\r
5718                 strcat(parseList[boardIndex-1], yy_text);\r
5719             }\r
5720             continue;\r
5721           case Comment:\r
5722           case PGNTag:\r
5723           case NAG:\r
5724           default:\r
5725             /* ignore */\r
5726             continue;\r
5727           case WhiteWins:\r
5728           case BlackWins:\r
5729           case GameIsDrawn:\r
5730           case GameUnfinished:\r
5731             if (gameMode == IcsExamining) {\r
5732                 if (boardIndex < backwardMostMove) {\r
5733                     /* Oops, gap.  How did that happen? */\r
5734                     return;\r
5735                 }\r
5736                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5737                 return;\r
5738             }\r
5739             gameInfo.result = moveType;\r
5740             p = strchr(yy_text, '{');\r
5741             if (p == NULL) p = strchr(yy_text, '(');\r
5742             if (p == NULL) {\r
5743                 p = yy_text;\r
5744                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
5745             } else {\r
5746                 q = strchr(p, *p == '{' ? '}' : ')');\r
5747                 if (q != NULL) *q = NULLCHAR;\r
5748                 p++;\r
5749             }\r
5750             gameInfo.resultDetails = StrSave(p);\r
5751             continue;\r
5752         }\r
5753         if (boardIndex >= forwardMostMove &&\r
5754             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
5755             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5756             return;\r
5757         }\r
5758         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
5759                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
5760                                  parseList[boardIndex]);\r
5761         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
5762         /* currentMoveString is set as a side-effect of yylex */\r
5763         strcpy(moveList[boardIndex], currentMoveString);\r
5764         strcat(moveList[boardIndex], "\n");\r
5765         boardIndex++;\r
5766         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
5767         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
5768                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
5769           case MT_NONE:\r
5770           case MT_STALEMATE:\r
5771           default:\r
5772             break;\r
5773           case MT_CHECK:\r
5774             strcat(parseList[boardIndex - 1], "+");\r
5775             break;\r
5776           case MT_CHECKMATE:\r
5777             strcat(parseList[boardIndex - 1], "#");\r
5778             break;\r
5779         }\r
5780     }\r
5781 }\r
5782 \r
5783 \r
5784 /* Apply a move to the given board  */\r
5785 void\r
5786 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
5787      int fromX, fromY, toX, toY;\r
5788      int promoChar;\r
5789      Board board;\r
5790 {\r
5791     ChessSquare captured = board[toY][toX], piece; int p;\r
5792 \r
5793     /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
5794     if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
5795        && promoChar != 0) promoChar = 'F';\r
5796          \r
5797     if (fromY == DROP_RANK) {\r
5798         /* must be first */\r
5799         board[toY][toX] = (ChessSquare) fromX;\r
5800     } else if (fromX == toX && fromY == toY) {\r
5801         return;\r
5802     }\r
5803 \r
5804     piece = board[fromY][fromX];\r
5805     \r
5806     /* Code added by Tord: */\r
5807     /* FRC castling assumed when king captures friendly rook. */\r
5808     else if (board[fromY][fromX] == WhiteKing &&\r
5809              board[toY][toX] == WhiteRook) {\r
5810       board[fromY][fromX] = EmptySquare;\r
5811       board[toY][toX] = EmptySquare;\r
5812       if(toX > fromX) {\r
5813         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
5814       } else {\r
5815         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
5816       }\r
5817     } else if (board[fromY][fromX] == BlackKing &&\r
5818                board[toY][toX] == BlackRook) {\r
5819       board[fromY][fromX] = EmptySquare;\r
5820       board[toY][toX] = EmptySquare;\r
5821       if(toX > fromX) {\r
5822         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
5823       } else {\r
5824         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
5825       }\r
5826     /* End of code added by Tord */\r
5827 \r
5828     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5829         && board[fromY][fromX] == WhiteKing\r
5830         && toY == fromY && toX > fromX+1) {\r
5831         board[fromY][fromX] = EmptySquare;\r
5832         board[toY][toX] = WhiteKing;\r
5833         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
5834         board[toY][toX-1] = WhiteRook;\r
5835     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5836                && board[fromY][fromX] == WhiteKing\r
5837                && toY == fromY && toX < fromX-1) {\r
5838         board[fromY][fromX] = EmptySquare;\r
5839         board[toY][toX] = WhiteKing;\r
5840         board[fromY][BOARD_LEFT] = EmptySquare;\r
5841         board[toY][toX+1] = WhiteRook;\r
5842     } else if (fromY == 0 && fromX == 3\r
5843                && board[fromY][fromX] == WhiteKing\r
5844                && toY == 0 && toX == 5) {\r
5845         board[fromY][fromX] = EmptySquare;\r
5846         board[toY][toX] = WhiteKing;\r
5847         board[fromY][7] = EmptySquare;\r
5848         board[toY][4] = WhiteRook;\r
5849     } else if (fromY == 0 && fromX == 3\r
5850                && board[fromY][fromX] == WhiteKing\r
5851                && toY == 0 && toX == 1) {\r
5852         board[fromY][fromX] = EmptySquare;\r
5853         board[toY][toX] = WhiteKing;\r
5854         board[fromY][0] = EmptySquare;\r
5855         board[toY][2] = WhiteRook;\r
5856     } else if (board[fromY][fromX] == WhitePawn\r
5857                && toY == BOARD_HEIGHT-1\r
5858 #ifdef FAIRY\r
5859                && gameInfo.variant != VariantXiangqi\r
5860 #endif\r
5861                ) {\r
5862         /* white pawn promotion */\r
5863         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
5864         if (board[toY][toX] == EmptySquare) {\r
5865             board[toY][toX] = WhiteQueen;\r
5866         }\r
5867         if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
5868             board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn;\r
5869         board[fromY][fromX] = EmptySquare;\r
5870     } else if ((fromY == BOARD_HEIGHT-4)\r
5871                && (toX != fromX)\r
5872                && (board[fromY][fromX] == WhitePawn)\r
5873                && (board[toY][toX] == EmptySquare)) {\r
5874         board[fromY][fromX] = EmptySquare;\r
5875         board[toY][toX] = WhitePawn;\r
5876         captured = board[toY - 1][toX];\r
5877         board[toY - 1][toX] = EmptySquare;\r
5878     } else if (initialPosition[fromY][fromX] == BlackKing\r
5879                && board[fromY][fromX] == BlackKing\r
5880                && toY == fromY && toX > fromX+1) {\r
5881         board[fromY][fromX] = EmptySquare;\r
5882         board[toY][toX] = BlackKing;\r
5883         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
5884         board[toY][toX-1] = BlackRook;\r
5885     } else if (initialPosition[fromY][fromX] == BlackKing\r
5886                && board[fromY][fromX] == BlackKing\r
5887                && toY == fromY && toX < fromX-1) {\r
5888         board[fromY][fromX] = EmptySquare;\r
5889         board[toY][toX] = BlackKing;\r
5890         board[fromY][BOARD_LEFT] = EmptySquare;\r
5891         board[toY][toX+1] = BlackRook;\r
5892     } else if (fromY == 7 && fromX == 3\r
5893                && board[fromY][fromX] == BlackKing\r
5894                && toY == 7 && toX == 5) {\r
5895         board[fromY][fromX] = EmptySquare;\r
5896         board[toY][toX] = BlackKing;\r
5897         board[fromY][7] = EmptySquare;\r
5898         board[toY][4] = BlackRook;\r
5899     } else if (fromY == 7 && fromX == 3\r
5900                && board[fromY][fromX] == BlackKing\r
5901                && toY == 7 && toX == 1) {\r
5902         board[fromY][fromX] = EmptySquare;\r
5903         board[toY][toX] = BlackKing;\r
5904         board[fromY][0] = EmptySquare;\r
5905         board[toY][2] = BlackRook;\r
5906     } else if (board[fromY][fromX] == BlackPawn\r
5907                && toY == 0\r
5908 #ifdef FAIRY\r
5909                && gameInfo.variant != VariantXiangqi\r
5910 #endif\r
5911                ) {\r
5912         /* black pawn promotion */\r
5913         board[0][toX] = CharToPiece(ToLower(promoChar));\r
5914         if (board[0][toX] == EmptySquare) {\r
5915             board[0][toX] = BlackQueen;\r
5916         }\r
5917         if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
5918             board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn;\r
5919         board[fromY][fromX] = EmptySquare;\r
5920     } else if ((fromY == 3)\r
5921                && (toX != fromX)\r
5922                && (board[fromY][fromX] == BlackPawn)\r
5923                && (board[toY][toX] == EmptySquare)) {\r
5924         board[fromY][fromX] = EmptySquare;\r
5925         board[toY][toX] = BlackPawn;\r
5926         captured = board[toY + 1][toX];\r
5927         board[toY + 1][toX] = EmptySquare;\r
5928     } else {\r
5929         board[toY][toX] = board[fromY][fromX];\r
5930         board[fromY][fromX] = EmptySquare;\r
5931     }\r
5932     if (gameInfo.holdingsWidth != 0) {\r
5933 \r
5934       /* !!A lot more code needs to be written to support holdings  */\r
5935       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
5936       /* penultimate board files, so they are automaticlly stored   */\r
5937       /* in the game history.                                       */\r
5938       if (fromY == DROP_RANK) {\r
5939         /* Delete from holdings, by decreasing count */\r
5940         /* and erasing image if necessary            */\r
5941         p = (int) fromX;\r
5942         if(p < (int) BlackPawn) { /* white drop */\r
5943              p -= (int)WhitePawn;\r
5944              if(p >= gameInfo.holdingsSize) p = 0;\r
5945              if(--board[p][BOARD_WIDTH-2] == 0)\r
5946                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
5947         } else {                  /* black drop */\r
5948              p -= (int)BlackPawn;\r
5949              if(p >= gameInfo.holdingsSize) p = 0;\r
5950              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
5951                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
5952         }\r
5953       }\r
5954       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
5955           && gameInfo.variant != VariantBughouse        ) {\r
5956         /* Add to holdings, if holdings exist */\r
5957         p = (int) captured;\r
5958         if (p >= (int) BlackPawn) {\r
5959           p -= (int)BlackPawn;\r
5960           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
5961                   /* in Shogi restore piece to its original  first */\r
5962                   captured = (ChessSquare) (DEMOTED captured);\r
5963                   p = DEMOTED p;\r
5964           }\r
5965           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
5966           board[p][BOARD_WIDTH-2]++;\r
5967           board[p][BOARD_WIDTH-1] =\r
5968                                    BLACK_TO_WHITE captured;\r
5969         } else {\r
5970           p -= (int)WhitePawn;\r
5971           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
5972                   captured = (ChessSquare) (DEMOTED captured);\r
5973                   p = DEMOTED p;\r
5974           }\r
5975           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
5976           board[BOARD_HEIGHT-1-p][1]++;\r
5977           board[BOARD_HEIGHT-1-p][0] =\r
5978                                   WHITE_TO_BLACK captured;\r
5979         }\r
5980       }\r
5981 \r
5982     } else if (gameInfo.variant == VariantAtomic) {\r
5983       if (captured != EmptySquare) {\r
5984         int y, x;\r
5985         for (y = toY-1; y <= toY+1; y++) {\r
5986           for (x = toX-1; x <= toX+1; x++) {\r
5987             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
5988                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
5989               board[y][x] = EmptySquare;\r
5990             }\r
5991           }\r
5992         }\r
5993         board[toY][toX] = EmptySquare;\r
5994       }\r
5995     }\r
5996     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR) {\r
5997         /* [HGM] Shogi promotions */\r
5998         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
5999     }\r
6000 \r
6001 }\r
6002 \r
6003 /* Updates forwardMostMove */\r
6004 void\r
6005 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6006      int fromX, fromY, toX, toY;\r
6007      int promoChar;\r
6008 {\r
6009     forwardMostMove++;\r
6010 \r
6011     if (forwardMostMove >= MAX_MOVES) {\r
6012       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6013                         0, 1);\r
6014       return;\r
6015     }\r
6016     SwitchClocks();\r
6017     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6018     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6019     if (commentList[forwardMostMove] != NULL) {\r
6020         free(commentList[forwardMostMove]);\r
6021         commentList[forwardMostMove] = NULL;\r
6022     }\r
6023     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6024     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6025     { int i, j;\r
6026 \r
6027       epStatus[forwardMostMove] = EP_NONE;\r
6028 \r
6029       if( boards[forwardMostMove][toY][toX] != EmptySquare ) \r
6030            epStatus[forwardMostMove] = EP_CAPTURE;  \r
6031 \r
6032       if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {\r
6033            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6034            if( toY-fromY==2 &&\r
6035                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||\r
6036                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )\r
6037               epStatus[forwardMostMove] = toX;\r
6038       } else \r
6039       if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {\r
6040            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6041            if( toY-fromY== -2 &&\r
6042                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||\r
6043                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )\r
6044               epStatus[forwardMostMove] = toX;\r
6045        }\r
6046 \r
6047        for(i=0; i<nrCastlingRights; i++) {\r
6048            castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];\r
6049            if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||\r
6050               castlingRights[forwardMostMove][i] == toX   && castlingRank[i] == toY   \r
6051              ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece\r
6052 \r
6053        }\r
6054 \r
6055     }\r
6056     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6057     gameInfo.result = GameUnfinished;\r
6058     if (gameInfo.resultDetails != NULL) {\r
6059         free(gameInfo.resultDetails);\r
6060         gameInfo.resultDetails = NULL;\r
6061     }\r
6062     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6063                               moveList[forwardMostMove - 1]);\r
6064     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6065                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6066                              fromY, fromX, toY, toX, promoChar,\r
6067                              parseList[forwardMostMove - 1]);\r
6068     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6069                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6070                             castlingRights[forwardMostMove]) ) {\r
6071       case MT_NONE:\r
6072       case MT_STALEMATE:\r
6073       default:\r
6074         break;\r
6075       case MT_CHECK:\r
6076         strcat(parseList[forwardMostMove - 1], "+");\r
6077         break;\r
6078       case MT_CHECKMATE:\r
6079         strcat(parseList[forwardMostMove - 1], "#");\r
6080         break;\r
6081     }\r
6082 }\r
6083 \r
6084 /* Updates currentMove if not pausing */\r
6085 void\r
6086 ShowMove(fromX, fromY, toX, toY)\r
6087 {\r
6088     int instant = (gameMode == PlayFromGameFile) ?\r
6089         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6090     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6091         if (!instant) {\r
6092             if (forwardMostMove == currentMove + 1) {\r
6093                 AnimateMove(boards[forwardMostMove - 1],\r
6094                             fromX, fromY, toX, toY);\r
6095             }\r
6096             if (appData.highlightLastMove) {\r
6097                 SetHighlights(fromX, fromY, toX, toY);\r
6098             }\r
6099         }\r
6100         currentMove = forwardMostMove;\r
6101     }\r
6102 \r
6103     if (instant) return;\r
6104 \r
6105     DisplayMove(currentMove - 1);\r
6106     DrawPosition(FALSE, boards[currentMove]);\r
6107     DisplayBothClocks();\r
6108     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6109 }\r
6110 \r
6111 \r
6112 void\r
6113 InitChessProgram(cps)\r
6114      ChessProgramState *cps;\r
6115 {\r
6116     char buf[MSG_SIZ], *b; int overruled;\r
6117     if (appData.noChessProgram) return;\r
6118     hintRequested = FALSE;\r
6119     bookRequested = FALSE;\r
6120     SendToProgram(cps->initString, cps);\r
6121     if (gameInfo.variant != VariantNormal &&\r
6122         gameInfo.variant != VariantLoadable\r
6123         /* [HGM] also send variant if board size non-standard */\r
6124         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8\r
6125                                             ) {\r
6126       char *v = VariantName(gameInfo.variant);\r
6127       if (StrStr(cps->variants, v) == NULL) {\r
6128         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
6129         DisplayFatalError(buf, 0, 1);\r
6130         return;\r
6131       }\r
6132       b = buf;\r
6133       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
6134       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6135       if( gameInfo.variant == VariantXiangqi )\r
6136            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
6137       if( gameInfo.variant == VariantShogi )\r
6138            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
6139       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
6140            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
6141       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )\r
6142            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6143       if( gameInfo.variant == VariantCourier )\r
6144            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6145 \r
6146       if(overruled) {\r
6147 #if 0\r
6148            // doesn't work in protocol 1\r
6149            if (StrStr(cps->variants, "boardsize") == NULL,) {\r
6150              sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
6151                   gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
6152              DisplayFatalError(buf, 0, 1);\r
6153              return;\r
6154            }\r
6155 #endif\r
6156            sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,\r
6157                               gameInfo.boardHeight, gameInfo.holdingsSize );\r
6158            while(*b++ != '_');\r
6159       }\r
6160       sprintf(b, "variant %s\n", VariantName(gameInfo.variant));\r
6161       SendToProgram(buf, cps);\r
6162     }\r
6163     if (cps->sendICS) {\r
6164       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
6165       SendToProgram(buf, cps);\r
6166     }\r
6167     cps->maybeThinking = FALSE;\r
6168     cps->offeredDraw = 0;\r
6169     if (!appData.icsActive) {\r
6170         SendTimeControl(cps, movesPerSession, timeControl,\r
6171                         timeIncrement, appData.searchDepth,\r
6172                         searchTime);\r
6173     }\r
6174     if (appData.showThinking) {\r
6175         SendToProgram("post\n", cps);\r
6176     }\r
6177     SendToProgram("hard\n", cps);\r
6178     if (!appData.ponderNextMove) {\r
6179         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
6180            it without being sure what state we are in first.  "hard"\r
6181            is not a toggle, so that one is OK.\r
6182          */\r
6183         SendToProgram("easy\n", cps);\r
6184     }\r
6185     if (cps->usePing) {\r
6186       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
6187       SendToProgram(buf, cps);\r
6188     }\r
6189     cps->initDone = TRUE;\r
6190 }   \r
6191 \r
6192 \r
6193 void\r
6194 StartChessProgram(cps)\r
6195      ChessProgramState *cps;\r
6196 {\r
6197     char buf[MSG_SIZ];\r
6198     int err;\r
6199 \r
6200     if (appData.noChessProgram) return;\r
6201     cps->initDone = FALSE;\r
6202 \r
6203     if (strcmp(cps->host, "localhost") == 0) {\r
6204         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
6205     } else if (*appData.remoteShell == NULLCHAR) {\r
6206         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
6207     } else {\r
6208         if (*appData.remoteUser == NULLCHAR) {\r
6209             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
6210                     cps->program);\r
6211         } else {\r
6212             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
6213                     cps->host, appData.remoteUser, cps->program);\r
6214         }\r
6215         err = StartChildProcess(buf, "", &cps->pr);\r
6216     }\r
6217     \r
6218     if (err != 0) {\r
6219         sprintf(buf, "Startup failure on '%s'", cps->program);\r
6220         DisplayFatalError(buf, err, 1);\r
6221         cps->pr = NoProc;\r
6222         cps->isr = NULL;\r
6223         return;\r
6224     }\r
6225     \r
6226     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
6227     if (cps->protocolVersion > 1) {\r
6228       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
6229       SendToProgram(buf, cps);\r
6230     } else {\r
6231       SendToProgram("xboard\n", cps);\r
6232     }\r
6233 }\r
6234 \r
6235 \r
6236 void\r
6237 TwoMachinesEventIfReady P((void))\r
6238 {\r
6239   if (first.lastPing != first.lastPong) {\r
6240     DisplayMessage("", "Waiting for first chess program");\r
6241     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6242     return;\r
6243   }\r
6244   if (second.lastPing != second.lastPong) {\r
6245     DisplayMessage("", "Waiting for second chess program");\r
6246     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6247     return;\r
6248   }\r
6249   ThawUI();\r
6250   TwoMachinesEvent();\r
6251 }\r
6252 \r
6253 void\r
6254 NextMatchGame P((void))\r
6255 {\r
6256     Reset(FALSE, TRUE);\r
6257     if (*appData.loadGameFile != NULLCHAR) {\r
6258         LoadGameFromFile(appData.loadGameFile,\r
6259                          appData.loadGameIndex,\r
6260                          appData.loadGameFile, FALSE);\r
6261     } else if (*appData.loadPositionFile != NULLCHAR) {\r
6262         LoadPositionFromFile(appData.loadPositionFile,\r
6263                              appData.loadPositionIndex,\r
6264                              appData.loadPositionFile);\r
6265     }\r
6266     TwoMachinesEventIfReady();\r
6267 }\r
6268 \r
6269 void UserAdjudicationEvent( int result )\r
6270 {\r
6271     ChessMove gameResult = GameIsDrawn;\r
6272 \r
6273     if( result > 0 ) {\r
6274         gameResult = WhiteWins;\r
6275     }\r
6276     else if( result < 0 ) {\r
6277         gameResult = BlackWins;\r
6278     }\r
6279 \r
6280     if( gameMode == TwoMachinesPlay ) {\r
6281         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
6282     }\r
6283 }\r
6284 \r
6285 \r
6286 void\r
6287 GameEnds(result, resultDetails, whosays)\r
6288      ChessMove result;\r
6289      char *resultDetails;\r
6290      int whosays;\r
6291 {\r
6292     GameMode nextGameMode;\r
6293     int isIcsGame;\r
6294     char buf[MSG_SIZ];\r
6295 \r
6296     if (appData.debugMode) {\r
6297       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
6298               result, resultDetails ? resultDetails : "(null)", whosays);\r
6299     }\r
6300 \r
6301     if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {\r
6302         /* If we are playing on ICS, the server decides when the\r
6303            game is over, but the engine can offer to draw, claim \r
6304            a draw, or resign. \r
6305          */\r
6306 #if ZIPPY\r
6307         if (appData.zippyPlay && first.initDone) {\r
6308             if (result == GameIsDrawn) {\r
6309                 /* In case draw still needs to be claimed */\r
6310                 SendToICS(ics_prefix);\r
6311                 SendToICS("draw\n");\r
6312             } else if (StrCaseStr(resultDetails, "resign")) {\r
6313                 SendToICS(ics_prefix);\r
6314                 SendToICS("resign\n");\r
6315             }\r
6316         }\r
6317 #endif\r
6318         return;\r
6319     }\r
6320 \r
6321     /* If we're loading the game from a file, stop */\r
6322     if (whosays == GE_FILE) {\r
6323       (void) StopLoadGameTimer();\r
6324       gameFileFP = NULL;\r
6325     }\r
6326 \r
6327     /* Cancel draw offers */\r
6328    first.offeredDraw = second.offeredDraw = 0;\r
6329 \r
6330     /* If this is an ICS game, only ICS can really say it's done;\r
6331        if not, anyone can. */\r
6332     isIcsGame = (gameMode == IcsPlayingWhite || \r
6333                  gameMode == IcsPlayingBlack || \r
6334                  gameMode == IcsObserving    || \r
6335                  gameMode == IcsExamining);\r
6336 \r
6337     if (!isIcsGame || whosays == GE_ICS) {\r
6338         /* OK -- not an ICS game, or ICS said it was done */\r
6339         StopClocks();\r
6340     if (appData.debugMode) {\r
6341       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6342               result, resultDetails ? resultDetails : "(null)", whosays);\r
6343     }\r
6344         if (!isIcsGame && !appData.noChessProgram) \r
6345           SetUserThinkingEnables();\r
6346     \r
6347         /* [HGM] if a machine claims the game end we verify this claim */\r
6348         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6349             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6350                 char claimer;\r
6351 \r
6352     if (appData.debugMode) {\r
6353       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6354               result, resultDetails ? resultDetails : "(null)", whosays);\r
6355     }\r
6356                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6357                                             first.twoMachinesColor[0] :\r
6358                                             second.twoMachinesColor[0] ;\r
6359                 if( result == WhiteWins && claimer == 'w' ||\r
6360                     result == BlackWins && claimer == 'b' ) {\r
6361                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6362                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6363                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6364                       resultDetails = buf;\r
6365                 } else\r
6366                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {\r
6367                       /* Draw that was not flagged by Xboard is false */\r
6368                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6369                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6370                       resultDetails = buf;\r
6371                 }\r
6372                 /* (Claiming a loss is accepted no questions asked!) */\r
6373         }\r
6374 \r
6375     if (appData.debugMode) {\r
6376       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6377               result, resultDetails ? resultDetails : "(null)", whosays);\r
6378     }\r
6379         if (resultDetails != NULL) {\r
6380             gameInfo.result = result;\r
6381             gameInfo.resultDetails = StrSave(resultDetails);\r
6382 \r
6383             /* Tell program how game ended in case it is learning */\r
6384             if (gameMode == MachinePlaysWhite ||\r
6385                 gameMode == MachinePlaysBlack ||\r
6386                 gameMode == TwoMachinesPlay ||\r
6387                 gameMode == IcsPlayingWhite ||\r
6388                 gameMode == IcsPlayingBlack ||\r
6389                 gameMode == BeginningOfGame) {\r
6390                 char buf[MSG_SIZ];\r
6391                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
6392                         resultDetails);\r
6393                 if (first.pr != NoProc) {\r
6394                     SendToProgram(buf, &first);\r
6395                 }\r
6396                 if (second.pr != NoProc &&\r
6397                     gameMode == TwoMachinesPlay) {\r
6398                     SendToProgram(buf, &second);\r
6399                 }\r
6400             }\r
6401 \r
6402             /* display last move only if game was not loaded from file */\r
6403             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
6404                 DisplayMove(currentMove - 1);\r
6405     \r
6406             if (forwardMostMove != 0) {\r
6407                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
6408                     if (*appData.saveGameFile != NULLCHAR) {\r
6409                         SaveGameToFile(appData.saveGameFile, TRUE);\r
6410                     } else if (appData.autoSaveGames) {\r
6411                         AutoSaveGame();\r
6412                     }\r
6413                     if (*appData.savePositionFile != NULLCHAR) {\r
6414                         SavePositionToFile(appData.savePositionFile);\r
6415                     }\r
6416                 }\r
6417             }\r
6418         }\r
6419 \r
6420         if (appData.icsActive) {\r
6421             if (appData.quietPlay &&\r
6422                 (gameMode == IcsPlayingWhite ||\r
6423                  gameMode == IcsPlayingBlack)) {\r
6424                 SendToICS(ics_prefix);\r
6425                 SendToICS("set shout 1\n");\r
6426             }\r
6427             nextGameMode = IcsIdle;\r
6428             ics_user_moved = FALSE;\r
6429             /* clean up premove.  It's ugly when the game has ended and the\r
6430              * premove highlights are still on the board.\r
6431              */\r
6432             if (gotPremove) {\r
6433               gotPremove = FALSE;\r
6434               ClearPremoveHighlights();\r
6435               DrawPosition(FALSE, boards[currentMove]);\r
6436             }\r
6437             if (whosays == GE_ICS) {\r
6438                 switch (result) {\r
6439                 case WhiteWins:\r
6440                     if (gameMode == IcsPlayingWhite)\r
6441                         PlayIcsWinSound();\r
6442                     else if(gameMode == IcsPlayingBlack)\r
6443                         PlayIcsLossSound();\r
6444                     break;\r
6445                 case BlackWins:\r
6446                     if (gameMode == IcsPlayingBlack)\r
6447                         PlayIcsWinSound();\r
6448                     else if(gameMode == IcsPlayingWhite)\r
6449                         PlayIcsLossSound();\r
6450                     break;\r
6451                 case GameIsDrawn:\r
6452                     PlayIcsDrawSound();\r
6453                     break;\r
6454                 default:\r
6455                     PlayIcsUnfinishedSound();\r
6456                 }\r
6457             }\r
6458         } else if (gameMode == EditGame ||\r
6459                    gameMode == PlayFromGameFile || \r
6460                    gameMode == AnalyzeMode || \r
6461                    gameMode == AnalyzeFile) {\r
6462             nextGameMode = gameMode;\r
6463         } else {\r
6464             nextGameMode = EndOfGame;\r
6465         }\r
6466         pausing = FALSE;\r
6467         ModeHighlight();\r
6468     } else {\r
6469         nextGameMode = gameMode;\r
6470     }\r
6471 \r
6472     if (appData.noChessProgram) {\r
6473         gameMode = nextGameMode;\r
6474         ModeHighlight();\r
6475         return;\r
6476     }\r
6477 \r
6478     if (first.reuse) {\r
6479         /* Put first chess program into idle state */\r
6480         if (first.pr != NoProc &&\r
6481             (gameMode == MachinePlaysWhite ||\r
6482              gameMode == MachinePlaysBlack ||\r
6483              gameMode == TwoMachinesPlay ||\r
6484              gameMode == IcsPlayingWhite ||\r
6485              gameMode == IcsPlayingBlack ||\r
6486              gameMode == BeginningOfGame)) {\r
6487             SendToProgram("force\n", &first);\r
6488             if (first.usePing) {\r
6489               char buf[MSG_SIZ];\r
6490               sprintf(buf, "ping %d\n", ++first.lastPing);\r
6491               SendToProgram(buf, &first);\r
6492             }\r
6493         }\r
6494     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6495         /* Kill off first chess program */\r
6496         if (first.isr != NULL)\r
6497           RemoveInputSource(first.isr);\r
6498         first.isr = NULL;\r
6499     \r
6500         if (first.pr != NoProc) {\r
6501             ExitAnalyzeMode();\r
6502             DoSleep( appData.delayBeforeQuit );\r
6503             SendToProgram("quit\n", &first);\r
6504             DoSleep( appData.delayAfterQuit );\r
6505             DestroyChildProcess(first.pr, first.useSigterm);\r
6506         }\r
6507         first.pr = NoProc;\r
6508     }\r
6509     if (second.reuse) {\r
6510         /* Put second chess program into idle state */\r
6511         if (second.pr != NoProc &&\r
6512             gameMode == TwoMachinesPlay) {\r
6513             SendToProgram("force\n", &second);\r
6514             if (second.usePing) {\r
6515               char buf[MSG_SIZ];\r
6516               sprintf(buf, "ping %d\n", ++second.lastPing);\r
6517               SendToProgram(buf, &second);\r
6518             }\r
6519         }\r
6520     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6521         /* Kill off second chess program */\r
6522         if (second.isr != NULL)\r
6523           RemoveInputSource(second.isr);\r
6524         second.isr = NULL;\r
6525     \r
6526         if (second.pr != NoProc) {\r
6527             DoSleep( appData.delayBeforeQuit );\r
6528             SendToProgram("quit\n", &second);\r
6529             DoSleep( appData.delayAfterQuit );\r
6530             DestroyChildProcess(second.pr, second.useSigterm);\r
6531         }\r
6532         second.pr = NoProc;\r
6533     }\r
6534 \r
6535     if (matchMode && gameMode == TwoMachinesPlay) {\r
6536         switch (result) {\r
6537         case WhiteWins:\r
6538           if (first.twoMachinesColor[0] == 'w') {\r
6539             first.matchWins++;\r
6540           } else {\r
6541             second.matchWins++;\r
6542           }\r
6543           break;\r
6544         case BlackWins:\r
6545           if (first.twoMachinesColor[0] == 'b') {\r
6546             first.matchWins++;\r
6547           } else {\r
6548             second.matchWins++;\r
6549           }\r
6550           break;\r
6551         default:\r
6552           break;\r
6553         }\r
6554         if (matchGame < appData.matchGames) {\r
6555             char *tmp;\r
6556             tmp = first.twoMachinesColor;\r
6557             first.twoMachinesColor = second.twoMachinesColor;\r
6558             second.twoMachinesColor = tmp;\r
6559             gameMode = nextGameMode;\r
6560             matchGame++;\r
6561             if(appData.matchPause>10000 || appData.matchPause<10)\r
6562                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
6563             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
6564             return;\r
6565         } else {\r
6566             char buf[MSG_SIZ];\r
6567             gameMode = nextGameMode;\r
6568             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
6569                     first.tidy, second.tidy,\r
6570                     first.matchWins, second.matchWins,\r
6571                     appData.matchGames - (first.matchWins + second.matchWins));\r
6572             DisplayFatalError(buf, 0, 0);\r
6573         }\r
6574     }\r
6575     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6576         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
6577       ExitAnalyzeMode();\r
6578     gameMode = nextGameMode;\r
6579     ModeHighlight();\r
6580 }\r
6581 \r
6582 /* Assumes program was just initialized (initString sent).\r
6583    Leaves program in force mode. */\r
6584 void\r
6585 FeedMovesToProgram(cps, upto) \r
6586      ChessProgramState *cps;\r
6587      int upto;\r
6588 {\r
6589     int i;\r
6590     \r
6591     if (appData.debugMode)\r
6592       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
6593               startedFromSetupPosition ? "position and " : "",\r
6594               backwardMostMove, upto, cps->which);\r
6595     SendToProgram("force\n", cps);\r
6596     if (startedFromSetupPosition) {\r
6597         SendBoard(cps, backwardMostMove);\r
6598     }\r
6599     for (i = backwardMostMove; i < upto; i++) {\r
6600         SendMoveToProgram(i, cps);\r
6601     }\r
6602 }\r
6603 \r
6604 \r
6605 void\r
6606 ResurrectChessProgram()\r
6607 {\r
6608      /* The chess program may have exited.\r
6609         If so, restart it and feed it all the moves made so far. */\r
6610 \r
6611     if (appData.noChessProgram || first.pr != NoProc) return;\r
6612     \r
6613     StartChessProgram(&first);\r
6614     InitChessProgram(&first);\r
6615     FeedMovesToProgram(&first, currentMove);\r
6616 \r
6617     if (!first.sendTime) {\r
6618         /* can't tell gnuchess what its clock should read,\r
6619            so we bow to its notion. */\r
6620         ResetClocks();\r
6621         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
6622         timeRemaining[1][currentMove] = blackTimeRemaining;\r
6623     }\r
6624 \r
6625     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6626         first.analysisSupport) {\r
6627       SendToProgram("analyze\n", &first);\r
6628       first.analyzing = TRUE;\r
6629     }\r
6630 }\r
6631 \r
6632 /*\r
6633  * Button procedures\r
6634  */\r
6635 void\r
6636 Reset(redraw, init)\r
6637      int redraw, init;\r
6638 {\r
6639     int i;\r
6640 \r
6641     if (appData.debugMode) {\r
6642         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
6643                 redraw, init, gameMode);\r
6644     }\r
6645 \r
6646     pausing = pauseExamInvalid = FALSE;\r
6647     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
6648     firstMove = TRUE;\r
6649     whiteFlag = blackFlag = FALSE;\r
6650     userOfferedDraw = FALSE;\r
6651     hintRequested = bookRequested = FALSE;\r
6652     first.maybeThinking = FALSE;\r
6653     second.maybeThinking = FALSE;\r
6654     thinkOutput[0] = NULLCHAR;\r
6655     lastHint[0] = NULLCHAR;\r
6656     ClearGameInfo(&gameInfo);\r
6657     gameInfo.variant = StringToVariant(appData.variant);\r
6658     ics_user_moved = ics_clock_paused = FALSE;\r
6659     ics_getting_history = H_FALSE;\r
6660     ics_gamenum = -1;\r
6661     white_holding[0] = black_holding[0] = NULLCHAR;\r
6662     ClearProgramStats();\r
6663     \r
6664     ResetFrontEnd();\r
6665     ClearHighlights();\r
6666     flipView = appData.flipView;\r
6667     ClearPremoveHighlights();\r
6668     gotPremove = FALSE;\r
6669     alarmSounded = FALSE;\r
6670 \r
6671     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
6672     ExitAnalyzeMode();\r
6673     gameMode = BeginningOfGame;\r
6674     ModeHighlight();\r
6675     InitPosition(redraw);\r
6676     for (i = 0; i < MAX_MOVES; i++) {\r
6677         if (commentList[i] != NULL) {\r
6678             free(commentList[i]);\r
6679             commentList[i] = NULL;\r
6680         }\r
6681     }\r
6682     ResetClocks();\r
6683     timeRemaining[0][0] = whiteTimeRemaining;\r
6684     timeRemaining[1][0] = blackTimeRemaining;\r
6685     if (first.pr == NULL) {\r
6686         StartChessProgram(&first);\r
6687     }\r
6688     if (init) InitChessProgram(&first);\r
6689     DisplayTitle("");\r
6690     DisplayMessage("", "");\r
6691     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
6692 }\r
6693 \r
6694 void\r
6695 AutoPlayGameLoop()\r
6696 {\r
6697     for (;;) {\r
6698         if (!AutoPlayOneMove())\r
6699           return;\r
6700         if (matchMode || appData.timeDelay == 0)\r
6701           continue;\r
6702         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
6703           return;\r
6704         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
6705         break;\r
6706     }\r
6707 }\r
6708 \r
6709 \r
6710 int\r
6711 AutoPlayOneMove()\r
6712 {\r
6713     int fromX, fromY, toX, toY;\r
6714 \r
6715     if (appData.debugMode) {\r
6716       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
6717     }\r
6718 \r
6719     if (gameMode != PlayFromGameFile)\r
6720       return FALSE;\r
6721 \r
6722     if (currentMove >= forwardMostMove) {\r
6723       gameMode = EditGame;\r
6724       ModeHighlight();\r
6725 \r
6726       /* [AS] Clear current move marker at the end of a game */\r
6727       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
6728 \r
6729       return FALSE;\r
6730     }\r
6731     \r
6732     toX = moveList[currentMove][2] - AAA;\r
6733     toY = moveList[currentMove][3] - ONE;\r
6734 \r
6735     if (moveList[currentMove][1] == '@') {\r
6736         if (appData.highlightLastMove) {\r
6737             SetHighlights(-1, -1, toX, toY);\r
6738         }\r
6739     } else {\r
6740         fromX = moveList[currentMove][0] - AAA;\r
6741         fromY = moveList[currentMove][1] - ONE;\r
6742 \r
6743         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
6744 \r
6745         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
6746 \r
6747         if (appData.highlightLastMove) {\r
6748             SetHighlights(fromX, fromY, toX, toY);\r
6749         }\r
6750     }\r
6751     DisplayMove(currentMove);\r
6752     SendMoveToProgram(currentMove++, &first);\r
6753     DisplayBothClocks();\r
6754     DrawPosition(FALSE, boards[currentMove]);\r
6755     if (commentList[currentMove] != NULL) {\r
6756         DisplayComment(currentMove - 1, commentList[currentMove]);\r
6757     }\r
6758     return TRUE;\r
6759 }\r
6760 \r
6761 \r
6762 int\r
6763 LoadGameOneMove(readAhead)\r
6764      ChessMove readAhead;\r
6765 {\r
6766     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
6767     char promoChar = NULLCHAR;\r
6768     ChessMove moveType;\r
6769     char move[MSG_SIZ];\r
6770     char *p, *q;\r
6771     \r
6772     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
6773         gameMode != AnalyzeMode && gameMode != Training) {\r
6774         gameFileFP = NULL;\r
6775         return FALSE;\r
6776     }\r
6777     \r
6778     yyboardindex = forwardMostMove;\r
6779     if (readAhead != (ChessMove)0) {\r
6780       moveType = readAhead;\r
6781     } else {\r
6782       if (gameFileFP == NULL)\r
6783           return FALSE;\r
6784       moveType = (ChessMove) yylex();\r
6785     }\r
6786     \r
6787     done = FALSE;\r
6788     switch (moveType) {\r
6789       case Comment:\r
6790         if (appData.debugMode) \r
6791           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
6792         p = yy_text;\r
6793         if (*p == '{' || *p == '[' || *p == '(') {\r
6794             p[strlen(p) - 1] = NULLCHAR;\r
6795             p++;\r
6796         }\r
6797 \r
6798         /* append the comment but don't display it */\r
6799         while (*p == '\n') p++;\r
6800         AppendComment(currentMove, p);\r
6801         return TRUE;\r
6802 \r
6803       case WhiteCapturesEnPassant:\r
6804       case BlackCapturesEnPassant:\r
6805 #ifdef FAIRY\r
6806       case WhitePromotionChancellor:\r
6807       case BlackPromotionChancellor:\r
6808       case WhitePromotionArchbishop:\r
6809       case BlackPromotionArchbishop:\r
6810 #endif\r
6811       case WhitePromotionQueen:\r
6812       case BlackPromotionQueen:\r
6813       case WhitePromotionRook:\r
6814       case BlackPromotionRook:\r
6815       case WhitePromotionBishop:\r
6816       case BlackPromotionBishop:\r
6817       case WhitePromotionKnight:\r
6818       case BlackPromotionKnight:\r
6819       case WhitePromotionKing:\r
6820       case BlackPromotionKing:\r
6821       case NormalMove:\r
6822       case WhiteKingSideCastle:\r
6823       case WhiteQueenSideCastle:\r
6824       case BlackKingSideCastle:\r
6825       case BlackQueenSideCastle:\r
6826       case WhiteKingSideCastleWild:\r
6827       case WhiteQueenSideCastleWild:\r
6828       case BlackKingSideCastleWild:\r
6829       case BlackQueenSideCastleWild:\r
6830       /* PUSH Fabien */\r
6831       case WhiteHSideCastleFR:\r
6832       case WhiteASideCastleFR:\r
6833       case BlackHSideCastleFR:\r
6834       case BlackASideCastleFR:\r
6835       /* POP Fabien */\r
6836         if (appData.debugMode)\r
6837           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
6838         fromX = currentMoveString[0] - AAA;\r
6839         fromY = currentMoveString[1] - ONE;\r
6840         toX = currentMoveString[2] - AAA;\r
6841         toY = currentMoveString[3] - ONE;\r
6842         promoChar = currentMoveString[4];\r
6843         break;\r
6844 \r
6845       case WhiteDrop:\r
6846       case BlackDrop:\r
6847         if (appData.debugMode)\r
6848           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
6849         fromX = moveType == WhiteDrop ?\r
6850           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6851         (int) CharToPiece(ToLower(currentMoveString[0]));\r
6852         fromY = DROP_RANK;\r
6853         toX = currentMoveString[2] - AAA;\r
6854         toY = currentMoveString[3] - ONE;\r
6855         break;\r
6856 \r
6857       case WhiteWins:\r
6858       case BlackWins:\r
6859       case GameIsDrawn:\r
6860       case GameUnfinished:\r
6861         if (appData.debugMode)\r
6862           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
6863         p = strchr(yy_text, '{');\r
6864         if (p == NULL) p = strchr(yy_text, '(');\r
6865         if (p == NULL) {\r
6866             p = yy_text;\r
6867             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6868         } else {\r
6869             q = strchr(p, *p == '{' ? '}' : ')');\r
6870             if (q != NULL) *q = NULLCHAR;\r
6871             p++;\r
6872         }\r
6873         GameEnds(moveType, p, GE_FILE);\r
6874         done = TRUE;\r
6875         if (cmailMsgLoaded) {\r
6876             ClearHighlights();\r
6877             flipView = WhiteOnMove(currentMove);\r
6878             if (moveType == GameUnfinished) flipView = !flipView;\r
6879             if (appData.debugMode)\r
6880               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
6881         }\r
6882         break;\r
6883 \r
6884       case (ChessMove) 0:       /* end of file */\r
6885         if (appData.debugMode)\r
6886           fprintf(debugFP, "Parser hit end of file\n");\r
6887         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
6888                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
6889           case MT_NONE:\r
6890           case MT_CHECK:\r
6891             break;\r
6892           case MT_CHECKMATE:\r
6893             if (WhiteOnMove(currentMove)) {\r
6894                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
6895             } else {\r
6896                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
6897             }\r
6898             break;\r
6899           case MT_STALEMATE:\r
6900             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
6901             break;\r
6902         }\r
6903         done = TRUE;\r
6904         break;\r
6905 \r
6906       case MoveNumberOne:\r
6907         if (lastLoadGameStart == GNUChessGame) {\r
6908             /* GNUChessGames have numbers, but they aren't move numbers */\r
6909             if (appData.debugMode)\r
6910               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
6911                       yy_text, (int) moveType);\r
6912             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
6913         }\r
6914         /* else fall thru */\r
6915 \r
6916       case XBoardGame:\r
6917       case GNUChessGame:\r
6918       case PGNTag:\r
6919         /* Reached start of next game in file */\r
6920         if (appData.debugMode)\r
6921           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
6922         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
6923                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
6924           case MT_NONE:\r
6925           case MT_CHECK:\r
6926             break;\r
6927           case MT_CHECKMATE:\r
6928             if (WhiteOnMove(currentMove)) {\r
6929                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
6930             } else {\r
6931                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
6932             }\r
6933             break;\r
6934           case MT_STALEMATE:\r
6935             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
6936             break;\r
6937         }\r
6938         done = TRUE;\r
6939         break;\r
6940 \r
6941       case PositionDiagram:     /* should not happen; ignore */\r
6942       case ElapsedTime:         /* ignore */\r
6943       case NAG:                 /* ignore */\r
6944         if (appData.debugMode)\r
6945           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
6946                   yy_text, (int) moveType);\r
6947         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
6948 \r
6949       case IllegalMove:\r
6950         if (appData.testLegality) {\r
6951             if (appData.debugMode)\r
6952               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
6953             sprintf(move, "Illegal move: %d.%s%s",\r
6954                     (forwardMostMove / 2) + 1,\r
6955                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6956             DisplayError(move, 0);\r
6957             done = TRUE;\r
6958         } else {\r
6959             if (appData.debugMode)\r
6960               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
6961                       yy_text, currentMoveString);\r
6962             fromX = currentMoveString[0] - AAA;\r
6963             fromY = currentMoveString[1] - ONE;\r
6964             toX = currentMoveString[2] - AAA;\r
6965             toY = currentMoveString[3] - ONE;\r
6966             promoChar = currentMoveString[4];\r
6967         }\r
6968         break;\r
6969 \r
6970       case AmbiguousMove:\r
6971         if (appData.debugMode)\r
6972           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
6973         sprintf(move, "Ambiguous move: %d.%s%s",\r
6974                 (forwardMostMove / 2) + 1,\r
6975                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6976         DisplayError(move, 0);\r
6977         done = TRUE;\r
6978         break;\r
6979 \r
6980       default:\r
6981       case ImpossibleMove:\r
6982         if (appData.debugMode)\r
6983           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
6984         sprintf(move, "Illegal move: %d.%s%s",\r
6985                 (forwardMostMove / 2) + 1,\r
6986                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
6987         DisplayError(move, 0);\r
6988         done = TRUE;\r
6989         break;\r
6990     }\r
6991 \r
6992     if (done) {\r
6993         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
6994             DrawPosition(FALSE, boards[currentMove]);\r
6995             DisplayBothClocks();\r
6996             if (!appData.matchMode && commentList[currentMove] != NULL)\r
6997               DisplayComment(currentMove - 1, commentList[currentMove]);\r
6998         }\r
6999         (void) StopLoadGameTimer();\r
7000         gameFileFP = NULL;\r
7001         cmailOldMove = forwardMostMove;\r
7002         return FALSE;\r
7003     } else {\r
7004         /* currentMoveString is set as a side-effect of yylex */\r
7005         strcat(currentMoveString, "\n");\r
7006         strcpy(moveList[forwardMostMove], currentMoveString);\r
7007         \r
7008         thinkOutput[0] = NULLCHAR;\r
7009         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7010         currentMove = forwardMostMove;\r
7011         return TRUE;\r
7012     }\r
7013 }\r
7014 \r
7015 /* Load the nth game from the given file */\r
7016 int\r
7017 LoadGameFromFile(filename, n, title, useList)\r
7018      char *filename;\r
7019      int n;\r
7020      char *title;\r
7021      /*Boolean*/ int useList;\r
7022 {\r
7023     FILE *f;\r
7024     char buf[MSG_SIZ];\r
7025 \r
7026     if (strcmp(filename, "-") == 0) {\r
7027         f = stdin;\r
7028         title = "stdin";\r
7029     } else {\r
7030         f = fopen(filename, "rb");\r
7031         if (f == NULL) {\r
7032             sprintf(buf, "Can't open \"%s\"", filename);\r
7033             DisplayError(buf, errno);\r
7034             return FALSE;\r
7035         }\r
7036     }\r
7037     if (fseek(f, 0, 0) == -1) {\r
7038         /* f is not seekable; probably a pipe */\r
7039         useList = FALSE;\r
7040     }\r
7041     if (useList && n == 0) {\r
7042         int error = GameListBuild(f);\r
7043         if (error) {\r
7044             DisplayError("Cannot build game list", error);\r
7045         } else if (!ListEmpty(&gameList) &&\r
7046                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7047             GameListPopUp(f, title);\r
7048             return TRUE;\r
7049         }\r
7050         GameListDestroy();\r
7051         n = 1;\r
7052     }\r
7053     if (n == 0) n = 1;\r
7054     return LoadGame(f, n, title, FALSE);\r
7055 }\r
7056 \r
7057 \r
7058 void\r
7059 MakeRegisteredMove()\r
7060 {\r
7061     int fromX, fromY, toX, toY;\r
7062     char promoChar;\r
7063     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7064         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
7065           case CMAIL_MOVE:\r
7066           case CMAIL_DRAW:\r
7067             if (appData.debugMode)\r
7068               fprintf(debugFP, "Restoring %s for game %d\n",\r
7069                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
7070     \r
7071             thinkOutput[0] = NULLCHAR;\r
7072             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
7073             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
7074             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
7075             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
7076             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
7077             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
7078             MakeMove(fromX, fromY, toX, toY, promoChar);\r
7079             ShowMove(fromX, fromY, toX, toY);\r
7080               \r
7081             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7082                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7083               case MT_NONE:\r
7084               case MT_CHECK:\r
7085                 break;\r
7086                 \r
7087               case MT_CHECKMATE:\r
7088                 if (WhiteOnMove(currentMove)) {\r
7089                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
7090                 } else {\r
7091                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
7092                 }\r
7093                 break;\r
7094                 \r
7095               case MT_STALEMATE:\r
7096                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
7097                 break;\r
7098             }\r
7099 \r
7100             break;\r
7101             \r
7102           case CMAIL_RESIGN:\r
7103             if (WhiteOnMove(currentMove)) {\r
7104                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
7105             } else {\r
7106                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
7107             }\r
7108             break;\r
7109             \r
7110           case CMAIL_ACCEPT:\r
7111             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
7112             break;\r
7113               \r
7114           default:\r
7115             break;\r
7116         }\r
7117     }\r
7118 \r
7119     return;\r
7120 }\r
7121 \r
7122 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
7123 int\r
7124 CmailLoadGame(f, gameNumber, title, useList)\r
7125      FILE *f;\r
7126      int gameNumber;\r
7127      char *title;\r
7128      int useList;\r
7129 {\r
7130     int retVal;\r
7131 \r
7132     if (gameNumber > nCmailGames) {\r
7133         DisplayError("No more games in this message", 0);\r
7134         return FALSE;\r
7135     }\r
7136     if (f == lastLoadGameFP) {\r
7137         int offset = gameNumber - lastLoadGameNumber;\r
7138         if (offset == 0) {\r
7139             cmailMsg[0] = NULLCHAR;\r
7140             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7141                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
7142                 nCmailMovesRegistered--;\r
7143             }\r
7144             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
7145             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
7146                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
7147             }\r
7148         } else {\r
7149             if (! RegisterMove()) return FALSE;\r
7150         }\r
7151     }\r
7152 \r
7153     retVal = LoadGame(f, gameNumber, title, useList);\r
7154 \r
7155     /* Make move registered during previous look at this game, if any */\r
7156     MakeRegisteredMove();\r
7157 \r
7158     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
7159         commentList[currentMove]\r
7160           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
7161         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7162     }\r
7163 \r
7164     return retVal;\r
7165 }\r
7166 \r
7167 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
7168 int\r
7169 ReloadGame(offset)\r
7170      int offset;\r
7171 {\r
7172     int gameNumber = lastLoadGameNumber + offset;\r
7173     if (lastLoadGameFP == NULL) {\r
7174         DisplayError("No game has been loaded yet", 0);\r
7175         return FALSE;\r
7176     }\r
7177     if (gameNumber <= 0) {\r
7178         DisplayError("Can't back up any further", 0);\r
7179         return FALSE;\r
7180     }\r
7181     if (cmailMsgLoaded) {\r
7182         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
7183                              lastLoadGameTitle, lastLoadGameUseList);\r
7184     } else {\r
7185         return LoadGame(lastLoadGameFP, gameNumber,\r
7186                         lastLoadGameTitle, lastLoadGameUseList);\r
7187     }\r
7188 }\r
7189 \r
7190 \r
7191 \r
7192 /* Load the nth game from open file f */\r
7193 int\r
7194 LoadGame(f, gameNumber, title, useList)\r
7195      FILE *f;\r
7196      int gameNumber;\r
7197      char *title;\r
7198      int useList;\r
7199 {\r
7200     ChessMove cm;\r
7201     char buf[MSG_SIZ];\r
7202     int gn = gameNumber;\r
7203     ListGame *lg = NULL;\r
7204     int numPGNTags = 0;\r
7205     int err;\r
7206     GameMode oldGameMode;\r
7207 \r
7208     if (appData.debugMode) \r
7209         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
7210 \r
7211     if (gameMode == Training )\r
7212         SetTrainingModeOff();\r
7213 \r
7214     oldGameMode = gameMode;\r
7215     if (gameMode != BeginningOfGame) {\r
7216       Reset(FALSE, TRUE);\r
7217     }\r
7218 \r
7219     gameFileFP = f;\r
7220     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
7221         fclose(lastLoadGameFP);\r
7222     }\r
7223 \r
7224     if (useList) {\r
7225         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
7226         \r
7227         if (lg) {\r
7228             fseek(f, lg->offset, 0);\r
7229             GameListHighlight(gameNumber);\r
7230             gn = 1;\r
7231         }\r
7232         else {\r
7233             DisplayError("Game number out of range", 0);\r
7234             return FALSE;\r
7235         }\r
7236     } else {\r
7237         GameListDestroy();\r
7238         if (fseek(f, 0, 0) == -1) {\r
7239             if (f == lastLoadGameFP ?\r
7240                 gameNumber == lastLoadGameNumber + 1 :\r
7241                 gameNumber == 1) {\r
7242                 gn = 1;\r
7243             } else {\r
7244                 DisplayError("Can't seek on game file", 0);\r
7245                 return FALSE;\r
7246             }\r
7247         }\r
7248     }\r
7249     lastLoadGameFP = f;\r
7250     lastLoadGameNumber = gameNumber;\r
7251     strcpy(lastLoadGameTitle, title);\r
7252     lastLoadGameUseList = useList;\r
7253 \r
7254     yynewfile(f);\r
7255 \r
7256 \r
7257     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
7258         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
7259                 lg->gameInfo.black);\r
7260             DisplayTitle(buf);\r
7261     } else if (*title != NULLCHAR) {\r
7262         if (gameNumber > 1) {\r
7263             sprintf(buf, "%s %d", title, gameNumber);\r
7264             DisplayTitle(buf);\r
7265         } else {\r
7266             DisplayTitle(title);\r
7267         }\r
7268     }\r
7269 \r
7270     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
7271         gameMode = PlayFromGameFile;\r
7272         ModeHighlight();\r
7273     }\r
7274 \r
7275     currentMove = forwardMostMove = backwardMostMove = 0;\r
7276     CopyBoard(boards[0], initialPosition);\r
7277     StopClocks();\r
7278 \r
7279     /*\r
7280      * Skip the first gn-1 games in the file.\r
7281      * Also skip over anything that precedes an identifiable \r
7282      * start of game marker, to avoid being confused by \r
7283      * garbage at the start of the file.  Currently \r
7284      * recognized start of game markers are the move number "1",\r
7285      * the pattern "gnuchess .* game", the pattern\r
7286      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
7287      * A game that starts with one of the latter two patterns\r
7288      * will also have a move number 1, possibly\r
7289      * following a position diagram.\r
7290      * 5-4-02: Let's try being more lenient and allowing a game to\r
7291      * start with an unnumbered move.  Does that break anything?\r
7292      */\r
7293     cm = lastLoadGameStart = (ChessMove) 0;\r
7294     while (gn > 0) {\r
7295         yyboardindex = forwardMostMove;\r
7296         cm = (ChessMove) yylex();\r
7297         switch (cm) {\r
7298           case (ChessMove) 0:\r
7299             if (cmailMsgLoaded) {\r
7300                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
7301             } else {\r
7302                 Reset(TRUE, TRUE);\r
7303                 DisplayError("Game not found in file", 0);\r
7304             }\r
7305             return FALSE;\r
7306 \r
7307           case GNUChessGame:\r
7308           case XBoardGame:\r
7309             gn--;\r
7310             lastLoadGameStart = cm;\r
7311             break;\r
7312             \r
7313           case MoveNumberOne:\r
7314             switch (lastLoadGameStart) {\r
7315               case GNUChessGame:\r
7316               case XBoardGame:\r
7317               case PGNTag:\r
7318                 break;\r
7319               case MoveNumberOne:\r
7320               case (ChessMove) 0:\r
7321                 gn--;           /* count this game */\r
7322                 lastLoadGameStart = cm;\r
7323                 break;\r
7324               default:\r
7325                 /* impossible */\r
7326                 break;\r
7327             }\r
7328             break;\r
7329 \r
7330           case PGNTag:\r
7331             switch (lastLoadGameStart) {\r
7332               case GNUChessGame:\r
7333               case PGNTag:\r
7334               case MoveNumberOne:\r
7335               case (ChessMove) 0:\r
7336                 gn--;           /* count this game */\r
7337                 lastLoadGameStart = cm;\r
7338                 break;\r
7339               case XBoardGame:\r
7340                 lastLoadGameStart = cm; /* game counted already */\r
7341                 break;\r
7342               default:\r
7343                 /* impossible */\r
7344                 break;\r
7345             }\r
7346             if (gn > 0) {\r
7347                 do {\r
7348                     yyboardindex = forwardMostMove;\r
7349                     cm = (ChessMove) yylex();\r
7350                 } while (cm == PGNTag || cm == Comment);\r
7351             }\r
7352             break;\r
7353 \r
7354           case WhiteWins:\r
7355           case BlackWins:\r
7356           case GameIsDrawn:\r
7357             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
7358                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
7359                     != CMAIL_OLD_RESULT) {\r
7360                     nCmailResults ++ ;\r
7361                     cmailResult[  CMAIL_MAX_GAMES\r
7362                                 - gn - 1] = CMAIL_OLD_RESULT;\r
7363                 }\r
7364             }\r
7365             break;\r
7366 \r
7367           case NormalMove:\r
7368             /* Only a NormalMove can be at the start of a game\r
7369              * without a position diagram. */\r
7370             if (lastLoadGameStart == (ChessMove) 0) {\r
7371               gn--;\r
7372               lastLoadGameStart = MoveNumberOne;\r
7373             }\r
7374             break;\r
7375 \r
7376           default:\r
7377             break;\r
7378         }\r
7379     }\r
7380     \r
7381     if (appData.debugMode)\r
7382       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
7383 \r
7384     if (cm == XBoardGame) {\r
7385         /* Skip any header junk before position diagram and/or move 1 */\r
7386         for (;;) {\r
7387             yyboardindex = forwardMostMove;\r
7388             cm = (ChessMove) yylex();\r
7389 \r
7390             if (cm == (ChessMove) 0 ||\r
7391                 cm == GNUChessGame || cm == XBoardGame) {\r
7392                 /* Empty game; pretend end-of-file and handle later */\r
7393                 cm = (ChessMove) 0;\r
7394                 break;\r
7395             }\r
7396 \r
7397             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
7398                 cm == PGNTag || cm == Comment)\r
7399               break;\r
7400         }\r
7401     } else if (cm == GNUChessGame) {\r
7402         if (gameInfo.event != NULL) {\r
7403             free(gameInfo.event);\r
7404         }\r
7405         gameInfo.event = StrSave(yy_text);\r
7406     }   \r
7407 \r
7408     startedFromSetupPosition = FALSE;\r
7409     while (cm == PGNTag) {\r
7410         if (appData.debugMode) \r
7411           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
7412         err = ParsePGNTag(yy_text, &gameInfo);\r
7413         if (!err) numPGNTags++;\r
7414 \r
7415         if (gameInfo.fen != NULL) {\r
7416           Board initial_position;\r
7417           startedFromSetupPosition = TRUE;\r
7418           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
7419             Reset(TRUE, TRUE);\r
7420             DisplayError("Bad FEN position in file", 0);\r
7421             return FALSE;\r
7422           }\r
7423           CopyBoard(boards[0], initial_position);\r
7424           /* [HGM] copy FEN attributes as well */\r
7425           {   int i;\r
7426               initialRulePlies = FENrulePlies;\r
7427               epStatus[0] = FENepStatus;\r
7428               for( i=0; i< nrCastlingRights; i++ )\r
7429                   castlingRights[0][i] = FENcastlingRights[i];\r
7430           }\r
7431           if (blackPlaysFirst) {\r
7432             currentMove = forwardMostMove = backwardMostMove = 1;\r
7433             CopyBoard(boards[1], initial_position);\r
7434             strcpy(moveList[0], "");\r
7435             strcpy(parseList[0], "");\r
7436             timeRemaining[0][1] = whiteTimeRemaining;\r
7437             timeRemaining[1][1] = blackTimeRemaining;\r
7438             if (commentList[0] != NULL) {\r
7439               commentList[1] = commentList[0];\r
7440               commentList[0] = NULL;\r
7441             }\r
7442           } else {\r
7443             currentMove = forwardMostMove = backwardMostMove = 0;\r
7444           }\r
7445           yyboardindex = forwardMostMove;\r
7446           free(gameInfo.fen);\r
7447           gameInfo.fen = NULL;\r
7448         }\r
7449 \r
7450         yyboardindex = forwardMostMove;\r
7451         cm = (ChessMove) yylex();\r
7452 \r
7453         /* Handle comments interspersed among the tags */\r
7454         while (cm == Comment) {\r
7455             char *p;\r
7456             if (appData.debugMode) \r
7457               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7458             p = yy_text;\r
7459             if (*p == '{' || *p == '[' || *p == '(') {\r
7460                 p[strlen(p) - 1] = NULLCHAR;\r
7461                 p++;\r
7462             }\r
7463             while (*p == '\n') p++;\r
7464             AppendComment(currentMove, p);\r
7465             yyboardindex = forwardMostMove;\r
7466             cm = (ChessMove) yylex();\r
7467         }\r
7468     }\r
7469 \r
7470     /* don't rely on existence of Event tag since if game was\r
7471      * pasted from clipboard the Event tag may not exist\r
7472      */\r
7473     if (numPGNTags > 0){\r
7474         char *tags;\r
7475         if (gameInfo.variant == VariantNormal) {\r
7476           gameInfo.variant = StringToVariant(gameInfo.event);\r
7477         }\r
7478         if (!matchMode) {\r
7479           if( appData.autoDisplayTags ) {\r
7480             tags = PGNTags(&gameInfo);\r
7481             TagsPopUp(tags, CmailMsg());\r
7482             free(tags);\r
7483           }\r
7484         }\r
7485     } else {\r
7486         /* Make something up, but don't display it now */\r
7487         SetGameInfo();\r
7488         TagsPopDown();\r
7489     }\r
7490 \r
7491     if (cm == PositionDiagram) {\r
7492         int i, j;\r
7493         char *p;\r
7494         Board initial_position;\r
7495 \r
7496         if (appData.debugMode)\r
7497           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
7498 \r
7499         if (!startedFromSetupPosition) {\r
7500             p = yy_text;\r
7501             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
7502               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
7503                 switch (*p) {\r
7504                   case '[':\r
7505                   case '-':\r
7506                   case ' ':\r
7507                   case '\t':\r
7508                   case '\n':\r
7509                   case '\r':\r
7510                     break;\r
7511                   default:\r
7512                     initial_position[i][j++] = CharToPiece(*p);\r
7513                     break;\r
7514                 }\r
7515             while (*p == ' ' || *p == '\t' ||\r
7516                    *p == '\n' || *p == '\r') p++;\r
7517         \r
7518             if (strncmp(p, "black", strlen("black"))==0)\r
7519               blackPlaysFirst = TRUE;\r
7520             else\r
7521               blackPlaysFirst = FALSE;\r
7522             startedFromSetupPosition = TRUE;\r
7523         \r
7524             CopyBoard(boards[0], initial_position);\r
7525             if (blackPlaysFirst) {\r
7526                 currentMove = forwardMostMove = backwardMostMove = 1;\r
7527                 CopyBoard(boards[1], initial_position);\r
7528                 strcpy(moveList[0], "");\r
7529                 strcpy(parseList[0], "");\r
7530                 timeRemaining[0][1] = whiteTimeRemaining;\r
7531                 timeRemaining[1][1] = blackTimeRemaining;\r
7532                 if (commentList[0] != NULL) {\r
7533                     commentList[1] = commentList[0];\r
7534                     commentList[0] = NULL;\r
7535                 }\r
7536             } else {\r
7537                 currentMove = forwardMostMove = backwardMostMove = 0;\r
7538             }\r
7539         }\r
7540         yyboardindex = forwardMostMove;\r
7541         cm = (ChessMove) yylex();\r
7542     }\r
7543 \r
7544     if (first.pr == NoProc) {\r
7545         StartChessProgram(&first);\r
7546     }\r
7547     InitChessProgram(&first);\r
7548     SendToProgram("force\n", &first);\r
7549     if (startedFromSetupPosition) {\r
7550         SendBoard(&first, forwardMostMove);\r
7551         DisplayBothClocks();\r
7552     }      \r
7553 \r
7554     while (cm == Comment) {\r
7555         char *p;\r
7556         if (appData.debugMode) \r
7557           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7558         p = yy_text;\r
7559         if (*p == '{' || *p == '[' || *p == '(') {\r
7560             p[strlen(p) - 1] = NULLCHAR;\r
7561             p++;\r
7562         }\r
7563         while (*p == '\n') p++;\r
7564         AppendComment(currentMove, p);\r
7565         yyboardindex = forwardMostMove;\r
7566         cm = (ChessMove) yylex();\r
7567     }\r
7568 \r
7569     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
7570         cm == WhiteWins || cm == BlackWins ||\r
7571         cm == GameIsDrawn || cm == GameUnfinished) {\r
7572         DisplayMessage("", "No moves in game");\r
7573         if (cmailMsgLoaded) {\r
7574             if (appData.debugMode)\r
7575               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
7576             ClearHighlights();\r
7577             flipView = FALSE;\r
7578         }\r
7579         DrawPosition(FALSE, boards[currentMove]);\r
7580         DisplayBothClocks();\r
7581         gameMode = EditGame;\r
7582         ModeHighlight();\r
7583         gameFileFP = NULL;\r
7584         cmailOldMove = 0;\r
7585         return TRUE;\r
7586     }\r
7587 \r
7588     if (commentList[currentMove] != NULL) {\r
7589       if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
7590         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7591       }\r
7592     }\r
7593     if (!matchMode && appData.timeDelay != 0) \r
7594       DrawPosition(FALSE, boards[currentMove]);\r
7595 \r
7596     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
7597       programStats.ok_to_send = 1;\r
7598     }\r
7599 \r
7600     /* if the first token after the PGN tags is a move\r
7601      * and not move number 1, retrieve it from the parser \r
7602      */\r
7603     if (cm != MoveNumberOne)\r
7604         LoadGameOneMove(cm);\r
7605 \r
7606     /* load the remaining moves from the file */\r
7607     while (LoadGameOneMove((ChessMove)0)) {\r
7608       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7609       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7610     }\r
7611 \r
7612     /* rewind to the start of the game */\r
7613     currentMove = backwardMostMove;\r
7614 \r
7615     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7616 \r
7617     if (oldGameMode == AnalyzeFile ||\r
7618         oldGameMode == AnalyzeMode) {\r
7619       AnalyzeFileEvent();\r
7620     }\r
7621 \r
7622     if (matchMode || appData.timeDelay == 0) {\r
7623       ToEndEvent();\r
7624       gameMode = EditGame;\r
7625       ModeHighlight();\r
7626     } else if (appData.timeDelay > 0) {\r
7627       AutoPlayGameLoop();\r
7628     }\r
7629 \r
7630     if (appData.debugMode) \r
7631         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
7632     return TRUE;\r
7633 }\r
7634 \r
7635 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
7636 int\r
7637 ReloadPosition(offset)\r
7638      int offset;\r
7639 {\r
7640     int positionNumber = lastLoadPositionNumber + offset;\r
7641     if (lastLoadPositionFP == NULL) {\r
7642         DisplayError("No position has been loaded yet", 0);\r
7643         return FALSE;\r
7644     }\r
7645     if (positionNumber <= 0) {\r
7646         DisplayError("Can't back up any further", 0);\r
7647         return FALSE;\r
7648     }\r
7649     return LoadPosition(lastLoadPositionFP, positionNumber,\r
7650                         lastLoadPositionTitle);\r
7651 }\r
7652 \r
7653 /* Load the nth position from the given file */\r
7654 int\r
7655 LoadPositionFromFile(filename, n, title)\r
7656      char *filename;\r
7657      int n;\r
7658      char *title;\r
7659 {\r
7660     FILE *f;\r
7661     char buf[MSG_SIZ];\r
7662 \r
7663     if (strcmp(filename, "-") == 0) {\r
7664         return LoadPosition(stdin, n, "stdin");\r
7665     } else {\r
7666         f = fopen(filename, "rb");\r
7667         if (f == NULL) {\r
7668             sprintf(buf, "Can't open \"%s\"", filename);\r
7669             DisplayError(buf, errno);\r
7670             return FALSE;\r
7671         } else {\r
7672             return LoadPosition(f, n, title);\r
7673         }\r
7674     }\r
7675 }\r
7676 \r
7677 /* Load the nth position from the given open file, and close it */\r
7678 int\r
7679 LoadPosition(f, positionNumber, title)\r
7680      FILE *f;\r
7681      int positionNumber;\r
7682      char *title;\r
7683 {\r
7684     char *p, line[MSG_SIZ];\r
7685     Board initial_position;\r
7686     int i, j, fenMode, pn;\r
7687     \r
7688     if (gameMode == Training )\r
7689         SetTrainingModeOff();\r
7690 \r
7691     if (gameMode != BeginningOfGame) {\r
7692         Reset(FALSE, TRUE);\r
7693     }\r
7694     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
7695         fclose(lastLoadPositionFP);\r
7696     }\r
7697     if (positionNumber == 0) positionNumber = 1;\r
7698     lastLoadPositionFP = f;\r
7699     lastLoadPositionNumber = positionNumber;\r
7700     strcpy(lastLoadPositionTitle, title);\r
7701     if (first.pr == NoProc) {\r
7702       StartChessProgram(&first);\r
7703       InitChessProgram(&first);\r
7704     }    \r
7705     pn = positionNumber;\r
7706     if (positionNumber < 0) {\r
7707         /* Negative position number means to seek to that byte offset */\r
7708         if (fseek(f, -positionNumber, 0) == -1) {\r
7709             DisplayError("Can't seek on position file", 0);\r
7710             return FALSE;\r
7711         };\r
7712         pn = 1;\r
7713     } else {\r
7714         if (fseek(f, 0, 0) == -1) {\r
7715             if (f == lastLoadPositionFP ?\r
7716                 positionNumber == lastLoadPositionNumber + 1 :\r
7717                 positionNumber == 1) {\r
7718                 pn = 1;\r
7719             } else {\r
7720                 DisplayError("Can't seek on position file", 0);\r
7721                 return FALSE;\r
7722             }\r
7723         }\r
7724     }\r
7725     /* See if this file is FEN or old-style xboard */\r
7726     if (fgets(line, MSG_SIZ, f) == NULL) {\r
7727         DisplayError("Position not found in file", 0);\r
7728         return FALSE;\r
7729     }\r
7730     switch (line[0]) {\r
7731       case '#':  case 'x':\r
7732       default:\r
7733         fenMode = FALSE;\r
7734         break;\r
7735       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
7736       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
7737       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
7738       case '7':  case '8':  case '9':\r
7739 #ifdef FAIRY\r
7740       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
7741       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
7742       case 'C':  case 'W':             case 'c':  case 'w': \r
7743 #endif\r
7744         fenMode = TRUE;\r
7745         break;\r
7746     }\r
7747 \r
7748     if (pn >= 2) {\r
7749         if (fenMode || line[0] == '#') pn--;\r
7750         while (pn > 0) {\r
7751             /* skip postions before number pn */\r
7752             if (fgets(line, MSG_SIZ, f) == NULL) {\r
7753                 Reset(TRUE, TRUE);\r
7754                 DisplayError("Position not found in file", 0);\r
7755                 return FALSE;\r
7756             }\r
7757             if (fenMode || line[0] == '#') pn--;\r
7758         }\r
7759     }\r
7760 \r
7761     if (fenMode) {\r
7762         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
7763             DisplayError("Bad FEN position in file", 0);\r
7764             return FALSE;\r
7765         }\r
7766     } else {\r
7767         (void) fgets(line, MSG_SIZ, f);\r
7768         (void) fgets(line, MSG_SIZ, f);\r
7769     \r
7770         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
7771             (void) fgets(line, MSG_SIZ, f);\r
7772             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
7773                 if (*p == ' ')\r
7774                   continue;\r
7775                 initial_position[i][j++] = CharToPiece(*p);\r
7776             }\r
7777         }\r
7778     \r
7779         blackPlaysFirst = FALSE;\r
7780         if (!feof(f)) {\r
7781             (void) fgets(line, MSG_SIZ, f);\r
7782             if (strncmp(line, "black", strlen("black"))==0)\r
7783               blackPlaysFirst = TRUE;\r
7784         }\r
7785     }\r
7786     startedFromSetupPosition = TRUE;\r
7787     \r
7788     SendToProgram("force\n", &first);\r
7789     CopyBoard(boards[0], initial_position);\r
7790           /* [HGM] copy FEN attributes as well */\r
7791           {   int i;\r
7792               initialRulePlies = FENrulePlies;\r
7793               epStatus[0] = FENepStatus;\r
7794               for( i=0; i< nrCastlingRights; i++ )\r
7795                   castlingRights[0][i] = FENcastlingRights[i];\r
7796           }\r
7797     if (blackPlaysFirst) {\r
7798         currentMove = forwardMostMove = backwardMostMove = 1;\r
7799         strcpy(moveList[0], "");\r
7800         strcpy(parseList[0], "");\r
7801         CopyBoard(boards[1], initial_position);\r
7802         DisplayMessage("", "Black to play");\r
7803     } else {\r
7804         currentMove = forwardMostMove = backwardMostMove = 0;\r
7805         DisplayMessage("", "White to play");\r
7806     }\r
7807     SendBoard(&first, forwardMostMove);\r
7808 \r
7809     if (positionNumber > 1) {\r
7810         sprintf(line, "%s %d", title, positionNumber);\r
7811         DisplayTitle(line);\r
7812     } else {\r
7813         DisplayTitle(title);\r
7814     }\r
7815     gameMode = EditGame;\r
7816     ModeHighlight();\r
7817     ResetClocks();\r
7818     timeRemaining[0][1] = whiteTimeRemaining;\r
7819     timeRemaining[1][1] = blackTimeRemaining;\r
7820     DrawPosition(FALSE, boards[currentMove]);\r
7821    \r
7822     return TRUE;\r
7823 }\r
7824 \r
7825 \r
7826 void\r
7827 CopyPlayerNameIntoFileName(dest, src)\r
7828      char **dest, *src;\r
7829 {\r
7830     while (*src != NULLCHAR && *src != ',') {\r
7831         if (*src == ' ') {\r
7832             *(*dest)++ = '_';\r
7833             src++;\r
7834         } else {\r
7835             *(*dest)++ = *src++;\r
7836         }\r
7837     }\r
7838 }\r
7839 \r
7840 char *DefaultFileName(ext)\r
7841      char *ext;\r
7842 {\r
7843     static char def[MSG_SIZ];\r
7844     char *p;\r
7845 \r
7846     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
7847         p = def;\r
7848         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
7849         *p++ = '-';\r
7850         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
7851         *p++ = '.';\r
7852         strcpy(p, ext);\r
7853     } else {\r
7854         def[0] = NULLCHAR;\r
7855     }\r
7856     return def;\r
7857 }\r
7858 \r
7859 /* Save the current game to the given file */\r
7860 int\r
7861 SaveGameToFile(filename, append)\r
7862      char *filename;\r
7863      int append;\r
7864 {\r
7865     FILE *f;\r
7866     char buf[MSG_SIZ];\r
7867 \r
7868     if (strcmp(filename, "-") == 0) {\r
7869         return SaveGame(stdout, 0, NULL);\r
7870     } else {\r
7871         f = fopen(filename, append ? "a" : "w");\r
7872         if (f == NULL) {\r
7873             sprintf(buf, "Can't open \"%s\"", filename);\r
7874             DisplayError(buf, errno);\r
7875             return FALSE;\r
7876         } else {\r
7877             return SaveGame(f, 0, NULL);\r
7878         }\r
7879     }\r
7880 }\r
7881 \r
7882 char *\r
7883 SavePart(str)\r
7884      char *str;\r
7885 {\r
7886     static char buf[MSG_SIZ];\r
7887     char *p;\r
7888     \r
7889     p = strchr(str, ' ');\r
7890     if (p == NULL) return str;\r
7891     strncpy(buf, str, p - str);\r
7892     buf[p - str] = NULLCHAR;\r
7893     return buf;\r
7894 }\r
7895 \r
7896 #define PGN_MAX_LINE 75\r
7897 \r
7898 #define PGN_SIDE_WHITE  0\r
7899 #define PGN_SIDE_BLACK  1\r
7900 \r
7901 /* [AS] */\r
7902 static int FindFirstMoveOutOfBook( int side )\r
7903 {\r
7904     int result = -1;\r
7905 \r
7906     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
7907         int index = backwardMostMove;\r
7908         int has_book_hit = 0;\r
7909 \r
7910         if( (index % 2) != side ) {\r
7911             index++;\r
7912         }\r
7913 \r
7914         while( index < forwardMostMove ) {\r
7915             /* Check to see if engine is in book */\r
7916             int depth = pvInfoList[index].depth;\r
7917             int score = pvInfoList[index].score;\r
7918             int in_book = 0;\r
7919 \r
7920             if( depth <= 2 ) {\r
7921                 in_book = 1;\r
7922             }\r
7923             else if( score == 0 && depth == 63 ) {\r
7924                 in_book = 1; /* Zappa */\r
7925             }\r
7926             else if( score == 2 && depth == 99 ) {\r
7927                 in_book = 1; /* Abrok */\r
7928             }\r
7929 \r
7930             has_book_hit += in_book;\r
7931 \r
7932             if( ! in_book ) {\r
7933                 result = index;\r
7934 \r
7935                 break;\r
7936             }\r
7937 \r
7938             index += 2;\r
7939         }\r
7940     }\r
7941 \r
7942     return result;\r
7943 }\r
7944 \r
7945 /* [AS] */\r
7946 void GetOutOfBookInfo( char * buf )\r
7947 {\r
7948     int oob[2];\r
7949     int i;\r
7950     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
7951 \r
7952     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
7953     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
7954 \r
7955     *buf = '\0';\r
7956 \r
7957     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
7958         for( i=0; i<2; i++ ) {\r
7959             int idx = oob[i];\r
7960 \r
7961             if( idx >= 0 ) {\r
7962                 if( i > 0 && oob[0] >= 0 ) {\r
7963                     strcat( buf, "   " );\r
7964                 }\r
7965 \r
7966                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
7967                 sprintf( buf+strlen(buf), "%s%.2f", \r
7968                     pvInfoList[idx].score >= 0 ? "+" : "",\r
7969                     pvInfoList[idx].score / 100.0 );\r
7970             }\r
7971         }\r
7972     }\r
7973 }\r
7974 \r
7975 /* Save game in PGN style and close the file */\r
7976 int\r
7977 SaveGamePGN(f)\r
7978      FILE *f;\r
7979 {\r
7980     int i, offset, linelen, newblock;\r
7981     time_t tm;\r
7982     char *movetext;\r
7983     char numtext[32];\r
7984     int movelen, numlen, blank;\r
7985     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
7986 \r
7987     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
7988     \r
7989     tm = time((time_t *) NULL);\r
7990     \r
7991     PrintPGNTags(f, &gameInfo);\r
7992     \r
7993     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
7994         char *fen = PositionToFEN(backwardMostMove, 1);\r
7995         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
7996         fprintf(f, "\n{--------------\n");\r
7997         PrintPosition(f, backwardMostMove);\r
7998         fprintf(f, "--------------}\n");\r
7999         free(fen);\r
8000     }\r
8001     else {\r
8002         /* [AS] Out of book annotation */\r
8003         if( appData.saveOutOfBookInfo ) {\r
8004             char buf[64];\r
8005 \r
8006             GetOutOfBookInfo( buf );\r
8007 \r
8008             if( buf[0] != '\0' ) {\r
8009                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8010             }\r
8011         }\r
8012 \r
8013         fprintf(f, "\n");\r
8014     }\r
8015 \r
8016     i = backwardMostMove;\r
8017     linelen = 0;\r
8018     newblock = TRUE;\r
8019 \r
8020     while (i < forwardMostMove) {\r
8021         /* Print comments preceding this move */\r
8022         if (commentList[i] != NULL) {\r
8023             if (linelen > 0) fprintf(f, "\n");\r
8024             fprintf(f, "{\n%s}\n", commentList[i]);\r
8025             linelen = 0;\r
8026             newblock = TRUE;\r
8027         }\r
8028 \r
8029         /* Format move number */\r
8030         if ((i % 2) == 0) {\r
8031             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8032         } else {\r
8033             if (newblock) {\r
8034                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
8035             } else {\r
8036                 numtext[0] = NULLCHAR;\r
8037             }\r
8038         }\r
8039         numlen = strlen(numtext);\r
8040         newblock = FALSE;\r
8041 \r
8042         /* Print move number */\r
8043         blank = linelen > 0 && numlen > 0;\r
8044         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
8045             fprintf(f, "\n");\r
8046             linelen = 0;\r
8047             blank = 0;\r
8048         }\r
8049         if (blank) {\r
8050             fprintf(f, " ");\r
8051             linelen++;\r
8052         }\r
8053         fprintf(f, numtext);\r
8054         linelen += numlen;\r
8055 \r
8056         /* Get move */\r
8057         movetext = SavePart(parseList[i]);\r
8058 \r
8059         /* [AS] Add PV info if present */\r
8060         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
8061             /* [HGM] add time */\r
8062             char buf[MSG_SIZ]; int seconds = 0;\r
8063 \r
8064             if(i >= backwardMostMove) {\r
8065                 /* take the time that changed */\r
8066                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
8067                 if(seconds <= 0)\r
8068                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
8069             }\r
8070             seconds /= 1000;\r
8071     if (appData.debugMode) {\r
8072         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
8073                 timeRemaining[0][i+1], timeRemaining[0][i],\r
8074                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
8075         );\r
8076     }\r
8077 \r
8078             if( seconds < 0 ) buf[0] = 0; else\r
8079             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
8080             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
8081 \r
8082             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
8083                 movetext, \r
8084                 pvInfoList[i].score >= 0 ? "+" : "",\r
8085                 pvInfoList[i].score / 100.0,\r
8086                 pvInfoList[i].depth,\r
8087                 buf );\r
8088             movetext = move_buffer;\r
8089         }\r
8090 \r
8091         movelen = strlen(movetext);\r
8092 \r
8093         /* Print move */\r
8094         blank = linelen > 0 && movelen > 0;\r
8095         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
8096             fprintf(f, "\n");\r
8097             linelen = 0;\r
8098             blank = 0;\r
8099         }\r
8100         if (blank) {\r
8101             fprintf(f, " ");\r
8102             linelen++;\r
8103         }\r
8104         fprintf(f, movetext);\r
8105         linelen += movelen;\r
8106 \r
8107         i++;\r
8108     }\r
8109     \r
8110     /* Start a new line */\r
8111     if (linelen > 0) fprintf(f, "\n");\r
8112 \r
8113     /* Print comments after last move */\r
8114     if (commentList[i] != NULL) {\r
8115         fprintf(f, "{\n%s}\n", commentList[i]);\r
8116     }\r
8117 \r
8118     /* Print result */\r
8119     if (gameInfo.resultDetails != NULL &&\r
8120         gameInfo.resultDetails[0] != NULLCHAR) {\r
8121         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
8122                 PGNResult(gameInfo.result));\r
8123     } else {\r
8124         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8125     }\r
8126 \r
8127     fclose(f);\r
8128     return TRUE;\r
8129 }\r
8130 \r
8131 /* Save game in old style and close the file */\r
8132 int\r
8133 SaveGameOldStyle(f)\r
8134      FILE *f;\r
8135 {\r
8136     int i, offset;\r
8137     time_t tm;\r
8138     \r
8139     tm = time((time_t *) NULL);\r
8140     \r
8141     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
8142     PrintOpponents(f);\r
8143     \r
8144     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8145         fprintf(f, "\n[--------------\n");\r
8146         PrintPosition(f, backwardMostMove);\r
8147         fprintf(f, "--------------]\n");\r
8148     } else {\r
8149         fprintf(f, "\n");\r
8150     }\r
8151 \r
8152     i = backwardMostMove;\r
8153     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8154 \r
8155     while (i < forwardMostMove) {\r
8156         if (commentList[i] != NULL) {\r
8157             fprintf(f, "[%s]\n", commentList[i]);\r
8158         }\r
8159 \r
8160         if ((i % 2) == 1) {\r
8161             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
8162             i++;\r
8163         } else {\r
8164             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
8165             i++;\r
8166             if (commentList[i] != NULL) {\r
8167                 fprintf(f, "\n");\r
8168                 continue;\r
8169             }\r
8170             if (i >= forwardMostMove) {\r
8171                 fprintf(f, "\n");\r
8172                 break;\r
8173             }\r
8174             fprintf(f, "%s\n", parseList[i]);\r
8175             i++;\r
8176         }\r
8177     }\r
8178     \r
8179     if (commentList[i] != NULL) {\r
8180         fprintf(f, "[%s]\n", commentList[i]);\r
8181     }\r
8182 \r
8183     /* This isn't really the old style, but it's close enough */\r
8184     if (gameInfo.resultDetails != NULL &&\r
8185         gameInfo.resultDetails[0] != NULLCHAR) {\r
8186         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
8187                 gameInfo.resultDetails);\r
8188     } else {\r
8189         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8190     }\r
8191 \r
8192     fclose(f);\r
8193     return TRUE;\r
8194 }\r
8195 \r
8196 /* Save the current game to open file f and close the file */\r
8197 int\r
8198 SaveGame(f, dummy, dummy2)\r
8199      FILE *f;\r
8200      int dummy;\r
8201      char *dummy2;\r
8202 {\r
8203     if (gameMode == EditPosition) EditPositionDone();\r
8204     if (appData.oldSaveStyle)\r
8205       return SaveGameOldStyle(f);\r
8206     else\r
8207       return SaveGamePGN(f);\r
8208 }\r
8209 \r
8210 /* Save the current position to the given file */\r
8211 int\r
8212 SavePositionToFile(filename)\r
8213      char *filename;\r
8214 {\r
8215     FILE *f;\r
8216     char buf[MSG_SIZ];\r
8217 \r
8218     if (strcmp(filename, "-") == 0) {\r
8219         return SavePosition(stdout, 0, NULL);\r
8220     } else {\r
8221         f = fopen(filename, "a");\r
8222         if (f == NULL) {\r
8223             sprintf(buf, "Can't open \"%s\"", filename);\r
8224             DisplayError(buf, errno);\r
8225             return FALSE;\r
8226         } else {\r
8227             SavePosition(f, 0, NULL);\r
8228             return TRUE;\r
8229         }\r
8230     }\r
8231 }\r
8232 \r
8233 /* Save the current position to the given open file and close the file */\r
8234 int\r
8235 SavePosition(f, dummy, dummy2)\r
8236      FILE *f;\r
8237      int dummy;\r
8238      char *dummy2;\r
8239 {\r
8240     time_t tm;\r
8241     char *fen;\r
8242     \r
8243     if (appData.oldSaveStyle) {\r
8244         tm = time((time_t *) NULL);\r
8245     \r
8246         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
8247         PrintOpponents(f);\r
8248         fprintf(f, "[--------------\n");\r
8249         PrintPosition(f, currentMove);\r
8250         fprintf(f, "--------------]\n");\r
8251     } else {\r
8252         fen = PositionToFEN(currentMove, 1);\r
8253         fprintf(f, "%s\n", fen);\r
8254         free(fen);\r
8255     }\r
8256     fclose(f);\r
8257     return TRUE;\r
8258 }\r
8259 \r
8260 void\r
8261 ReloadCmailMsgEvent(unregister)\r
8262      int unregister;\r
8263 {\r
8264 #if !WIN32\r
8265     static char *inFilename = NULL;\r
8266     static char *outFilename;\r
8267     int i;\r
8268     struct stat inbuf, outbuf;\r
8269     int status;\r
8270     \r
8271     /* Any registered moves are unregistered if unregister is set, */\r
8272     /* i.e. invoked by the signal handler */\r
8273     if (unregister) {\r
8274         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8275             cmailMoveRegistered[i] = FALSE;\r
8276             if (cmailCommentList[i] != NULL) {\r
8277                 free(cmailCommentList[i]);\r
8278                 cmailCommentList[i] = NULL;\r
8279             }\r
8280         }\r
8281         nCmailMovesRegistered = 0;\r
8282     }\r
8283 \r
8284     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8285         cmailResult[i] = CMAIL_NOT_RESULT;\r
8286     }\r
8287     nCmailResults = 0;\r
8288 \r
8289     if (inFilename == NULL) {\r
8290         /* Because the filenames are static they only get malloced once  */\r
8291         /* and they never get freed                                      */\r
8292         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
8293         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
8294 \r
8295         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
8296         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
8297     }\r
8298     \r
8299     status = stat(outFilename, &outbuf);\r
8300     if (status < 0) {\r
8301         cmailMailedMove = FALSE;\r
8302     } else {\r
8303         status = stat(inFilename, &inbuf);\r
8304         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
8305     }\r
8306     \r
8307     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
8308        counts the games, notes how each one terminated, etc.\r
8309        \r
8310        It would be nice to remove this kludge and instead gather all\r
8311        the information while building the game list.  (And to keep it\r
8312        in the game list nodes instead of having a bunch of fixed-size\r
8313        parallel arrays.)  Note this will require getting each game's\r
8314        termination from the PGN tags, as the game list builder does\r
8315        not process the game moves.  --mann\r
8316        */\r
8317     cmailMsgLoaded = TRUE;\r
8318     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
8319     \r
8320     /* Load first game in the file or popup game menu */\r
8321     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
8322 \r
8323 #endif /* !WIN32 */\r
8324     return;\r
8325 }\r
8326 \r
8327 int\r
8328 RegisterMove()\r
8329 {\r
8330     FILE *f;\r
8331     char string[MSG_SIZ];\r
8332 \r
8333     if (   cmailMailedMove\r
8334         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
8335         return TRUE;            /* Allow free viewing  */\r
8336     }\r
8337 \r
8338     /* Unregister move to ensure that we don't leave RegisterMove        */\r
8339     /* with the move registered when the conditions for registering no   */\r
8340     /* longer hold                                                       */\r
8341     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8342         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8343         nCmailMovesRegistered --;\r
8344 \r
8345         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
8346           {\r
8347               free(cmailCommentList[lastLoadGameNumber - 1]);\r
8348               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
8349           }\r
8350     }\r
8351 \r
8352     if (cmailOldMove == -1) {\r
8353         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
8354         return FALSE;\r
8355     }\r
8356 \r
8357     if (currentMove > cmailOldMove + 1) {\r
8358         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
8359         return FALSE;\r
8360     }\r
8361 \r
8362     if (currentMove < cmailOldMove) {\r
8363         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
8364         return FALSE;\r
8365     }\r
8366 \r
8367     if (forwardMostMove > currentMove) {\r
8368         /* Silently truncate extra moves */\r
8369         TruncateGame();\r
8370     }\r
8371 \r
8372     if (   (currentMove == cmailOldMove + 1)\r
8373         || (   (currentMove == cmailOldMove)\r
8374             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
8375                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
8376         if (gameInfo.result != GameUnfinished) {\r
8377             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
8378         }\r
8379 \r
8380         if (commentList[currentMove] != NULL) {\r
8381             cmailCommentList[lastLoadGameNumber - 1]\r
8382               = StrSave(commentList[currentMove]);\r
8383         }\r
8384         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
8385 \r
8386         if (appData.debugMode)\r
8387           fprintf(debugFP, "Saving %s for game %d\n",\r
8388                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8389 \r
8390         sprintf(string,\r
8391                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
8392         \r
8393         f = fopen(string, "w");\r
8394         if (appData.oldSaveStyle) {\r
8395             SaveGameOldStyle(f); /* also closes the file */\r
8396             \r
8397             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
8398             f = fopen(string, "w");\r
8399             SavePosition(f, 0, NULL); /* also closes the file */\r
8400         } else {\r
8401             fprintf(f, "{--------------\n");\r
8402             PrintPosition(f, currentMove);\r
8403             fprintf(f, "--------------}\n\n");\r
8404             \r
8405             SaveGame(f, 0, NULL); /* also closes the file*/\r
8406         }\r
8407         \r
8408         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
8409         nCmailMovesRegistered ++;\r
8410     } else if (nCmailGames == 1) {\r
8411         DisplayError("You have not made a move yet", 0);\r
8412         return FALSE;\r
8413     }\r
8414 \r
8415     return TRUE;\r
8416 }\r
8417 \r
8418 void\r
8419 MailMoveEvent()\r
8420 {\r
8421 #if !WIN32\r
8422     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
8423     FILE *commandOutput;\r
8424     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
8425     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
8426     int nBuffers;\r
8427     int i;\r
8428     int archived;\r
8429     char *arcDir;\r
8430 \r
8431     if (! cmailMsgLoaded) {\r
8432         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
8433         return;\r
8434     }\r
8435 \r
8436     if (nCmailGames == nCmailResults) {\r
8437         DisplayError("No unfinished games", 0);\r
8438         return;\r
8439     }\r
8440 \r
8441 #if CMAIL_PROHIBIT_REMAIL\r
8442     if (cmailMailedMove) {\r
8443         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
8444         DisplayError(msg, 0);\r
8445         return;\r
8446     }\r
8447 #endif\r
8448 \r
8449     if (! (cmailMailedMove || RegisterMove())) return;\r
8450     \r
8451     if (   cmailMailedMove\r
8452         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
8453         sprintf(string, partCommandString,\r
8454                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
8455         commandOutput = popen(string, "rb");\r
8456 \r
8457         if (commandOutput == NULL) {\r
8458             DisplayError("Failed to invoke cmail", 0);\r
8459         } else {\r
8460             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
8461                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
8462             }\r
8463             if (nBuffers > 1) {\r
8464                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
8465                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
8466                 nBytes = MSG_SIZ - 1;\r
8467             } else {\r
8468                 (void) memcpy(msg, buffer, nBytes);\r
8469             }\r
8470             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
8471 \r
8472             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
8473                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
8474 \r
8475                 archived = TRUE;\r
8476                 for (i = 0; i < nCmailGames; i ++) {\r
8477                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
8478                         archived = FALSE;\r
8479                     }\r
8480                 }\r
8481                 if (   archived\r
8482                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
8483                         != NULL)) {\r
8484                     sprintf(buffer, "%s/%s.%s.archive",\r
8485                             arcDir,\r
8486                             appData.cmailGameName,\r
8487                             gameInfo.date);\r
8488                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
8489                     cmailMsgLoaded = FALSE;\r
8490                 }\r
8491             }\r
8492 \r
8493             DisplayInformation(msg);\r
8494             pclose(commandOutput);\r
8495         }\r
8496     } else {\r
8497         if ((*cmailMsg) != '\0') {\r
8498             DisplayInformation(cmailMsg);\r
8499         }\r
8500     }\r
8501 \r
8502     return;\r
8503 #endif /* !WIN32 */\r
8504 }\r
8505 \r
8506 char *\r
8507 CmailMsg()\r
8508 {\r
8509 #if WIN32\r
8510     return NULL;\r
8511 #else\r
8512     int  prependComma = 0;\r
8513     char number[5];\r
8514     char string[MSG_SIZ];       /* Space for game-list */\r
8515     int  i;\r
8516     \r
8517     if (!cmailMsgLoaded) return "";\r
8518 \r
8519     if (cmailMailedMove) {\r
8520         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
8521     } else {\r
8522         /* Create a list of games left */\r
8523         sprintf(string, "[");\r
8524         for (i = 0; i < nCmailGames; i ++) {\r
8525             if (! (   cmailMoveRegistered[i]\r
8526                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
8527                 if (prependComma) {\r
8528                     sprintf(number, ",%d", i + 1);\r
8529                 } else {\r
8530                     sprintf(number, "%d", i + 1);\r
8531                     prependComma = 1;\r
8532                 }\r
8533                 \r
8534                 strcat(string, number);\r
8535             }\r
8536         }\r
8537         strcat(string, "]");\r
8538 \r
8539         if (nCmailMovesRegistered + nCmailResults == 0) {\r
8540             switch (nCmailGames) {\r
8541               case 1:\r
8542                 sprintf(cmailMsg,\r
8543                         "Still need to make move for game\n");\r
8544                 break;\r
8545                 \r
8546               case 2:\r
8547                 sprintf(cmailMsg,\r
8548                         "Still need to make moves for both games\n");\r
8549                 break;\r
8550                 \r
8551               default:\r
8552                 sprintf(cmailMsg,\r
8553                         "Still need to make moves for all %d games\n",\r
8554                         nCmailGames);\r
8555                 break;\r
8556             }\r
8557         } else {\r
8558             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
8559               case 1:\r
8560                 sprintf(cmailMsg,\r
8561                         "Still need to make a move for game %s\n",\r
8562                         string);\r
8563                 break;\r
8564                 \r
8565               case 0:\r
8566                 if (nCmailResults == nCmailGames) {\r
8567                     sprintf(cmailMsg, "No unfinished games\n");\r
8568                 } else {\r
8569                     sprintf(cmailMsg, "Ready to send mail\n");\r
8570                 }\r
8571                 break;\r
8572                 \r
8573               default:\r
8574                 sprintf(cmailMsg,\r
8575                         "Still need to make moves for games %s\n",\r
8576                         string);\r
8577             }\r
8578         }\r
8579     }\r
8580     return cmailMsg;\r
8581 #endif /* WIN32 */\r
8582 }\r
8583 \r
8584 void\r
8585 ResetGameEvent()\r
8586 {\r
8587     if (gameMode == Training)\r
8588       SetTrainingModeOff();\r
8589 \r
8590     Reset(TRUE, TRUE);\r
8591     cmailMsgLoaded = FALSE;\r
8592     if (appData.icsActive) {\r
8593       SendToICS(ics_prefix);\r
8594       SendToICS("refresh\n");\r
8595     }\r
8596 }\r
8597 \r
8598 static int exiting = 0;\r
8599 \r
8600 void\r
8601 ExitEvent(status)\r
8602      int status;\r
8603 {\r
8604     exiting++;\r
8605     if (exiting > 2) {\r
8606       /* Give up on clean exit */\r
8607       exit(status);\r
8608     }\r
8609     if (exiting > 1) {\r
8610       /* Keep trying for clean exit */\r
8611       return;\r
8612     }\r
8613 \r
8614     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
8615 \r
8616     if (telnetISR != NULL) {\r
8617       RemoveInputSource(telnetISR);\r
8618     }\r
8619     if (icsPR != NoProc) {\r
8620       DestroyChildProcess(icsPR, TRUE);\r
8621     }\r
8622     /* Save game if resource set and not already saved by GameEnds() */\r
8623     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {\r
8624       if (*appData.saveGameFile != NULLCHAR) {\r
8625         SaveGameToFile(appData.saveGameFile, TRUE);\r
8626       } else if (appData.autoSaveGames) {\r
8627         AutoSaveGame();\r
8628       }\r
8629       if (*appData.savePositionFile != NULLCHAR) {\r
8630         SavePositionToFile(appData.savePositionFile);\r
8631       }\r
8632     }\r
8633     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8634 \r
8635     /* Kill off chess programs */\r
8636     if (first.pr != NoProc) {\r
8637         ExitAnalyzeMode();\r
8638         \r
8639         DoSleep( appData.delayBeforeQuit );\r
8640         SendToProgram("quit\n", &first);\r
8641         DoSleep( appData.delayAfterQuit );\r
8642         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
8643     }\r
8644     if (second.pr != NoProc) {\r
8645         DoSleep( appData.delayBeforeQuit );\r
8646         SendToProgram("quit\n", &second);\r
8647         DoSleep( appData.delayAfterQuit );\r
8648         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
8649     }\r
8650     if (first.isr != NULL) {\r
8651         RemoveInputSource(first.isr);\r
8652     }\r
8653     if (second.isr != NULL) {\r
8654         RemoveInputSource(second.isr);\r
8655     }\r
8656 \r
8657     ShutDownFrontEnd();\r
8658     exit(status);\r
8659 }\r
8660 \r
8661 void\r
8662 PauseEvent()\r
8663 {\r
8664     if (appData.debugMode)\r
8665         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
8666     if (pausing) {\r
8667         pausing = FALSE;\r
8668         ModeHighlight();\r
8669         if (gameMode == MachinePlaysWhite ||\r
8670             gameMode == MachinePlaysBlack) {\r
8671             StartClocks();\r
8672         } else {\r
8673             DisplayBothClocks();\r
8674         }\r
8675         if (gameMode == PlayFromGameFile) {\r
8676             if (appData.timeDelay >= 0) \r
8677                 AutoPlayGameLoop();\r
8678         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
8679             Reset(FALSE, TRUE);\r
8680             SendToICS(ics_prefix);\r
8681             SendToICS("refresh\n");\r
8682         } else if (currentMove < forwardMostMove) {\r
8683             ForwardInner(forwardMostMove);\r
8684         }\r
8685         pauseExamInvalid = FALSE;\r
8686     } else {\r
8687         switch (gameMode) {\r
8688           default:\r
8689             return;\r
8690           case IcsExamining:\r
8691             pauseExamForwardMostMove = forwardMostMove;\r
8692             pauseExamInvalid = FALSE;\r
8693             /* fall through */\r
8694           case IcsObserving:\r
8695           case IcsPlayingWhite:\r
8696           case IcsPlayingBlack:\r
8697             pausing = TRUE;\r
8698             ModeHighlight();\r
8699             return;\r
8700           case PlayFromGameFile:\r
8701             (void) StopLoadGameTimer();\r
8702             pausing = TRUE;\r
8703             ModeHighlight();\r
8704             break;\r
8705           case BeginningOfGame:\r
8706             if (appData.icsActive) return;\r
8707             /* else fall through */\r
8708           case MachinePlaysWhite:\r
8709           case MachinePlaysBlack:\r
8710           case TwoMachinesPlay:\r
8711             if (forwardMostMove == 0)\r
8712               return;           /* don't pause if no one has moved */\r
8713             if ((gameMode == MachinePlaysWhite &&\r
8714                  !WhiteOnMove(forwardMostMove)) ||\r
8715                 (gameMode == MachinePlaysBlack &&\r
8716                  WhiteOnMove(forwardMostMove))) {\r
8717                 StopClocks();\r
8718             }\r
8719             pausing = TRUE;\r
8720             ModeHighlight();\r
8721             break;\r
8722         }\r
8723     }\r
8724 }\r
8725 \r
8726 void\r
8727 EditCommentEvent()\r
8728 {\r
8729     char title[MSG_SIZ];\r
8730 \r
8731     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
8732         strcpy(title, "Edit comment");\r
8733     } else {\r
8734         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
8735                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
8736                 parseList[currentMove - 1]);\r
8737     }\r
8738 \r
8739     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
8740 }\r
8741 \r
8742 \r
8743 void\r
8744 EditTagsEvent()\r
8745 {\r
8746     char *tags = PGNTags(&gameInfo);\r
8747     EditTagsPopUp(tags);\r
8748     free(tags);\r
8749 }\r
8750 \r
8751 void\r
8752 AnalyzeModeEvent()\r
8753 {\r
8754     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
8755       return;\r
8756 \r
8757     if (gameMode != AnalyzeFile) {\r
8758         EditGameEvent();\r
8759         if (gameMode != EditGame) return;\r
8760         ResurrectChessProgram();\r
8761         SendToProgram("analyze\n", &first);\r
8762         first.analyzing = TRUE;\r
8763         /*first.maybeThinking = TRUE;*/\r
8764         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8765         AnalysisPopUp("Analysis",\r
8766                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8767     }\r
8768     gameMode = AnalyzeMode;\r
8769     pausing = FALSE;\r
8770     ModeHighlight();\r
8771     SetGameInfo();\r
8772 \r
8773     StartAnalysisClock();\r
8774     GetTimeMark(&lastNodeCountTime);\r
8775     lastNodeCount = 0;\r
8776 }\r
8777 \r
8778 void\r
8779 AnalyzeFileEvent()\r
8780 {\r
8781     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
8782       return;\r
8783 \r
8784     if (gameMode != AnalyzeMode) {\r
8785         EditGameEvent();\r
8786         if (gameMode != EditGame) return;\r
8787         ResurrectChessProgram();\r
8788         SendToProgram("analyze\n", &first);\r
8789         first.analyzing = TRUE;\r
8790         /*first.maybeThinking = TRUE;*/\r
8791         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8792         AnalysisPopUp("Analysis",\r
8793                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8794     }\r
8795     gameMode = AnalyzeFile;\r
8796     pausing = FALSE;\r
8797     ModeHighlight();\r
8798     SetGameInfo();\r
8799 \r
8800     StartAnalysisClock();\r
8801     GetTimeMark(&lastNodeCountTime);\r
8802     lastNodeCount = 0;\r
8803 }\r
8804 \r
8805 void\r
8806 MachineWhiteEvent()\r
8807 {\r
8808     char buf[MSG_SIZ];\r
8809 \r
8810     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
8811       return;\r
8812 \r
8813 \r
8814     if (gameMode == PlayFromGameFile || \r
8815         gameMode == TwoMachinesPlay  || \r
8816         gameMode == Training         || \r
8817         gameMode == AnalyzeMode      || \r
8818         gameMode == EndOfGame)\r
8819         EditGameEvent();\r
8820 \r
8821     if (gameMode == EditPosition) \r
8822         EditPositionDone();\r
8823 \r
8824     if (!WhiteOnMove(currentMove)) {\r
8825         DisplayError("It is not White's turn", 0);\r
8826         return;\r
8827     }\r
8828   \r
8829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
8830       ExitAnalyzeMode();\r
8831 \r
8832     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
8833         gameMode == AnalyzeFile)\r
8834         TruncateGame();\r
8835 \r
8836     ResurrectChessProgram();    /* in case it isn't running */\r
8837     gameMode = MachinePlaysWhite;\r
8838     pausing = FALSE;\r
8839     ModeHighlight();\r
8840     SetGameInfo();\r
8841     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8842     DisplayTitle(buf);\r
8843     if (first.sendName) {\r
8844       sprintf(buf, "name %s\n", gameInfo.black);\r
8845       SendToProgram(buf, &first);\r
8846     }\r
8847     if (first.sendTime) {\r
8848       if (first.useColors) {\r
8849         SendToProgram("black\n", &first); /*gnu kludge*/\r
8850       }\r
8851       SendTimeRemaining(&first, TRUE);\r
8852     }\r
8853     if (first.useColors) {\r
8854       SendToProgram("white\ngo\n", &first);\r
8855     } else {\r
8856       SendToProgram("go\n", &first);\r
8857     }\r
8858     SetMachineThinkingEnables();\r
8859     first.maybeThinking = TRUE;\r
8860     StartClocks();\r
8861 \r
8862     if (appData.autoFlipView && !flipView) {\r
8863       flipView = !flipView;\r
8864       DrawPosition(FALSE, NULL);\r
8865     }\r
8866 }\r
8867 \r
8868 void\r
8869 MachineBlackEvent()\r
8870 {\r
8871     char buf[MSG_SIZ];\r
8872 \r
8873     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
8874         return;\r
8875 \r
8876 \r
8877     if (gameMode == PlayFromGameFile || \r
8878         gameMode == TwoMachinesPlay  || \r
8879         gameMode == Training         || \r
8880         gameMode == AnalyzeMode      || \r
8881         gameMode == EndOfGame)\r
8882         EditGameEvent();\r
8883 \r
8884     if (gameMode == EditPosition) \r
8885         EditPositionDone();\r
8886 \r
8887     if (WhiteOnMove(currentMove)) {\r
8888         DisplayError("It is not Black's turn", 0);\r
8889         return;\r
8890     }\r
8891     \r
8892     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
8893       ExitAnalyzeMode();\r
8894 \r
8895     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
8896         gameMode == AnalyzeFile)\r
8897         TruncateGame();\r
8898 \r
8899     ResurrectChessProgram();    /* in case it isn't running */\r
8900     gameMode = MachinePlaysBlack;\r
8901     pausing = FALSE;\r
8902     ModeHighlight();\r
8903     SetGameInfo();\r
8904     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8905     DisplayTitle(buf);\r
8906     if (first.sendName) {\r
8907       sprintf(buf, "name %s\n", gameInfo.white);\r
8908       SendToProgram(buf, &first);\r
8909     }\r
8910     if (first.sendTime) {\r
8911       if (first.useColors) {\r
8912         SendToProgram("white\n", &first); /*gnu kludge*/\r
8913       }\r
8914       SendTimeRemaining(&first, FALSE);\r
8915     }\r
8916     if (first.useColors) {\r
8917       SendToProgram("black\ngo\n", &first);\r
8918     } else {\r
8919       SendToProgram("go\n", &first);\r
8920     }\r
8921     SetMachineThinkingEnables();\r
8922     first.maybeThinking = TRUE;\r
8923     StartClocks();\r
8924 \r
8925     if (appData.autoFlipView && flipView) {\r
8926       flipView = !flipView;\r
8927       DrawPosition(FALSE, NULL);\r
8928     }\r
8929 }\r
8930 \r
8931 \r
8932 void\r
8933 DisplayTwoMachinesTitle()\r
8934 {\r
8935     char buf[MSG_SIZ];\r
8936     if (appData.matchGames > 0) {\r
8937         if (first.twoMachinesColor[0] == 'w') {\r
8938             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
8939                     gameInfo.white, gameInfo.black,\r
8940                     first.matchWins, second.matchWins,\r
8941                     matchGame - 1 - (first.matchWins + second.matchWins));\r
8942         } else {\r
8943             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
8944                     gameInfo.white, gameInfo.black,\r
8945                     second.matchWins, first.matchWins,\r
8946                     matchGame - 1 - (first.matchWins + second.matchWins));\r
8947         }\r
8948     } else {\r
8949         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8950     }\r
8951     DisplayTitle(buf);\r
8952 }\r
8953 \r
8954 void\r
8955 TwoMachinesEvent P((void))\r
8956 {\r
8957     int i;\r
8958     char buf[MSG_SIZ];\r
8959     ChessProgramState *onmove;\r
8960     \r
8961     if (appData.noChessProgram) return;\r
8962 \r
8963     switch (gameMode) {\r
8964       case TwoMachinesPlay:\r
8965         return;\r
8966       case MachinePlaysWhite:\r
8967       case MachinePlaysBlack:\r
8968         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
8969             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
8970             return;\r
8971         }\r
8972         /* fall through */\r
8973       case BeginningOfGame:\r
8974       case PlayFromGameFile:\r
8975       case EndOfGame:\r
8976         EditGameEvent();\r
8977         if (gameMode != EditGame) return;\r
8978         break;\r
8979       case EditPosition:\r
8980         EditPositionDone();\r
8981         break;\r
8982       case AnalyzeMode:\r
8983       case AnalyzeFile:\r
8984         ExitAnalyzeMode();\r
8985         break;\r
8986       case EditGame:\r
8987       default:\r
8988         break;\r
8989     }\r
8990 \r
8991     forwardMostMove = currentMove;\r
8992     ResurrectChessProgram();    /* in case first program isn't running */\r
8993 \r
8994     if (second.pr == NULL) {\r
8995         StartChessProgram(&second);\r
8996         if (second.protocolVersion == 1) {\r
8997           TwoMachinesEventIfReady();\r
8998         } else {\r
8999           /* kludge: allow timeout for initial "feature" command */\r
9000           FreezeUI();\r
9001           DisplayMessage("", "Starting second chess program");\r
9002           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9003         }\r
9004         return;\r
9005     }\r
9006     DisplayMessage("", "");\r
9007     InitChessProgram(&second);\r
9008     SendToProgram("force\n", &second);\r
9009     if (startedFromSetupPosition) {\r
9010         SendBoard(&second, backwardMostMove);\r
9011     }\r
9012     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9013         SendMoveToProgram(i, &second);\r
9014     }\r
9015 \r
9016     gameMode = TwoMachinesPlay;\r
9017     pausing = FALSE;\r
9018     ModeHighlight();\r
9019     SetGameInfo();\r
9020     DisplayTwoMachinesTitle();\r
9021     firstMove = TRUE;\r
9022     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
9023         onmove = &first;\r
9024     } else {\r
9025         onmove = &second;\r
9026     }\r
9027 \r
9028     SendToProgram(first.computerString, &first);\r
9029     if (first.sendName) {\r
9030       sprintf(buf, "name %s\n", second.tidy);\r
9031       SendToProgram(buf, &first);\r
9032     }\r
9033     SendToProgram(second.computerString, &second);\r
9034     if (second.sendName) {\r
9035       sprintf(buf, "name %s\n", first.tidy);\r
9036       SendToProgram(buf, &second);\r
9037     }\r
9038 \r
9039     if (!first.sendTime || !second.sendTime) {\r
9040         ResetClocks();\r
9041         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9042         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9043     }\r
9044     if (onmove->sendTime) {\r
9045       if (onmove->useColors) {\r
9046         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
9047       }\r
9048       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
9049     }\r
9050     if (onmove->useColors) {\r
9051       SendToProgram(onmove->twoMachinesColor, onmove);\r
9052     }\r
9053     SendToProgram("go\n", onmove);\r
9054     onmove->maybeThinking = TRUE;\r
9055     SetMachineThinkingEnables();\r
9056 \r
9057     StartClocks();\r
9058 }\r
9059 \r
9060 void\r
9061 TrainingEvent()\r
9062 {\r
9063     if (gameMode == Training) {\r
9064       SetTrainingModeOff();\r
9065       gameMode = PlayFromGameFile;\r
9066       DisplayMessage("", "Training mode off");\r
9067     } else {\r
9068       gameMode = Training;\r
9069       animateTraining = appData.animate;\r
9070 \r
9071       /* make sure we are not already at the end of the game */\r
9072       if (currentMove < forwardMostMove) {\r
9073         SetTrainingModeOn();\r
9074         DisplayMessage("", "Training mode on");\r
9075       } else {\r
9076         gameMode = PlayFromGameFile;\r
9077         DisplayError("Already at end of game", 0);\r
9078       }\r
9079     }\r
9080     ModeHighlight();\r
9081 }\r
9082 \r
9083 void\r
9084 IcsClientEvent()\r
9085 {\r
9086     if (!appData.icsActive) return;\r
9087     switch (gameMode) {\r
9088       case IcsPlayingWhite:\r
9089       case IcsPlayingBlack:\r
9090       case IcsObserving:\r
9091       case IcsIdle:\r
9092       case BeginningOfGame:\r
9093       case IcsExamining:\r
9094         return;\r
9095 \r
9096       case EditGame:\r
9097         break;\r
9098 \r
9099       case EditPosition:\r
9100         EditPositionDone();\r
9101         break;\r
9102 \r
9103       case AnalyzeMode:\r
9104       case AnalyzeFile:\r
9105         ExitAnalyzeMode();\r
9106         break;\r
9107         \r
9108       default:\r
9109         EditGameEvent();\r
9110         break;\r
9111     }\r
9112 \r
9113     gameMode = IcsIdle;\r
9114     ModeHighlight();\r
9115     return;\r
9116 }\r
9117 \r
9118 \r
9119 void\r
9120 EditGameEvent()\r
9121 {\r
9122     int i;\r
9123 \r
9124     switch (gameMode) {\r
9125       case Training:\r
9126         SetTrainingModeOff();\r
9127         break;\r
9128       case MachinePlaysWhite:\r
9129       case MachinePlaysBlack:\r
9130       case BeginningOfGame:\r
9131         SendToProgram("force\n", &first);\r
9132         SetUserThinkingEnables();\r
9133         break;\r
9134       case PlayFromGameFile:\r
9135         (void) StopLoadGameTimer();\r
9136         if (gameFileFP != NULL) {\r
9137             gameFileFP = NULL;\r
9138         }\r
9139         break;\r
9140       case EditPosition:\r
9141         EditPositionDone();\r
9142         break;\r
9143       case AnalyzeMode:\r
9144       case AnalyzeFile:\r
9145         ExitAnalyzeMode();\r
9146         SendToProgram("force\n", &first);\r
9147         break;\r
9148       case TwoMachinesPlay:\r
9149         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9150         ResurrectChessProgram();\r
9151         SetUserThinkingEnables();\r
9152         break;\r
9153       case EndOfGame:\r
9154         ResurrectChessProgram();\r
9155         break;\r
9156       case IcsPlayingBlack:\r
9157       case IcsPlayingWhite:\r
9158         DisplayError("Warning: You are still playing a game", 0);\r
9159         break;\r
9160       case IcsObserving:\r
9161         DisplayError("Warning: You are still observing a game", 0);\r
9162         break;\r
9163       case IcsExamining:\r
9164         DisplayError("Warning: You are still examining a game", 0);\r
9165         break;\r
9166       case IcsIdle:\r
9167         break;\r
9168       case EditGame:\r
9169       default:\r
9170         return;\r
9171     }\r
9172     \r
9173     pausing = FALSE;\r
9174     StopClocks();\r
9175     first.offeredDraw = second.offeredDraw = 0;\r
9176 \r
9177     if (gameMode == PlayFromGameFile) {\r
9178         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9179         blackTimeRemaining = timeRemaining[1][currentMove];\r
9180         DisplayTitle("");\r
9181     }\r
9182 \r
9183     if (gameMode == MachinePlaysWhite ||\r
9184         gameMode == MachinePlaysBlack ||\r
9185         gameMode == TwoMachinesPlay ||\r
9186         gameMode == EndOfGame) {\r
9187         i = forwardMostMove;\r
9188         while (i > currentMove) {\r
9189             SendToProgram("undo\n", &first);\r
9190             i--;\r
9191         }\r
9192         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9193         blackTimeRemaining = timeRemaining[1][currentMove];\r
9194         DisplayBothClocks();\r
9195         if (whiteFlag || blackFlag) {\r
9196             whiteFlag = blackFlag = 0;\r
9197         }\r
9198         DisplayTitle("");\r
9199     }           \r
9200     \r
9201     gameMode = EditGame;\r
9202     ModeHighlight();\r
9203     SetGameInfo();\r
9204 }\r
9205 \r
9206 \r
9207 void\r
9208 EditPositionEvent()\r
9209 {\r
9210     if (gameMode == EditPosition) {\r
9211         EditGameEvent();\r
9212         return;\r
9213     }\r
9214     \r
9215     EditGameEvent();\r
9216     if (gameMode != EditGame) return;\r
9217     \r
9218     gameMode = EditPosition;\r
9219     ModeHighlight();\r
9220     SetGameInfo();\r
9221     if (currentMove > 0)\r
9222       CopyBoard(boards[0], boards[currentMove]);\r
9223     \r
9224     blackPlaysFirst = !WhiteOnMove(currentMove);\r
9225     ResetClocks();\r
9226     currentMove = forwardMostMove = backwardMostMove = 0;\r
9227     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9228     DisplayMove(-1);\r
9229 }\r
9230 \r
9231 void\r
9232 ExitAnalyzeMode()\r
9233 {\r
9234     if (first.analysisSupport && first.analyzing) {\r
9235       SendToProgram("exit\n", &first);\r
9236       first.analyzing = FALSE;\r
9237     }\r
9238     AnalysisPopDown();\r
9239     thinkOutput[0] = NULLCHAR;\r
9240 }\r
9241 \r
9242 void\r
9243 EditPositionDone()\r
9244 {\r
9245     startedFromSetupPosition = TRUE;\r
9246     InitChessProgram(&first);\r
9247     SendToProgram("force\n", &first);\r
9248     if (blackPlaysFirst) {\r
9249         strcpy(moveList[0], "");\r
9250         strcpy(parseList[0], "");\r
9251         currentMove = forwardMostMove = backwardMostMove = 1;\r
9252         CopyBoard(boards[1], boards[0]);\r
9253     } else {\r
9254         currentMove = forwardMostMove = backwardMostMove = 0;\r
9255     }\r
9256     SendBoard(&first, forwardMostMove);\r
9257     DisplayTitle("");\r
9258     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9259     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9260     gameMode = EditGame;\r
9261     ModeHighlight();\r
9262     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9263     ClearHighlights(); /* [AS] */\r
9264 }\r
9265 \r
9266 /* Pause for `ms' milliseconds */\r
9267 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9268 void\r
9269 TimeDelay(ms)\r
9270      long ms;\r
9271 {\r
9272     TimeMark m1, m2;\r
9273 \r
9274     GetTimeMark(&m1);\r
9275     do {\r
9276         GetTimeMark(&m2);\r
9277     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
9278 }\r
9279 \r
9280 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9281 void\r
9282 SendMultiLineToICS(buf)\r
9283      char *buf;\r
9284 {\r
9285     char temp[MSG_SIZ+1], *p;\r
9286     int len;\r
9287 \r
9288     len = strlen(buf);\r
9289     if (len > MSG_SIZ)\r
9290       len = MSG_SIZ;\r
9291   \r
9292     strncpy(temp, buf, len);\r
9293     temp[len] = 0;\r
9294 \r
9295     p = temp;\r
9296     while (*p) {\r
9297         if (*p == '\n' || *p == '\r')\r
9298           *p = ' ';\r
9299         ++p;\r
9300     }\r
9301 \r
9302     strcat(temp, "\n");\r
9303     SendToICS(temp);\r
9304     SendToPlayer(temp, strlen(temp));\r
9305 }\r
9306 \r
9307 void\r
9308 SetWhiteToPlayEvent()\r
9309 {\r
9310     if (gameMode == EditPosition) {\r
9311         blackPlaysFirst = FALSE;\r
9312         DisplayBothClocks();    /* works because currentMove is 0 */\r
9313     } else if (gameMode == IcsExamining) {\r
9314         SendToICS(ics_prefix);\r
9315         SendToICS("tomove white\n");\r
9316     }\r
9317 }\r
9318 \r
9319 void\r
9320 SetBlackToPlayEvent()\r
9321 {\r
9322     if (gameMode == EditPosition) {\r
9323         blackPlaysFirst = TRUE;\r
9324         currentMove = 1;        /* kludge */\r
9325         DisplayBothClocks();\r
9326         currentMove = 0;\r
9327     } else if (gameMode == IcsExamining) {\r
9328         SendToICS(ics_prefix);\r
9329         SendToICS("tomove black\n");\r
9330     }\r
9331 }\r
9332 \r
9333 void\r
9334 EditPositionMenuEvent(selection, x, y)\r
9335      ChessSquare selection;\r
9336      int x, y;\r
9337 {\r
9338     char buf[MSG_SIZ];\r
9339 \r
9340     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
9341 \r
9342     switch (selection) {\r
9343       case ClearBoard:\r
9344         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
9345             SendToICS(ics_prefix);\r
9346             SendToICS("bsetup clear\n");\r
9347         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
9348             SendToICS(ics_prefix);\r
9349             SendToICS("clearboard\n");\r
9350         } else {\r
9351             for (x = 0; x < BOARD_WIDTH; x++) {\r
9352                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
9353                     if (gameMode == IcsExamining) {\r
9354                         if (boards[currentMove][y][x] != EmptySquare) {\r
9355                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
9356                                     AAA + x, ONE + y);\r
9357                             SendToICS(buf);\r
9358                         }\r
9359                     } else {\r
9360                         boards[0][y][x] = EmptySquare;\r
9361                     }\r
9362                 }\r
9363             }\r
9364         }\r
9365         if (gameMode == EditPosition) {\r
9366             DrawPosition(FALSE, boards[0]);\r
9367         }\r
9368         break;\r
9369 \r
9370       case WhitePlay:\r
9371         SetWhiteToPlayEvent();\r
9372         break;\r
9373 \r
9374       case BlackPlay:\r
9375         SetBlackToPlayEvent();\r
9376         break;\r
9377 \r
9378       case EmptySquare:\r
9379         if (gameMode == IcsExamining) {\r
9380             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
9381             SendToICS(buf);\r
9382         } else {\r
9383             boards[0][y][x] = EmptySquare;\r
9384             DrawPosition(FALSE, boards[0]);\r
9385         }\r
9386         break;\r
9387 \r
9388       default:\r
9389         if (gameMode == IcsExamining) {\r
9390             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
9391                     PieceToChar(selection), AAA + x, ONE + y);\r
9392             SendToICS(buf);\r
9393         } else {\r
9394             boards[0][y][x] = selection;\r
9395             DrawPosition(FALSE, boards[0]);\r
9396         }\r
9397         break;\r
9398     }\r
9399 }\r
9400 \r
9401 \r
9402 void\r
9403 DropMenuEvent(selection, x, y)\r
9404      ChessSquare selection;\r
9405      int x, y;\r
9406 {\r
9407     ChessMove moveType;\r
9408 \r
9409     switch (gameMode) {\r
9410       case IcsPlayingWhite:\r
9411       case MachinePlaysBlack:\r
9412         if (!WhiteOnMove(currentMove)) {\r
9413             DisplayMoveError("It is Black's turn");\r
9414             return;\r
9415         }\r
9416         moveType = WhiteDrop;\r
9417         break;\r
9418       case IcsPlayingBlack:\r
9419       case MachinePlaysWhite:\r
9420         if (WhiteOnMove(currentMove)) {\r
9421             DisplayMoveError("It is White's turn");\r
9422             return;\r
9423         }\r
9424         moveType = BlackDrop;\r
9425         break;\r
9426       case EditGame:\r
9427         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
9428         break;\r
9429       default:\r
9430         return;\r
9431     }\r
9432 \r
9433     if (moveType == BlackDrop && selection < BlackPawn) {\r
9434       selection = (ChessSquare) ((int) selection\r
9435                                  + (int) BlackPawn - (int) WhitePawn);\r
9436     }\r
9437     if (boards[currentMove][y][x] != EmptySquare) {\r
9438         DisplayMoveError("That square is occupied");\r
9439         return;\r
9440     }\r
9441 \r
9442     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
9443 }\r
9444 \r
9445 void\r
9446 AcceptEvent()\r
9447 {\r
9448     /* Accept a pending offer of any kind from opponent */\r
9449     \r
9450     if (appData.icsActive) {\r
9451         SendToICS(ics_prefix);\r
9452         SendToICS("accept\n");\r
9453     } else if (cmailMsgLoaded) {\r
9454         if (currentMove == cmailOldMove &&\r
9455             commentList[cmailOldMove] != NULL &&\r
9456             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9457                    "Black offers a draw" : "White offers a draw")) {\r
9458             TruncateGame();\r
9459             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9460             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9461         } else {\r
9462             DisplayError("There is no pending offer on this move", 0);\r
9463             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9464         }\r
9465     } else {\r
9466         /* Not used for offers from chess program */\r
9467     }\r
9468 }\r
9469 \r
9470 void\r
9471 DeclineEvent()\r
9472 {\r
9473     /* Decline a pending offer of any kind from opponent */\r
9474     \r
9475     if (appData.icsActive) {\r
9476         SendToICS(ics_prefix);\r
9477         SendToICS("decline\n");\r
9478     } else if (cmailMsgLoaded) {\r
9479         if (currentMove == cmailOldMove &&\r
9480             commentList[cmailOldMove] != NULL &&\r
9481             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9482                    "Black offers a draw" : "White offers a draw")) {\r
9483 #ifdef NOTDEF\r
9484             AppendComment(cmailOldMove, "Draw declined");\r
9485             DisplayComment(cmailOldMove - 1, "Draw declined");\r
9486 #endif /*NOTDEF*/\r
9487         } else {\r
9488             DisplayError("There is no pending offer on this move", 0);\r
9489         }\r
9490     } else {\r
9491         /* Not used for offers from chess program */\r
9492     }\r
9493 }\r
9494 \r
9495 void\r
9496 RematchEvent()\r
9497 {\r
9498     /* Issue ICS rematch command */\r
9499     if (appData.icsActive) {\r
9500         SendToICS(ics_prefix);\r
9501         SendToICS("rematch\n");\r
9502     }\r
9503 }\r
9504 \r
9505 void\r
9506 CallFlagEvent()\r
9507 {\r
9508     /* Call your opponent's flag (claim a win on time) */\r
9509     if (appData.icsActive) {\r
9510         SendToICS(ics_prefix);\r
9511         SendToICS("flag\n");\r
9512     } else {\r
9513         switch (gameMode) {\r
9514           default:\r
9515             return;\r
9516           case MachinePlaysWhite:\r
9517             if (whiteFlag) {\r
9518                 if (blackFlag)\r
9519                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9520                            GE_PLAYER);\r
9521                 else\r
9522                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
9523             } else {\r
9524                 DisplayError("Your opponent is not out of time", 0);\r
9525             }\r
9526             break;\r
9527           case MachinePlaysBlack:\r
9528             if (blackFlag) {\r
9529                 if (whiteFlag)\r
9530                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9531                            GE_PLAYER);\r
9532                 else\r
9533                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
9534             } else {\r
9535                 DisplayError("Your opponent is not out of time", 0);\r
9536             }\r
9537             break;\r
9538         }\r
9539     }\r
9540 }\r
9541 \r
9542 void\r
9543 DrawEvent()\r
9544 {\r
9545     /* Offer draw or accept pending draw offer from opponent */\r
9546     \r
9547     if (appData.icsActive) {\r
9548         /* Note: tournament rules require draw offers to be\r
9549            made after you make your move but before you punch\r
9550            your clock.  Currently ICS doesn't let you do that;\r
9551            instead, you immediately punch your clock after making\r
9552            a move, but you can offer a draw at any time. */\r
9553         \r
9554         SendToICS(ics_prefix);\r
9555         SendToICS("draw\n");\r
9556     } else if (cmailMsgLoaded) {\r
9557         if (currentMove == cmailOldMove &&\r
9558             commentList[cmailOldMove] != NULL &&\r
9559             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9560                    "Black offers a draw" : "White offers a draw")) {\r
9561             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9562             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9563         } else if (currentMove == cmailOldMove + 1) {\r
9564             char *offer = WhiteOnMove(cmailOldMove) ?\r
9565               "White offers a draw" : "Black offers a draw";\r
9566             AppendComment(currentMove, offer);\r
9567             DisplayComment(currentMove - 1, offer);\r
9568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
9569         } else {\r
9570             DisplayError("You must make your move before offering a draw", 0);\r
9571             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9572         }\r
9573     } else if (first.offeredDraw) {\r
9574         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
9575     } else {\r
9576         if (first.sendDrawOffers) {\r
9577             SendToProgram("draw\n", &first);\r
9578             userOfferedDraw = TRUE;\r
9579         }\r
9580     }\r
9581 }\r
9582 \r
9583 void\r
9584 AdjournEvent()\r
9585 {\r
9586     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
9587     \r
9588     if (appData.icsActive) {\r
9589         SendToICS(ics_prefix);\r
9590         SendToICS("adjourn\n");\r
9591     } else {\r
9592         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
9593     }\r
9594 }\r
9595 \r
9596 \r
9597 void\r
9598 AbortEvent()\r
9599 {\r
9600     /* Offer Abort or accept pending Abort offer from opponent */\r
9601     \r
9602     if (appData.icsActive) {\r
9603         SendToICS(ics_prefix);\r
9604         SendToICS("abort\n");\r
9605     } else {\r
9606         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
9607     }\r
9608 }\r
9609 \r
9610 void\r
9611 ResignEvent()\r
9612 {\r
9613     /* Resign.  You can do this even if it's not your turn. */\r
9614     \r
9615     if (appData.icsActive) {\r
9616         SendToICS(ics_prefix);\r
9617         SendToICS("resign\n");\r
9618     } else {\r
9619         switch (gameMode) {\r
9620           case MachinePlaysWhite:\r
9621             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9622             break;\r
9623           case MachinePlaysBlack:\r
9624             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9625             break;\r
9626           case EditGame:\r
9627             if (cmailMsgLoaded) {\r
9628                 TruncateGame();\r
9629                 if (WhiteOnMove(cmailOldMove)) {\r
9630                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9631                 } else {\r
9632                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9633                 }\r
9634                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
9635             }\r
9636             break;\r
9637           default:\r
9638             break;\r
9639         }\r
9640     }\r
9641 }\r
9642 \r
9643 \r
9644 void\r
9645 StopObservingEvent()\r
9646 {\r
9647     /* Stop observing current games */\r
9648     SendToICS(ics_prefix);\r
9649     SendToICS("unobserve\n");\r
9650 }\r
9651 \r
9652 void\r
9653 StopExaminingEvent()\r
9654 {\r
9655     /* Stop observing current game */\r
9656     SendToICS(ics_prefix);\r
9657     SendToICS("unexamine\n");\r
9658 }\r
9659 \r
9660 void\r
9661 ForwardInner(target)\r
9662      int target;\r
9663 {\r
9664     int limit;\r
9665 \r
9666     if (appData.debugMode)\r
9667         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
9668                 target, currentMove, forwardMostMove);\r
9669 \r
9670     if (gameMode == EditPosition)\r
9671       return;\r
9672 \r
9673     if (gameMode == PlayFromGameFile && !pausing)\r
9674       PauseEvent();\r
9675     \r
9676     if (gameMode == IcsExamining && pausing)\r
9677       limit = pauseExamForwardMostMove;\r
9678     else\r
9679       limit = forwardMostMove;\r
9680     \r
9681     if (target > limit) target = limit;\r
9682 \r
9683     if (target > 0 && moveList[target - 1][0]) {\r
9684         int fromX, fromY, toX, toY;\r
9685         toX = moveList[target - 1][2] - AAA;\r
9686         toY = moveList[target - 1][3] - ONE;\r
9687         if (moveList[target - 1][1] == '@') {\r
9688             if (appData.highlightLastMove) {\r
9689                 SetHighlights(-1, -1, toX, toY);\r
9690             }\r
9691         } else {\r
9692             fromX = moveList[target - 1][0] - AAA;\r
9693             fromY = moveList[target - 1][1] - ONE;\r
9694             if (target == currentMove + 1) {\r
9695                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
9696             }\r
9697             if (appData.highlightLastMove) {\r
9698                 SetHighlights(fromX, fromY, toX, toY);\r
9699             }\r
9700         }\r
9701     }\r
9702     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9703         gameMode == Training || gameMode == PlayFromGameFile || \r
9704         gameMode == AnalyzeFile) {\r
9705         while (currentMove < target) {\r
9706             SendMoveToProgram(currentMove++, &first);\r
9707         }\r
9708     } else {\r
9709         currentMove = target;\r
9710     }\r
9711     \r
9712     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9713         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9714         blackTimeRemaining = timeRemaining[1][currentMove];\r
9715     }\r
9716     DisplayBothClocks();\r
9717     DisplayMove(currentMove - 1);\r
9718     DrawPosition(FALSE, boards[currentMove]);\r
9719     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9720     if (commentList[currentMove] && !matchMode && gameMode != Training) {\r
9721         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9722     }\r
9723 }\r
9724 \r
9725 \r
9726 void\r
9727 ForwardEvent()\r
9728 {\r
9729     if (gameMode == IcsExamining && !pausing) {\r
9730         SendToICS(ics_prefix);\r
9731         SendToICS("forward\n");\r
9732     } else {\r
9733         ForwardInner(currentMove + 1);\r
9734     }\r
9735 }\r
9736 \r
9737 void\r
9738 ToEndEvent()\r
9739 {\r
9740     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9741         /* to optimze, we temporarily turn off analysis mode while we feed\r
9742          * the remaining moves to the engine. Otherwise we get analysis output\r
9743          * after each move.\r
9744          */ \r
9745         if (first.analysisSupport) {\r
9746           SendToProgram("exit\nforce\n", &first);\r
9747           first.analyzing = FALSE;\r
9748         }\r
9749     }\r
9750         \r
9751     if (gameMode == IcsExamining && !pausing) {\r
9752         SendToICS(ics_prefix);\r
9753         SendToICS("forward 999999\n");\r
9754     } else {\r
9755         ForwardInner(forwardMostMove);\r
9756     }\r
9757 \r
9758     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9759         /* we have fed all the moves, so reactivate analysis mode */\r
9760         SendToProgram("analyze\n", &first);\r
9761         first.analyzing = TRUE;\r
9762         /*first.maybeThinking = TRUE;*/\r
9763         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9764     }\r
9765 }\r
9766 \r
9767 void\r
9768 BackwardInner(target)\r
9769      int target;\r
9770 {\r
9771     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
9772 \r
9773     if (appData.debugMode)\r
9774         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
9775                 target, currentMove, forwardMostMove);\r
9776 \r
9777     if (gameMode == EditPosition) return;\r
9778     if (currentMove <= backwardMostMove) {\r
9779         ClearHighlights();\r
9780         DrawPosition(full_redraw, boards[currentMove]);\r
9781         return;\r
9782     }\r
9783     if (gameMode == PlayFromGameFile && !pausing)\r
9784       PauseEvent();\r
9785     \r
9786     if (moveList[target][0]) {\r
9787         int fromX, fromY, toX, toY;\r
9788         toX = moveList[target][2] - AAA;\r
9789         toY = moveList[target][3] - ONE;\r
9790         if (moveList[target][1] == '@') {\r
9791             if (appData.highlightLastMove) {\r
9792                 SetHighlights(-1, -1, toX, toY);\r
9793             }\r
9794         } else {\r
9795             fromX = moveList[target][0] - AAA;\r
9796             fromY = moveList[target][1] - ONE;\r
9797             if (target == currentMove - 1) {\r
9798                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
9799             }\r
9800             if (appData.highlightLastMove) {\r
9801                 SetHighlights(fromX, fromY, toX, toY);\r
9802             }\r
9803         }\r
9804     }\r
9805     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
9806         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
9807         while (currentMove > target) {\r
9808             SendToProgram("undo\n", &first);\r
9809             currentMove--;\r
9810         }\r
9811     } else {\r
9812         currentMove = target;\r
9813     }\r
9814     \r
9815     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9816         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9817         blackTimeRemaining = timeRemaining[1][currentMove];\r
9818     }\r
9819     DisplayBothClocks();\r
9820     DisplayMove(currentMove - 1);\r
9821     DrawPosition(full_redraw, boards[currentMove]);\r
9822     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9823     if (commentList[currentMove] != NULL) {\r
9824         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9825     }\r
9826 }\r
9827 \r
9828 void\r
9829 BackwardEvent()\r
9830 {\r
9831     if (gameMode == IcsExamining && !pausing) {\r
9832         SendToICS(ics_prefix);\r
9833         SendToICS("backward\n");\r
9834     } else {\r
9835         BackwardInner(currentMove - 1);\r
9836     }\r
9837 }\r
9838 \r
9839 void\r
9840 ToStartEvent()\r
9841 {\r
9842     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9843         /* to optimze, we temporarily turn off analysis mode while we undo\r
9844          * all the moves. Otherwise we get analysis output after each undo.\r
9845          */ \r
9846         if (first.analysisSupport) {\r
9847           SendToProgram("exit\nforce\n", &first);\r
9848           first.analyzing = FALSE;\r
9849         }\r
9850     }\r
9851 \r
9852     if (gameMode == IcsExamining && !pausing) {\r
9853         SendToICS(ics_prefix);\r
9854         SendToICS("backward 999999\n");\r
9855     } else {\r
9856         BackwardInner(backwardMostMove);\r
9857     }\r
9858 \r
9859     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9860         /* we have fed all the moves, so reactivate analysis mode */\r
9861         SendToProgram("analyze\n", &first);\r
9862         first.analyzing = TRUE;\r
9863         /*first.maybeThinking = TRUE;*/\r
9864         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9865     }\r
9866 }\r
9867 \r
9868 void\r
9869 ToNrEvent(int to)\r
9870 {\r
9871   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
9872   if (to >= forwardMostMove) to = forwardMostMove;\r
9873   if (to <= backwardMostMove) to = backwardMostMove;\r
9874   if (to < currentMove) {\r
9875     BackwardInner(to);\r
9876   } else {\r
9877     ForwardInner(to);\r
9878   }\r
9879 }\r
9880 \r
9881 void\r
9882 RevertEvent()\r
9883 {\r
9884     if (gameMode != IcsExamining) {\r
9885         DisplayError("You are not examining a game", 0);\r
9886         return;\r
9887     }\r
9888     if (pausing) {\r
9889         DisplayError("You can't revert while pausing", 0);\r
9890         return;\r
9891     }\r
9892     SendToICS(ics_prefix);\r
9893     SendToICS("revert\n");\r
9894 }\r
9895 \r
9896 void\r
9897 RetractMoveEvent()\r
9898 {\r
9899     switch (gameMode) {\r
9900       case MachinePlaysWhite:\r
9901       case MachinePlaysBlack:\r
9902         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9903             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9904             return;\r
9905         }\r
9906         if (forwardMostMove < 2) return;\r
9907         currentMove = forwardMostMove = forwardMostMove - 2;\r
9908         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9909         blackTimeRemaining = timeRemaining[1][currentMove];\r
9910         DisplayBothClocks();\r
9911         DisplayMove(currentMove - 1);\r
9912         ClearHighlights();/*!! could figure this out*/\r
9913         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
9914         SendToProgram("remove\n", &first);\r
9915         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
9916         break;\r
9917 \r
9918       case BeginningOfGame:\r
9919       default:\r
9920         break;\r
9921 \r
9922       case IcsPlayingWhite:\r
9923       case IcsPlayingBlack:\r
9924         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
9925             SendToICS(ics_prefix);\r
9926             SendToICS("takeback 2\n");\r
9927         } else {\r
9928             SendToICS(ics_prefix);\r
9929             SendToICS("takeback 1\n");\r
9930         }\r
9931         break;\r
9932     }\r
9933 }\r
9934 \r
9935 void\r
9936 MoveNowEvent()\r
9937 {\r
9938     ChessProgramState *cps;\r
9939 \r
9940     switch (gameMode) {\r
9941       case MachinePlaysWhite:\r
9942         if (!WhiteOnMove(forwardMostMove)) {\r
9943             DisplayError("It is your turn", 0);\r
9944             return;\r
9945         }\r
9946         cps = &first;\r
9947         break;\r
9948       case MachinePlaysBlack:\r
9949         if (WhiteOnMove(forwardMostMove)) {\r
9950             DisplayError("It is your turn", 0);\r
9951             return;\r
9952         }\r
9953         cps = &first;\r
9954         break;\r
9955       case TwoMachinesPlay:\r
9956         if (WhiteOnMove(forwardMostMove) ==\r
9957             (first.twoMachinesColor[0] == 'w')) {\r
9958             cps = &first;\r
9959         } else {\r
9960             cps = &second;\r
9961         }\r
9962         break;\r
9963       case BeginningOfGame:\r
9964       default:\r
9965         return;\r
9966     }\r
9967     SendToProgram("?\n", cps);\r
9968 }\r
9969 \r
9970 void\r
9971 TruncateGameEvent()\r
9972 {\r
9973     EditGameEvent();\r
9974     if (gameMode != EditGame) return;\r
9975     TruncateGame();\r
9976 }\r
9977 \r
9978 void\r
9979 TruncateGame()\r
9980 {\r
9981     if (forwardMostMove > currentMove) {\r
9982         if (gameInfo.resultDetails != NULL) {\r
9983             free(gameInfo.resultDetails);\r
9984             gameInfo.resultDetails = NULL;\r
9985             gameInfo.result = GameUnfinished;\r
9986         }\r
9987         forwardMostMove = currentMove;\r
9988         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
9989                    currentMove-1);\r
9990     }\r
9991 }\r
9992 \r
9993 void\r
9994 HintEvent()\r
9995 {\r
9996     if (appData.noChessProgram) return;\r
9997     switch (gameMode) {\r
9998       case MachinePlaysWhite:\r
9999         if (WhiteOnMove(forwardMostMove)) {\r
10000             DisplayError("Wait until your turn", 0);\r
10001             return;\r
10002         }\r
10003         break;\r
10004       case BeginningOfGame:\r
10005       case MachinePlaysBlack:\r
10006         if (!WhiteOnMove(forwardMostMove)) {\r
10007             DisplayError("Wait until your turn", 0);\r
10008             return;\r
10009         }\r
10010         break;\r
10011       default:\r
10012         DisplayError("No hint available", 0);\r
10013         return;\r
10014     }\r
10015     SendToProgram("hint\n", &first);\r
10016     hintRequested = TRUE;\r
10017 }\r
10018 \r
10019 void\r
10020 BookEvent()\r
10021 {\r
10022     if (appData.noChessProgram) return;\r
10023     switch (gameMode) {\r
10024       case MachinePlaysWhite:\r
10025         if (WhiteOnMove(forwardMostMove)) {\r
10026             DisplayError("Wait until your turn", 0);\r
10027             return;\r
10028         }\r
10029         break;\r
10030       case BeginningOfGame:\r
10031       case MachinePlaysBlack:\r
10032         if (!WhiteOnMove(forwardMostMove)) {\r
10033             DisplayError("Wait until your turn", 0);\r
10034             return;\r
10035         }\r
10036         break;\r
10037       case EditPosition:\r
10038         EditPositionDone();\r
10039         break;\r
10040       case TwoMachinesPlay:\r
10041         return;\r
10042       default:\r
10043         break;\r
10044     }\r
10045     SendToProgram("bk\n", &first);\r
10046     bookOutput[0] = NULLCHAR;\r
10047     bookRequested = TRUE;\r
10048 }\r
10049 \r
10050 void\r
10051 AboutGameEvent()\r
10052 {\r
10053     char *tags = PGNTags(&gameInfo);\r
10054     TagsPopUp(tags, CmailMsg());\r
10055     free(tags);\r
10056 }\r
10057 \r
10058 /* end button procedures */\r
10059 \r
10060 void\r
10061 PrintPosition(fp, move)\r
10062      FILE *fp;\r
10063      int move;\r
10064 {\r
10065     int i, j;\r
10066     \r
10067     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
10068         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
10069             char c = PieceToChar(boards[move][i][j]);\r
10070             fputc(c == 'x' ? '.' : c, fp);\r
10071             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
10072         }\r
10073     }\r
10074     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
10075       fprintf(fp, "white to play\n");\r
10076     else\r
10077       fprintf(fp, "black to play\n");\r
10078 }\r
10079 \r
10080 void\r
10081 PrintOpponents(fp)\r
10082      FILE *fp;\r
10083 {\r
10084     if (gameInfo.white != NULL) {\r
10085         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
10086     } else {\r
10087         fprintf(fp, "\n");\r
10088     }\r
10089 }\r
10090 \r
10091 /* Find last component of program's own name, using some heuristics */\r
10092 void\r
10093 TidyProgramName(prog, host, buf)\r
10094      char *prog, *host, buf[MSG_SIZ];\r
10095 {\r
10096     char *p, *q;\r
10097     int local = (strcmp(host, "localhost") == 0);\r
10098     while (!local && (p = strchr(prog, ';')) != NULL) {\r
10099         p++;\r
10100         while (*p == ' ') p++;\r
10101         prog = p;\r
10102     }\r
10103     if (*prog == '"' || *prog == '\'') {\r
10104         q = strchr(prog + 1, *prog);\r
10105     } else {\r
10106         q = strchr(prog, ' ');\r
10107     }\r
10108     if (q == NULL) q = prog + strlen(prog);\r
10109     p = q;\r
10110     while (p >= prog && *p != '/' && *p != '\\') p--;\r
10111     p++;\r
10112     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
10113     memcpy(buf, p, q - p);\r
10114     buf[q - p] = NULLCHAR;\r
10115     if (!local) {\r
10116         strcat(buf, "@");\r
10117         strcat(buf, host);\r
10118     }\r
10119 }\r
10120 \r
10121 char *\r
10122 TimeControlTagValue()\r
10123 {\r
10124     char buf[MSG_SIZ];\r
10125     if (!appData.clockMode) {\r
10126         strcpy(buf, "-");\r
10127     } else if (movesPerSession > 0) {\r
10128         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
10129     } else if (timeIncrement == 0) {\r
10130         sprintf(buf, "%ld", timeControl/1000);\r
10131     } else {\r
10132         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
10133     }\r
10134     return StrSave(buf);\r
10135 }\r
10136 \r
10137 void\r
10138 SetGameInfo()\r
10139 {\r
10140     /* This routine is used only for certain modes */\r
10141     VariantClass v = gameInfo.variant;\r
10142     ClearGameInfo(&gameInfo);\r
10143     gameInfo.variant = v;\r
10144 \r
10145     switch (gameMode) {\r
10146       case MachinePlaysWhite:\r
10147         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10148         gameInfo.site = StrSave(HostName());\r
10149         gameInfo.date = PGNDate();\r
10150         gameInfo.round = StrSave("-");\r
10151         gameInfo.white = StrSave(first.tidy);\r
10152         gameInfo.black = StrSave(UserName());\r
10153         gameInfo.timeControl = TimeControlTagValue();\r
10154         break;\r
10155 \r
10156       case MachinePlaysBlack:\r
10157         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10158         gameInfo.site = StrSave(HostName());\r
10159         gameInfo.date = PGNDate();\r
10160         gameInfo.round = StrSave("-");\r
10161         gameInfo.white = StrSave(UserName());\r
10162         gameInfo.black = StrSave(first.tidy);\r
10163         gameInfo.timeControl = TimeControlTagValue();\r
10164         break;\r
10165 \r
10166       case TwoMachinesPlay:\r
10167         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10168         gameInfo.site = StrSave(HostName());\r
10169         gameInfo.date = PGNDate();\r
10170         if (matchGame > 0) {\r
10171             char buf[MSG_SIZ];\r
10172             sprintf(buf, "%d", matchGame);\r
10173             gameInfo.round = StrSave(buf);\r
10174         } else {\r
10175             gameInfo.round = StrSave("-");\r
10176         }\r
10177         if (first.twoMachinesColor[0] == 'w') {\r
10178             gameInfo.white = StrSave(first.tidy);\r
10179             gameInfo.black = StrSave(second.tidy);\r
10180         } else {\r
10181             gameInfo.white = StrSave(second.tidy);\r
10182             gameInfo.black = StrSave(first.tidy);\r
10183         }\r
10184         gameInfo.timeControl = TimeControlTagValue();\r
10185         break;\r
10186 \r
10187       case EditGame:\r
10188         gameInfo.event = StrSave("Edited game");\r
10189         gameInfo.site = StrSave(HostName());\r
10190         gameInfo.date = PGNDate();\r
10191         gameInfo.round = StrSave("-");\r
10192         gameInfo.white = StrSave("-");\r
10193         gameInfo.black = StrSave("-");\r
10194         break;\r
10195 \r
10196       case EditPosition:\r
10197         gameInfo.event = StrSave("Edited position");\r
10198         gameInfo.site = StrSave(HostName());\r
10199         gameInfo.date = PGNDate();\r
10200         gameInfo.round = StrSave("-");\r
10201         gameInfo.white = StrSave("-");\r
10202         gameInfo.black = StrSave("-");\r
10203         break;\r
10204 \r
10205       case IcsPlayingWhite:\r
10206       case IcsPlayingBlack:\r
10207       case IcsObserving:\r
10208       case IcsExamining:\r
10209         break;\r
10210 \r
10211       case PlayFromGameFile:\r
10212         gameInfo.event = StrSave("Game from non-PGN file");\r
10213         gameInfo.site = StrSave(HostName());\r
10214         gameInfo.date = PGNDate();\r
10215         gameInfo.round = StrSave("-");\r
10216         gameInfo.white = StrSave("?");\r
10217         gameInfo.black = StrSave("?");\r
10218         break;\r
10219 \r
10220       default:\r
10221         break;\r
10222     }\r
10223 }\r
10224 \r
10225 void\r
10226 ReplaceComment(index, text)\r
10227      int index;\r
10228      char *text;\r
10229 {\r
10230     int len;\r
10231 \r
10232     while (*text == '\n') text++;\r
10233     len = strlen(text);\r
10234     while (len > 0 && text[len - 1] == '\n') len--;\r
10235 \r
10236     if (commentList[index] != NULL)\r
10237       free(commentList[index]);\r
10238 \r
10239     if (len == 0) {\r
10240         commentList[index] = NULL;\r
10241         return;\r
10242     }\r
10243     commentList[index] = (char *) malloc(len + 2);\r
10244     strncpy(commentList[index], text, len);\r
10245     commentList[index][len] = '\n';\r
10246     commentList[index][len + 1] = NULLCHAR;\r
10247 }\r
10248 \r
10249 void\r
10250 CrushCRs(text)\r
10251      char *text;\r
10252 {\r
10253   char *p = text;\r
10254   char *q = text;\r
10255   char ch;\r
10256 \r
10257   do {\r
10258     ch = *p++;\r
10259     if (ch == '\r') continue;\r
10260     *q++ = ch;\r
10261   } while (ch != '\0');\r
10262 }\r
10263 \r
10264 void\r
10265 AppendComment(index, text)\r
10266      int index;\r
10267      char *text;\r
10268 {\r
10269     int oldlen, len;\r
10270     char *old;\r
10271 \r
10272     GetInfoFromComment( index, text );\r
10273 \r
10274     CrushCRs(text);\r
10275     while (*text == '\n') text++;\r
10276     len = strlen(text);\r
10277     while (len > 0 && text[len - 1] == '\n') len--;\r
10278 \r
10279     if (len == 0) return;\r
10280 \r
10281     if (commentList[index] != NULL) {\r
10282         old = commentList[index];\r
10283         oldlen = strlen(old);\r
10284         commentList[index] = (char *) malloc(oldlen + len + 2);\r
10285         strcpy(commentList[index], old);\r
10286         free(old);\r
10287         strncpy(&commentList[index][oldlen], text, len);\r
10288         commentList[index][oldlen + len] = '\n';\r
10289         commentList[index][oldlen + len + 1] = NULLCHAR;\r
10290     } else {\r
10291         commentList[index] = (char *) malloc(len + 2);\r
10292         strncpy(commentList[index], text, len);\r
10293         commentList[index][len] = '\n';\r
10294         commentList[index][len + 1] = NULLCHAR;\r
10295     }\r
10296 }\r
10297 \r
10298 static char * FindStr( char * text, char * sub_text )\r
10299 {\r
10300     char * result = strstr( text, sub_text );\r
10301 \r
10302     if( result != NULL ) {\r
10303         result += strlen( sub_text );\r
10304     }\r
10305 \r
10306     return result;\r
10307 }\r
10308 \r
10309 /* [AS] Try to extract PV info from PGN comment */\r
10310 void GetInfoFromComment( int index, char * text )\r
10311 {\r
10312     if( text != NULL && index > 0 ) {\r
10313         int score = 0;\r
10314         int depth = 0;\r
10315         int time = -1;\r
10316         char * s_eval = FindStr( text, "[%eval " );\r
10317         char * s_emt = FindStr( text, "[%emt " );\r
10318 \r
10319         if( s_eval != NULL || s_emt != NULL ) {\r
10320             /* New style */\r
10321             char delim;\r
10322 \r
10323             if( s_eval != NULL ) {\r
10324                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
10325                     return;\r
10326                 }\r
10327 \r
10328                 if( delim != ']' ) {\r
10329                     return;\r
10330                 }\r
10331             }\r
10332 \r
10333             if( s_emt != NULL ) {\r
10334             }\r
10335         }\r
10336         else {\r
10337             /* We expect something like: [+|-]nnn.nn/dd */\r
10338             char * sep = strchr( text, '/' );\r
10339             int score_lo = 0;\r
10340 \r
10341             if( sep == NULL || sep < (text+4) ) {\r
10342                 return;\r
10343             }\r
10344 \r
10345             if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
10346                 return;\r
10347             }\r
10348 \r
10349             if( score_lo < 0 || score_lo >= 100 ) {\r
10350                 return;\r
10351             }\r
10352 \r
10353             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
10354         }\r
10355 \r
10356         if( depth <= 0 ) {\r
10357             return;\r
10358         }\r
10359 \r
10360         if( time < 0 ) {\r
10361             time = -1;\r
10362         }\r
10363 \r
10364         pvInfoList[index-1].depth = depth;\r
10365         pvInfoList[index-1].score = score;\r
10366         pvInfoList[index-1].time = time;\r
10367     }\r
10368 }\r
10369 \r
10370 void\r
10371 SendToProgram(message, cps)\r
10372      char *message;\r
10373      ChessProgramState *cps;\r
10374 {\r
10375     int count, outCount, error;\r
10376     char buf[MSG_SIZ];\r
10377 \r
10378     if (cps->pr == NULL) return;\r
10379     Attention(cps);\r
10380     \r
10381     if (appData.debugMode) {\r
10382         TimeMark now;\r
10383         GetTimeMark(&now);\r
10384         fprintf(debugFP, "%ld >%-6s: %s", \r
10385                 SubtractTimeMarks(&now, &programStartTime),\r
10386                 cps->which, message);\r
10387     }\r
10388     \r
10389     count = strlen(message);\r
10390     outCount = OutputToProcess(cps->pr, message, count, &error);\r
10391     if (outCount < count && !exiting) {\r
10392         sprintf(buf, "Error writing to %s chess program", cps->which);\r
10393         DisplayFatalError(buf, error, 1);\r
10394     }\r
10395 }\r
10396 \r
10397 void\r
10398 ReceiveFromProgram(isr, closure, message, count, error)\r
10399      InputSourceRef isr;\r
10400      VOIDSTAR closure;\r
10401      char *message;\r
10402      int count;\r
10403      int error;\r
10404 {\r
10405     char *end_str;\r
10406     char buf[MSG_SIZ];\r
10407     ChessProgramState *cps = (ChessProgramState *)closure;\r
10408 \r
10409     if (isr != cps->isr) return; /* Killed intentionally */\r
10410     if (count <= 0) {\r
10411         if (count == 0) {\r
10412             sprintf(buf,\r
10413                     "Error: %s chess program (%s) exited unexpectedly",\r
10414                     cps->which, cps->program);\r
10415             RemoveInputSource(cps->isr);\r
10416             DisplayFatalError(buf, 0, 1);\r
10417         } else {\r
10418             sprintf(buf,\r
10419                     "Error reading from %s chess program (%s)",\r
10420                     cps->which, cps->program);\r
10421             RemoveInputSource(cps->isr);\r
10422 \r
10423             /* [AS] Program is misbehaving badly... kill it */\r
10424             if( count == -2 ) {\r
10425                 DestroyChildProcess( cps->pr, 9 );\r
10426                 cps->pr = NoProc;\r
10427             }\r
10428 \r
10429             DisplayFatalError(buf, error, 1);\r
10430         }\r
10431         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10432         return;\r
10433     }\r
10434     \r
10435     if ((end_str = strchr(message, '\r')) != NULL)\r
10436       *end_str = NULLCHAR;\r
10437     if ((end_str = strchr(message, '\n')) != NULL)\r
10438       *end_str = NULLCHAR;\r
10439     \r
10440     if (appData.debugMode) {\r
10441         TimeMark now;\r
10442         GetTimeMark(&now);\r
10443         fprintf(debugFP, "%ld <%-6s: %s\n", \r
10444                 SubtractTimeMarks(&now, &programStartTime),\r
10445                 cps->which, message);\r
10446     }\r
10447     HandleMachineMove(message, cps);\r
10448 }\r
10449 \r
10450 \r
10451 void\r
10452 SendTimeControl(cps, mps, tc, inc, sd, st)\r
10453      ChessProgramState *cps;\r
10454      int mps, inc, sd, st;\r
10455      long tc;\r
10456 {\r
10457     char buf[MSG_SIZ];\r
10458     int seconds = (tc / 1000) % 60;\r
10459 \r
10460     if( timeControl_2 > 0 ) {\r
10461         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
10462             tc = timeControl_2;\r
10463         }\r
10464     }\r
10465 \r
10466     if (st > 0) {\r
10467       /* Set exact time per move, normally using st command */\r
10468       if (cps->stKludge) {\r
10469         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
10470         seconds = st % 60;\r
10471         if (seconds == 0) {\r
10472           sprintf(buf, "level 1 %d\n", st/60);\r
10473         } else {\r
10474           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
10475         }\r
10476       } else {\r
10477         sprintf(buf, "st %d\n", st);\r
10478       }\r
10479     } else {\r
10480       /* Set conventional or incremental time control, using level command */\r
10481       if (seconds == 0) {\r
10482         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
10483            Fixed in later versions, but still avoid :seconds\r
10484            when seconds is 0. */\r
10485         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
10486       } else {\r
10487         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
10488                 seconds, inc/1000);\r
10489       }\r
10490     }\r
10491     SendToProgram(buf, cps);\r
10492 \r
10493     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
10494     /* Orthogonally, limit search to given depth */\r
10495     if (sd > 0) {\r
10496       if (cps->sdKludge) {\r
10497         sprintf(buf, "depth\n%d\n", sd);\r
10498       } else {\r
10499         sprintf(buf, "sd %d\n", sd);\r
10500       }\r
10501       SendToProgram(buf, cps);\r
10502     }\r
10503 }\r
10504 \r
10505 void\r
10506 SendTimeRemaining(cps, machineWhite)\r
10507      ChessProgramState *cps;\r
10508      int /*boolean*/ machineWhite;\r
10509 {\r
10510     char message[MSG_SIZ];\r
10511     long time, otime;\r
10512 \r
10513     /* Note: this routine must be called when the clocks are stopped\r
10514        or when they have *just* been set or switched; otherwise\r
10515        it will be off by the time since the current tick started.\r
10516     */\r
10517     if (machineWhite) {\r
10518         time = whiteTimeRemaining / 10;\r
10519         otime = blackTimeRemaining / 10;\r
10520     } else {\r
10521         time = blackTimeRemaining / 10;\r
10522         otime = whiteTimeRemaining / 10;\r
10523     }\r
10524     if (time <= 0) time = 1;\r
10525     if (otime <= 0) otime = 1;\r
10526     \r
10527     sprintf(message, "time %ld\n", time);\r
10528     SendToProgram(message, cps);\r
10529 \r
10530     sprintf(message, "otim %ld\n", otime);\r
10531     SendToProgram(message, cps);\r
10532 }\r
10533 \r
10534 int\r
10535 BoolFeature(p, name, loc, cps)\r
10536      char **p;\r
10537      char *name;\r
10538      int *loc;\r
10539      ChessProgramState *cps;\r
10540 {\r
10541   char buf[MSG_SIZ];\r
10542   int len = strlen(name);\r
10543   int val;\r
10544   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10545     (*p) += len + 1;\r
10546     sscanf(*p, "%d", &val);\r
10547     *loc = (val != 0);\r
10548     while (**p && **p != ' ') (*p)++;\r
10549     sprintf(buf, "accepted %s\n", name);\r
10550     SendToProgram(buf, cps);\r
10551     return TRUE;\r
10552   }\r
10553   return FALSE;\r
10554 }\r
10555 \r
10556 int\r
10557 IntFeature(p, name, loc, cps)\r
10558      char **p;\r
10559      char *name;\r
10560      int *loc;\r
10561      ChessProgramState *cps;\r
10562 {\r
10563   char buf[MSG_SIZ];\r
10564   int len = strlen(name);\r
10565   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10566     (*p) += len + 1;\r
10567     sscanf(*p, "%d", loc);\r
10568     while (**p && **p != ' ') (*p)++;\r
10569     sprintf(buf, "accepted %s\n", name);\r
10570     SendToProgram(buf, cps);\r
10571     return TRUE;\r
10572   }\r
10573   return FALSE;\r
10574 }\r
10575 \r
10576 int\r
10577 StringFeature(p, name, loc, cps)\r
10578      char **p;\r
10579      char *name;\r
10580      char loc[];\r
10581      ChessProgramState *cps;\r
10582 {\r
10583   char buf[MSG_SIZ];\r
10584   int len = strlen(name);\r
10585   if (strncmp((*p), name, len) == 0\r
10586       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
10587     (*p) += len + 2;\r
10588     sscanf(*p, "%[^\"]", loc);\r
10589     while (**p && **p != '\"') (*p)++;\r
10590     if (**p == '\"') (*p)++;\r
10591     sprintf(buf, "accepted %s\n", name);\r
10592     SendToProgram(buf, cps);\r
10593     return TRUE;\r
10594   }\r
10595   return FALSE;\r
10596 }\r
10597 \r
10598 void\r
10599 FeatureDone(cps, val)\r
10600      ChessProgramState* cps;\r
10601      int val;\r
10602 {\r
10603   DelayedEventCallback cb = GetDelayedEvent();\r
10604   if ((cb == InitBackEnd3 && cps == &first) ||\r
10605       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
10606     CancelDelayedEvent();\r
10607     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
10608   }\r
10609   cps->initDone = val;\r
10610 }\r
10611 \r
10612 /* Parse feature command from engine */\r
10613 void\r
10614 ParseFeatures(args, cps)\r
10615      char* args;\r
10616      ChessProgramState *cps;  \r
10617 {\r
10618   char *p = args;\r
10619   char *q;\r
10620   int val;\r
10621   char buf[MSG_SIZ];\r
10622 \r
10623   for (;;) {\r
10624     while (*p == ' ') p++;\r
10625     if (*p == NULLCHAR) return;\r
10626 \r
10627     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
10628     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
10629     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
10630     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
10631     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
10632     if (BoolFeature(&p, "reuse", &val, cps)) {\r
10633       /* Engine can disable reuse, but can't enable it if user said no */\r
10634       if (!val) cps->reuse = FALSE;\r
10635       continue;\r
10636     }\r
10637     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
10638     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
10639       if (gameMode == TwoMachinesPlay) {\r
10640         DisplayTwoMachinesTitle();\r
10641       } else {\r
10642         DisplayTitle("");\r
10643       }\r
10644       continue;\r
10645     }\r
10646     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
10647     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
10648     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
10649     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
10650     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
10651     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
10652     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
10653     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
10654     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
10655     if (IntFeature(&p, "done", &val, cps)) {\r
10656       FeatureDone(cps, val);\r
10657       continue;\r
10658     }\r
10659     /* Added by Tord: */\r
10660     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
10661     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
10662     /* End of additions by Tord */\r
10663 \r
10664     /* unknown feature: complain and skip */\r
10665     q = p;\r
10666     while (*q && *q != '=') q++;\r
10667     sprintf(buf, "rejected %.*s\n", q-p, p);\r
10668     SendToProgram(buf, cps);\r
10669     p = q;\r
10670     if (*p == '=') {\r
10671       p++;\r
10672       if (*p == '\"') {\r
10673         p++;\r
10674         while (*p && *p != '\"') p++;\r
10675         if (*p == '\"') p++;\r
10676       } else {\r
10677         while (*p && *p != ' ') p++;\r
10678       }\r
10679     }\r
10680   }\r
10681 \r
10682 }\r
10683 \r
10684 void\r
10685 PeriodicUpdatesEvent(newState)\r
10686      int newState;\r
10687 {\r
10688     if (newState == appData.periodicUpdates)\r
10689       return;\r
10690 \r
10691     appData.periodicUpdates=newState;\r
10692 \r
10693     /* Display type changes, so update it now */\r
10694     DisplayAnalysis();\r
10695 \r
10696     /* Get the ball rolling again... */\r
10697     if (newState) {\r
10698         AnalysisPeriodicEvent(1);\r
10699         StartAnalysisClock();\r
10700     }\r
10701 }\r
10702 \r
10703 void\r
10704 PonderNextMoveEvent(newState)\r
10705      int newState;\r
10706 {\r
10707     if (newState == appData.ponderNextMove) return;\r
10708     if (gameMode == EditPosition) EditPositionDone();\r
10709     if (newState) {\r
10710         SendToProgram("hard\n", &first);\r
10711         if (gameMode == TwoMachinesPlay) {\r
10712             SendToProgram("hard\n", &second);\r
10713         }\r
10714     } else {\r
10715         SendToProgram("easy\n", &first);\r
10716         thinkOutput[0] = NULLCHAR;\r
10717         if (gameMode == TwoMachinesPlay) {\r
10718             SendToProgram("easy\n", &second);\r
10719         }\r
10720     }\r
10721     appData.ponderNextMove = newState;\r
10722 }\r
10723 \r
10724 void\r
10725 ShowThinkingEvent(newState)\r
10726      int newState;\r
10727 {\r
10728     if (newState == appData.showThinking) return;\r
10729     if (gameMode == EditPosition) EditPositionDone();\r
10730     if (newState) {\r
10731         SendToProgram("post\n", &first);\r
10732         if (gameMode == TwoMachinesPlay) {\r
10733             SendToProgram("post\n", &second);\r
10734         }\r
10735     } else {\r
10736         SendToProgram("nopost\n", &first);\r
10737         thinkOutput[0] = NULLCHAR;\r
10738         if (gameMode == TwoMachinesPlay) {\r
10739             SendToProgram("nopost\n", &second);\r
10740         }\r
10741     }\r
10742     appData.showThinking = newState;\r
10743 }\r
10744 \r
10745 void\r
10746 AskQuestionEvent(title, question, replyPrefix, which)\r
10747      char *title; char *question; char *replyPrefix; char *which;\r
10748 {\r
10749   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
10750   if (pr == NoProc) return;\r
10751   AskQuestion(title, question, replyPrefix, pr);\r
10752 }\r
10753 \r
10754 void\r
10755 DisplayMove(moveNumber)\r
10756      int moveNumber;\r
10757 {\r
10758     char message[MSG_SIZ];\r
10759     char res[MSG_SIZ];\r
10760     char cpThinkOutput[MSG_SIZ];\r
10761 \r
10762     if (moveNumber == forwardMostMove - 1 || \r
10763         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10764 \r
10765         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
10766 \r
10767         if (strchr(cpThinkOutput, '\n')) {\r
10768             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
10769         }\r
10770     } else {\r
10771         *cpThinkOutput = NULLCHAR;\r
10772     }\r
10773 \r
10774     /* [AS] Hide thinking from human user */\r
10775     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
10776         *cpThinkOutput = NULLCHAR;\r
10777         if( thinkOutput[0] != NULLCHAR ) {\r
10778             int i;\r
10779 \r
10780             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
10781                 cpThinkOutput[i] = '.';\r
10782             }\r
10783             cpThinkOutput[i] = NULLCHAR;\r
10784             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
10785         }\r
10786     }\r
10787 \r
10788     if (moveNumber == forwardMostMove - 1 &&\r
10789         gameInfo.resultDetails != NULL) {\r
10790         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
10791             sprintf(res, " %s", PGNResult(gameInfo.result));\r
10792         } else {\r
10793             sprintf(res, " {%s} %s",\r
10794                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
10795         }\r
10796     } else {\r
10797         res[0] = NULLCHAR;\r
10798     }\r
10799     \r
10800     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
10801         DisplayMessage(res, cpThinkOutput);\r
10802     } else {\r
10803         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
10804                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
10805                 parseList[moveNumber], res);\r
10806         DisplayMessage(message, cpThinkOutput);\r
10807     }\r
10808 }\r
10809 \r
10810 void\r
10811 DisplayAnalysisText(text)\r
10812      char *text;\r
10813 {\r
10814     char buf[MSG_SIZ];\r
10815 \r
10816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10817         sprintf(buf, "Analysis (%s)", first.tidy);\r
10818         AnalysisPopUp(buf, text);\r
10819     }\r
10820 }\r
10821 \r
10822 static int\r
10823 only_one_move(str)\r
10824      char *str;\r
10825 {\r
10826     while (*str && isspace(*str)) ++str;\r
10827     while (*str && !isspace(*str)) ++str;\r
10828     if (!*str) return 1;\r
10829     while (*str && isspace(*str)) ++str;\r
10830     if (!*str) return 1;\r
10831     return 0;\r
10832 }\r
10833 \r
10834 void\r
10835 DisplayAnalysis()\r
10836 {\r
10837     char buf[MSG_SIZ];\r
10838     char lst[MSG_SIZ / 2];\r
10839     double nps;\r
10840     static char *xtra[] = { "", " (--)", " (++)" };\r
10841     int h, m, s, cs;\r
10842   \r
10843     if (programStats.time == 0) {\r
10844         programStats.time = 1;\r
10845     }\r
10846   \r
10847     if (programStats.got_only_move) {\r
10848         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
10849     } else {\r
10850         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
10851 \r
10852         nps = (((double)programStats.nodes) /\r
10853                (((double)programStats.time)/100.0));\r
10854 \r
10855         cs = programStats.time % 100;\r
10856         s = programStats.time / 100;\r
10857         h = (s / (60*60));\r
10858         s = s - h*60*60;\r
10859         m = (s/60);\r
10860         s = s - m*60;\r
10861 \r
10862         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
10863           if (programStats.move_name[0] != NULLCHAR) {\r
10864             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10865                     programStats.depth,\r
10866                     programStats.nr_moves-programStats.moves_left,\r
10867                     programStats.nr_moves, programStats.move_name,\r
10868                     ((float)programStats.score)/100.0, lst,\r
10869                     only_one_move(lst)?\r
10870                     xtra[programStats.got_fail] : "",\r
10871                     programStats.nodes, (int)nps, h, m, s, cs);\r
10872           } else {\r
10873             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10874                     programStats.depth,\r
10875                     programStats.nr_moves-programStats.moves_left,\r
10876                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
10877                     lst,\r
10878                     only_one_move(lst)?\r
10879                     xtra[programStats.got_fail] : "",\r
10880                     programStats.nodes, (int)nps, h, m, s, cs);\r
10881           }\r
10882         } else {\r
10883             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
10884                     programStats.depth,\r
10885                     ((float)programStats.score)/100.0,\r
10886                     lst,\r
10887                     only_one_move(lst)?\r
10888                     xtra[programStats.got_fail] : "",\r
10889                     programStats.nodes, (int)nps, h, m, s, cs);\r
10890         }\r
10891     }\r
10892     DisplayAnalysisText(buf);\r
10893 }\r
10894 \r
10895 void\r
10896 DisplayComment(moveNumber, text)\r
10897      int moveNumber;\r
10898      char *text;\r
10899 {\r
10900     char title[MSG_SIZ];\r
10901 \r
10902     if( appData.autoDisplayComment ) {\r
10903         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
10904             strcpy(title, "Comment");\r
10905         } else {\r
10906             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
10907                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
10908                     parseList[moveNumber]);\r
10909         }\r
10910 \r
10911         CommentPopUp(title, text);\r
10912     }\r
10913 }\r
10914 \r
10915 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
10916  * might be busy thinking or pondering.  It can be omitted if your\r
10917  * gnuchess is configured to stop thinking immediately on any user\r
10918  * input.  However, that gnuchess feature depends on the FIONREAD\r
10919  * ioctl, which does not work properly on some flavors of Unix.\r
10920  */\r
10921 void\r
10922 Attention(cps)\r
10923      ChessProgramState *cps;\r
10924 {\r
10925 #if ATTENTION\r
10926     if (!cps->useSigint) return;\r
10927     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
10928     switch (gameMode) {\r
10929       case MachinePlaysWhite:\r
10930       case MachinePlaysBlack:\r
10931       case TwoMachinesPlay:\r
10932       case IcsPlayingWhite:\r
10933       case IcsPlayingBlack:\r
10934       case AnalyzeMode:\r
10935       case AnalyzeFile:\r
10936         /* Skip if we know it isn't thinking */\r
10937         if (!cps->maybeThinking) return;\r
10938         if (appData.debugMode)\r
10939           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
10940         InterruptChildProcess(cps->pr);\r
10941         cps->maybeThinking = FALSE;\r
10942         break;\r
10943       default:\r
10944         break;\r
10945     }\r
10946 #endif /*ATTENTION*/\r
10947 }\r
10948 \r
10949 int\r
10950 CheckFlags()\r
10951 {\r
10952     if (whiteTimeRemaining <= 0) {\r
10953         if (!whiteFlag) {\r
10954             whiteFlag = TRUE;\r
10955             if (appData.icsActive) {\r
10956                 if (appData.autoCallFlag &&\r
10957                     gameMode == IcsPlayingBlack && !blackFlag) {\r
10958                   SendToICS(ics_prefix);\r
10959                   SendToICS("flag\n");\r
10960                 }\r
10961             } else {\r
10962                 if (blackFlag) {\r
10963                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
10964                 } else {\r
10965                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
10966                     if (appData.autoCallFlag) {\r
10967                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
10968                         return TRUE;\r
10969                     }\r
10970                 }\r
10971             }\r
10972         }\r
10973     }\r
10974     if (blackTimeRemaining <= 0) {\r
10975         if (!blackFlag) {\r
10976             blackFlag = TRUE;\r
10977             if (appData.icsActive) {\r
10978                 if (appData.autoCallFlag &&\r
10979                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
10980                   SendToICS(ics_prefix);\r
10981                   SendToICS("flag\n");\r
10982                 }\r
10983             } else {\r
10984                 if (whiteFlag) {\r
10985                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
10986                 } else {\r
10987                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
10988                     if (appData.autoCallFlag) {\r
10989                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
10990                         return TRUE;\r
10991                     }\r
10992                 }\r
10993             }\r
10994         }\r
10995     }\r
10996     return FALSE;\r
10997 }\r
10998 \r
10999 void\r
11000 CheckTimeControl()\r
11001 {\r
11002     if (!appData.clockMode || appData.icsActive ||\r
11003         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
11004 \r
11005     if (timeIncrement >= 0) {\r
11006         if (WhiteOnMove(forwardMostMove)) {\r
11007             blackTimeRemaining += timeIncrement;\r
11008         } else {\r
11009             whiteTimeRemaining += timeIncrement;\r
11010         }\r
11011     }\r
11012     /*\r
11013      * add time to clocks when time control is achieved\r
11014      */\r
11015     if (movesPerSession) {\r
11016       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {\r
11017       case 0:\r
11018         /* White made time control */\r
11019         whiteTimeRemaining += GetTimeControlForWhite();\r
11020         break;\r
11021       case 1:\r
11022         /* Black made time control */\r
11023         blackTimeRemaining += GetTimeControlForBlack();\r
11024         break;\r
11025       default:\r
11026         break;\r
11027       }\r
11028     }\r
11029 }\r
11030 \r
11031 void\r
11032 DisplayBothClocks()\r
11033 {\r
11034     int wom = gameMode == EditPosition ?\r
11035       !blackPlaysFirst : WhiteOnMove(currentMove);\r
11036     DisplayWhiteClock(whiteTimeRemaining, wom);\r
11037     DisplayBlackClock(blackTimeRemaining, !wom);\r
11038 }\r
11039 \r
11040 \r
11041 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
11042    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
11043    to use other calls if you don't.  Clocks will be less accurate if\r
11044    you have neither ftime nor gettimeofday.\r
11045 */\r
11046 \r
11047 /* Get the current time as a TimeMark */\r
11048 void\r
11049 GetTimeMark(tm)\r
11050      TimeMark *tm;\r
11051 {\r
11052 #if HAVE_GETTIMEOFDAY\r
11053 \r
11054     struct timeval timeVal;\r
11055     struct timezone timeZone;\r
11056 \r
11057     gettimeofday(&timeVal, &timeZone);\r
11058     tm->sec = (long) timeVal.tv_sec; \r
11059     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
11060 \r
11061 #else /*!HAVE_GETTIMEOFDAY*/\r
11062 #if HAVE_FTIME\r
11063 \r
11064 #include <sys/timeb.h>\r
11065     struct timeb timeB;\r
11066 \r
11067     ftime(&timeB);\r
11068     tm->sec = (long) timeB.time;\r
11069     tm->ms = (int) timeB.millitm;\r
11070 \r
11071 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
11072     tm->sec = (long) time(NULL);\r
11073     tm->ms = 0;\r
11074 #endif\r
11075 #endif\r
11076 }\r
11077 \r
11078 /* Return the difference in milliseconds between two\r
11079    time marks.  We assume the difference will fit in a long!\r
11080 */\r
11081 long\r
11082 SubtractTimeMarks(tm2, tm1)\r
11083      TimeMark *tm2, *tm1;\r
11084 {\r
11085     return 1000L*(tm2->sec - tm1->sec) +\r
11086            (long) (tm2->ms - tm1->ms);\r
11087 }\r
11088 \r
11089 \r
11090 /*\r
11091  * Code to manage the game clocks.\r
11092  *\r
11093  * In tournament play, black starts the clock and then white makes a move.\r
11094  * We give the human user a slight advantage if he is playing white---the\r
11095  * clocks don't run until he makes his first move, so it takes zero time.\r
11096  * Also, we don't account for network lag, so we could get out of sync\r
11097  * with GNU Chess's clock -- but then, referees are always right.  \r
11098  */\r
11099 \r
11100 static TimeMark tickStartTM;\r
11101 static long intendedTickLength;\r
11102 \r
11103 long\r
11104 NextTickLength(timeRemaining)\r
11105      long timeRemaining;\r
11106 {\r
11107     long nominalTickLength, nextTickLength;\r
11108 \r
11109     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
11110       nominalTickLength = 100L;\r
11111     else\r
11112       nominalTickLength = 1000L;\r
11113     nextTickLength = timeRemaining % nominalTickLength;\r
11114     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
11115 \r
11116     return nextTickLength;\r
11117 }\r
11118 \r
11119 /* Stop clocks and reset to a fresh time control */\r
11120 void\r
11121 ResetClocks() \r
11122 {\r
11123     (void) StopClockTimer();\r
11124     if (appData.icsActive) {\r
11125         whiteTimeRemaining = blackTimeRemaining = 0;\r
11126     } else {\r
11127         whiteTimeRemaining = GetTimeControlForWhite();\r
11128         blackTimeRemaining = GetTimeControlForBlack();\r
11129     }\r
11130     if (whiteFlag || blackFlag) {\r
11131         DisplayTitle("");\r
11132         whiteFlag = blackFlag = FALSE;\r
11133     }\r
11134     DisplayBothClocks();\r
11135 }\r
11136 \r
11137 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
11138 \r
11139 /* Decrement running clock by amount of time that has passed */\r
11140 void\r
11141 DecrementClocks()\r
11142 {\r
11143     long timeRemaining;\r
11144     long lastTickLength, fudge;\r
11145     TimeMark now;\r
11146 \r
11147     if (!appData.clockMode) return;\r
11148     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
11149         \r
11150     GetTimeMark(&now);\r
11151 \r
11152     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11153 \r
11154     /* Fudge if we woke up a little too soon */\r
11155     fudge = intendedTickLength - lastTickLength;\r
11156     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
11157 \r
11158     if (WhiteOnMove(forwardMostMove)) {\r
11159         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
11160         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
11161                           WhiteOnMove(currentMove));\r
11162     } else {\r
11163         timeRemaining = blackTimeRemaining -= lastTickLength;\r
11164         DisplayBlackClock(blackTimeRemaining - fudge,\r
11165                           !WhiteOnMove(currentMove));\r
11166     }\r
11167 \r
11168     if (CheckFlags()) return;\r
11169         \r
11170     tickStartTM = now;\r
11171     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
11172     StartClockTimer(intendedTickLength);\r
11173 \r
11174     /* if the time remaining has fallen below the alarm threshold, sound the\r
11175      * alarm. if the alarm has sounded and (due to a takeback or time control\r
11176      * with increment) the time remaining has increased to a level above the\r
11177      * threshold, reset the alarm so it can sound again. \r
11178      */\r
11179     \r
11180     if (appData.icsActive && appData.icsAlarm) {\r
11181 \r
11182         /* make sure we are dealing with the user's clock */\r
11183         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
11184                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
11185            )) return;\r
11186 \r
11187         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
11188             alarmSounded = FALSE;\r
11189         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
11190             PlayAlarmSound();\r
11191             alarmSounded = TRUE;\r
11192         }\r
11193     }\r
11194 }\r
11195 \r
11196 \r
11197 /* A player has just moved, so stop the previously running\r
11198    clock and (if in clock mode) start the other one.\r
11199    We redisplay both clocks in case we're in ICS mode, because\r
11200    ICS gives us an update to both clocks after every move.\r
11201    Note that this routine is called *after* forwardMostMove\r
11202    is updated, so the last fractional tick must be subtracted\r
11203    from the color that is *not* on move now.\r
11204 */\r
11205 void\r
11206 SwitchClocks()\r
11207 {\r
11208     long lastTickLength;\r
11209     TimeMark now;\r
11210     int flagged = FALSE;\r
11211 \r
11212     GetTimeMark(&now);\r
11213 \r
11214     if (StopClockTimer() && appData.clockMode) {\r
11215         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11216         if (WhiteOnMove(forwardMostMove)) {\r
11217             blackTimeRemaining -= lastTickLength;\r
11218         } else {\r
11219             whiteTimeRemaining -= lastTickLength;\r
11220         }\r
11221         /* [HGM] save time for PGN file if engine did not give it */\r
11222         if(pvInfoList[forwardMostMove-1].time == -1)\r
11223              pvInfoList[forwardMostMove-1].time = lastTickLength/100;\r
11224         flagged = CheckFlags();\r
11225     }\r
11226     CheckTimeControl();\r
11227 \r
11228     if (flagged || !appData.clockMode) return;\r
11229 \r
11230     switch (gameMode) {\r
11231       case MachinePlaysBlack:\r
11232       case MachinePlaysWhite:\r
11233       case BeginningOfGame:\r
11234         if (pausing) return;\r
11235         break;\r
11236 \r
11237       case EditGame:\r
11238       case PlayFromGameFile:\r
11239       case IcsExamining:\r
11240         return;\r
11241 \r
11242       default:\r
11243         break;\r
11244     }\r
11245 \r
11246     tickStartTM = now;\r
11247     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11248       whiteTimeRemaining : blackTimeRemaining);\r
11249     StartClockTimer(intendedTickLength);\r
11250 }\r
11251         \r
11252 \r
11253 /* Stop both clocks */\r
11254 void\r
11255 StopClocks()\r
11256 {       \r
11257     long lastTickLength;\r
11258     TimeMark now;\r
11259 \r
11260     if (!StopClockTimer()) return;\r
11261     if (!appData.clockMode) return;\r
11262 \r
11263     GetTimeMark(&now);\r
11264 \r
11265     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11266     if (WhiteOnMove(forwardMostMove)) {\r
11267         whiteTimeRemaining -= lastTickLength;\r
11268         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
11269     } else {\r
11270         blackTimeRemaining -= lastTickLength;\r
11271         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
11272     }\r
11273     CheckFlags();\r
11274 }\r
11275         \r
11276 /* Start clock of player on move.  Time may have been reset, so\r
11277    if clock is already running, stop and restart it. */\r
11278 void\r
11279 StartClocks()\r
11280 {\r
11281     (void) StopClockTimer(); /* in case it was running already */\r
11282     DisplayBothClocks();\r
11283     if (CheckFlags()) return;\r
11284 \r
11285     if (!appData.clockMode) return;\r
11286     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
11287 \r
11288     GetTimeMark(&tickStartTM);\r
11289     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11290       whiteTimeRemaining : blackTimeRemaining);\r
11291     StartClockTimer(intendedTickLength);\r
11292 }\r
11293 \r
11294 char *\r
11295 TimeString(ms)\r
11296      long ms;\r
11297 {\r
11298     long second, minute, hour, day;\r
11299     char *sign = "";\r
11300     static char buf[32];\r
11301     \r
11302     if (ms > 0 && ms <= 9900) {\r
11303       /* convert milliseconds to tenths, rounding up */\r
11304       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
11305 \r
11306       sprintf(buf, " %03.1f ", tenths/10.0);\r
11307       return buf;\r
11308     }\r
11309 \r
11310     /* convert milliseconds to seconds, rounding up */\r
11311     /* use floating point to avoid strangeness of integer division\r
11312        with negative dividends on many machines */\r
11313     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
11314 \r
11315     if (second < 0) {\r
11316         sign = "-";\r
11317         second = -second;\r
11318     }\r
11319     \r
11320     day = second / (60 * 60 * 24);\r
11321     second = second % (60 * 60 * 24);\r
11322     hour = second / (60 * 60);\r
11323     second = second % (60 * 60);\r
11324     minute = second / 60;\r
11325     second = second % 60;\r
11326     \r
11327     if (day > 0)\r
11328       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
11329               sign, day, hour, minute, second);\r
11330     else if (hour > 0)\r
11331       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
11332     else\r
11333       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
11334     \r
11335     return buf;\r
11336 }\r
11337 \r
11338 \r
11339 /*\r
11340  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
11341  */\r
11342 char *\r
11343 StrStr(string, match)\r
11344      char *string, *match;\r
11345 {\r
11346     int i, length;\r
11347     \r
11348     length = strlen(match);\r
11349     \r
11350     for (i = strlen(string) - length; i >= 0; i--, string++)\r
11351       if (!strncmp(match, string, length))\r
11352         return string;\r
11353     \r
11354     return NULL;\r
11355 }\r
11356 \r
11357 char *\r
11358 StrCaseStr(string, match)\r
11359      char *string, *match;\r
11360 {\r
11361     int i, j, length;\r
11362     \r
11363     length = strlen(match);\r
11364     \r
11365     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
11366         for (j = 0; j < length; j++) {\r
11367             if (ToLower(match[j]) != ToLower(string[j]))\r
11368               break;\r
11369         }\r
11370         if (j == length) return string;\r
11371     }\r
11372 \r
11373     return NULL;\r
11374 }\r
11375 \r
11376 #ifndef _amigados\r
11377 int\r
11378 StrCaseCmp(s1, s2)\r
11379      char *s1, *s2;\r
11380 {\r
11381     char c1, c2;\r
11382     \r
11383     for (;;) {\r
11384         c1 = ToLower(*s1++);\r
11385         c2 = ToLower(*s2++);\r
11386         if (c1 > c2) return 1;\r
11387         if (c1 < c2) return -1;\r
11388         if (c1 == NULLCHAR) return 0;\r
11389     }\r
11390 }\r
11391 \r
11392 \r
11393 int\r
11394 ToLower(c)\r
11395      int c;\r
11396 {\r
11397     return isupper(c) ? tolower(c) : c;\r
11398 }\r
11399 \r
11400 \r
11401 int\r
11402 ToUpper(c)\r
11403      int c;\r
11404 {\r
11405     return islower(c) ? toupper(c) : c;\r
11406 }\r
11407 #endif /* !_amigados    */\r
11408 \r
11409 char *\r
11410 StrSave(s)\r
11411      char *s;\r
11412 {\r
11413     char *ret;\r
11414 \r
11415     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
11416         strcpy(ret, s);\r
11417     }\r
11418     return ret;\r
11419 }\r
11420 \r
11421 char *\r
11422 StrSavePtr(s, savePtr)\r
11423      char *s, **savePtr;\r
11424 {\r
11425     if (*savePtr) {\r
11426         free(*savePtr);\r
11427     }\r
11428     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
11429         strcpy(*savePtr, s);\r
11430     }\r
11431     return(*savePtr);\r
11432 }\r
11433 \r
11434 char *\r
11435 PGNDate()\r
11436 {\r
11437     time_t clock;\r
11438     struct tm *tm;\r
11439     char buf[MSG_SIZ];\r
11440 \r
11441     clock = time((time_t *)NULL);\r
11442     tm = localtime(&clock);\r
11443     sprintf(buf, "%04d.%02d.%02d",\r
11444             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
11445     return StrSave(buf);\r
11446 }\r
11447 \r
11448 \r
11449 char *\r
11450 PositionToFEN(move, useFEN960)\r
11451      int move;\r
11452      int useFEN960;\r
11453 {\r
11454     int i, j, fromX, fromY, toX, toY;\r
11455     int whiteToPlay;\r
11456     char buf[128];\r
11457     char *p, *q;\r
11458     int emptycount;\r
11459     ChessSquare piece;\r
11460 \r
11461     whiteToPlay = (gameMode == EditPosition) ?\r
11462       !blackPlaysFirst : (move % 2 == 0);\r
11463     p = buf;\r
11464 \r
11465     /* Piece placement data */\r
11466     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11467         emptycount = 0;\r
11468         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11469             if (boards[move][i][j] == EmptySquare) {\r
11470                 emptycount++;\r
11471             } else {\r
11472                 if (emptycount > 0) {\r
11473                     if(emptycount<10) /* [HGM] can be >= 10 */\r
11474                         *p++ = '0' + emptycount;\r
11475                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11476                     emptycount = 0;\r
11477                 }\r
11478                 *p++ = PieceToChar(boards[move][i][j]);\r
11479                 if(gameInfo.variant == VariantCrazyhouse) {\r
11480                     /* [HGM] flag Crazyhouse promoted pieces */\r
11481                     if( (int)boards[move][i][j] > (int) WhiteQueen && (int)boards[move][i][j] < (int) WhiteKing ||\r
11482                         (int)boards[move][i][j] > (int) BlackQueen && (int)boards[move][i][j] < (int) BlackKing ) {\r
11483                         p[-1] = PieceToChar((ChessSquare)((int)boards[move][i][j]-(int)WhiteAlfil+(int)WhitePawn));\r
11484                         *p++ = '~';\r
11485                     }\r
11486                 }\r
11487             }\r
11488         }\r
11489         if (emptycount > 0) {\r
11490             if(emptycount<10) /* [HGM] can be >= 10 */\r
11491                 *p++ = '0' + emptycount;\r
11492             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11493             emptycount = 0;\r
11494         }\r
11495         *p++ = '/';\r
11496     }\r
11497     *(p - 1) = ' ';\r
11498 \r
11499     /* Active color */\r
11500     *p++ = whiteToPlay ? 'w' : 'b';\r
11501     *p++ = ' ';\r
11502 \r
11503     /* HACK: we don't keep track of castling availability, so fake it! */\r
11504     /* Tord! please fix with the aid of castlingRights[move][...] */\r
11505 \r
11506     /* PUSH Fabien & Tord */\r
11507 \r
11508     /* Declare all potential FRC castling rights (conservative) */\r
11509     /* outermost rook on each side of the king */\r
11510 \r
11511     if( gameInfo.variant == VariantFischeRandom ) {\r
11512        int fk, fr;\r
11513 \r
11514        q = p;\r
11515 \r
11516        /* White castling rights */\r
11517 \r
11518        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11519 \r
11520           if (boards[move][0][fk] == WhiteKing) {\r
11521 \r
11522              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11523                 if (boards[move][0][fr] == WhiteRook) {\r
11524                    *p++ = useFEN960 ? 'A' + fr : 'K';\r
11525                    break;\r
11526                 }\r
11527              }\r
11528 \r
11529              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11530                 if (boards[move][0][fr] == WhiteRook) {\r
11531                    *p++ = useFEN960 ? 'A' + fr : 'Q';\r
11532                    break;\r
11533                 }\r
11534              }\r
11535           }\r
11536        }\r
11537 \r
11538        /* Black castling rights */\r
11539 \r
11540        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11541 \r
11542           if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {\r
11543 \r
11544              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11545                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11546                    *p++ = useFEN960 ? 'a' + fr : 'k';\r
11547                    break;\r
11548                 }\r
11549              }\r
11550 \r
11551              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11552                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11553                    *p++ = useFEN960 ? 'a' + fr : 'q';\r
11554                    break;\r
11555                 }\r
11556              }\r
11557           }\r
11558        }\r
11559 \r
11560        if (q == p) *p++ = '-'; /* No castling rights */\r
11561        *p++ = ' ';\r
11562     }\r
11563     else {\r
11564         q = p;\r
11565 \r
11566 #ifdef OLDCASTLINGCODE\r
11567         if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {\r
11568             if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';\r
11569             if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';\r
11570         }\r
11571         if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {\r
11572             if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';\r
11573             if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';\r
11574         }           \r
11575 #else\r
11576         /* [HGM] write true castling rights */\r
11577         if( nrCastlingRights == 6 ) {\r
11578             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
11579                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
11580             if(castlingRights[move][1] == BOARD_LEFT &&\r
11581                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
11582             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
11583                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
11584             if(castlingRights[move][4] == BOARD_LEFT &&\r
11585                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
11586         }\r
11587 #endif\r
11588         if (q == p) *p++ = '-';\r
11589         *p++ = ' ';\r
11590     }\r
11591 \r
11592     /* POP Fabien & Tord */\r
11593 \r
11594     /* En passant target square */\r
11595     if (move > backwardMostMove) {\r
11596         fromX = moveList[move - 1][0] - AAA;\r
11597         fromY = moveList[move - 1][1] - ONE;\r
11598         toX = moveList[move - 1][2] - AAA;\r
11599         toY = moveList[move - 1][3] - ONE;\r
11600         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
11601             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
11602             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
11603             fromX == toX) {\r
11604             /* 2-square pawn move just happened */\r
11605             *p++ = toX + AAA;\r
11606             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
11607         } else {\r
11608             *p++ = '-';\r
11609         }\r
11610     } else {\r
11611         *p++ = '-';\r
11612     }\r
11613 \r
11614     /* [HGM] print Crazyhouse holdings */\r
11615     if( gameInfo.variant == VariantCrazyhouse ) {\r
11616         *p++ = ' '; q = p;\r
11617         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
11618             piece = boards[move][i][BOARD_WIDTH-1];\r
11619             if( piece != EmptySquare )\r
11620               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
11621                   *p++ = PieceToChar(piece);\r
11622         }\r
11623         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
11624             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
11625             if( piece != EmptySquare )\r
11626               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
11627                   *p++ = PieceToChar(piece);\r
11628         }\r
11629 \r
11630         if( q == p ) *p++ = '-';\r
11631         *p++ = ' ';\r
11632     }\r
11633 \r
11634     /* [HGM] find reversible plies */\r
11635     {   int i = 0, j=move;\r
11636 \r
11637     if (appData.debugMode) { int k;\r
11638         fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
11639         for(k=backwardMostMove; k<=forwardMostMove; k++)\r
11640             fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
11641 \r
11642     }\r
11643 \r
11644         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
11645         if( j == backwardMostMove ) i += initialRulePlies;\r
11646         sprintf(p, " %d", i);\r
11647       p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
11648     }\r
11649     /* Fullmove number */\r
11650     sprintf(p, " %d", (move / 2) + 1);\r
11651     \r
11652     return StrSave(buf);\r
11653 }\r
11654 \r
11655 Boolean\r
11656 ParseFEN(board, blackPlaysFirst, fen)\r
11657     Board board;\r
11658      int *blackPlaysFirst;\r
11659      char *fen;\r
11660 {\r
11661     int i, j;\r
11662     char *p;\r
11663     int emptycount;\r
11664     ChessSquare piece;\r
11665 \r
11666     p = fen;\r
11667 \r
11668     /* [HGM] by default clear Crazyhouse holdings, if present */\r
11669    if(gameInfo.holdingsWidth) {\r
11670        for(i=0; i<BOARD_HEIGHT; i++) {\r
11671            board[i][0]             = EmptySquare; /* black holdings */\r
11672            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
11673            board[i][1]             = (ChessSquare) 0; /* black counts */\r
11674            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
11675        }\r
11676    }\r
11677 \r
11678     /* Piece placement data */\r
11679     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11680         j = 0;\r
11681         for (;;) {\r
11682             if (*p == '/' || *p == ' ') {\r
11683                 if (*p == '/') p++;\r
11684                 emptycount = gameInfo.boardWidth - j;\r
11685                 while (emptycount--)\r
11686                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11687                 break;\r
11688 #if(BOARD_SIZE >= 10)\r
11689             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
11690                 p++; emptycount=10;\r
11691                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11692                 while (emptycount--)\r
11693                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11694 #endif\r
11695             } else if (isdigit(*p)) {\r
11696                 emptycount = *p++ - '0';\r
11697                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
11698                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11699                 while (emptycount--)\r
11700                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11701             } else if (isalpha(*p)) {\r
11702                 if (j >= gameInfo.boardWidth) return FALSE;\r
11703                 piece = CharToPiece(*p++);\r
11704                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
11705                     piece = (ChessSquare) ((int)piece + (int)WhiteAlfil - (int)WhitePawn);\r
11706                     p++;\r
11707                 }\r
11708                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
11709             } else {\r
11710                 return FALSE;\r
11711             }\r
11712         }\r
11713     }\r
11714     while (*p == '/' || *p == ' ') p++;\r
11715 \r
11716     /* Active color */\r
11717     switch (*p++) {\r
11718       case 'w':\r
11719         *blackPlaysFirst = FALSE;\r
11720         break;\r
11721       case 'b': \r
11722         *blackPlaysFirst = TRUE;\r
11723         break;\r
11724       default:\r
11725         return FALSE;\r
11726     }\r
11727 \r
11728     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
11729     /* return the extra info in global variiables             */\r
11730   {\r
11731     /* set defaults in case FEN is incomplete */\r
11732     FENepStatus = EP_UNKNOWN;\r
11733     for(i=0; i<nrCastlingRights; i++ ) {\r
11734         FENcastlingRights[i] = initialRights[i];\r
11735     }   /* assume possible unless obviously impossible */\r
11736     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
11737     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
11738     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
11739     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
11740     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
11741     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
11742     FENrulePlies = 0;\r
11743 \r
11744     while(*p==' ') p++;\r
11745 \r
11746     if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11747               /* castling indicator present, so default is no castlings */\r
11748               for(i=0; i<nrCastlingRights; i++ ) {\r
11749                      FENcastlingRights[i] = -1;\r
11750               }\r
11751     }\r
11752     while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11753         switch(*p++) {\r
11754           case'K':\r
11755               FENcastlingRights[0] = BOARD_RGHT-1;\r
11756               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11757               break;\r
11758           case'Q':\r
11759               FENcastlingRights[1] = BOARD_LEFT;\r
11760               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11761               break;\r
11762           case'k':\r
11763               FENcastlingRights[3] = BOARD_RGHT-1;\r
11764               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11765               break;\r
11766           case'q':\r
11767               FENcastlingRights[4] = BOARD_LEFT;\r
11768               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11769               break;\r
11770           /* Tord! FRC! */\r
11771         }\r
11772     }\r
11773 \r
11774     while(*p==' ') p++;\r
11775 \r
11776 \r
11777     if(*p=='-') {\r
11778         p++; FENepStatus = EP_NONE;\r
11779     } else {\r
11780        char c = *p++ - AAA;\r
11781 \r
11782        if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
11783        if(*p >= '0' && *p <='9') *p++;\r
11784        FENepStatus = c;\r
11785     }\r
11786 \r
11787     /* [HGM] look for Crazyhouse holdings here */\r
11788     while(*p==' ') p++;\r
11789     if( !isdigit(*p) ) {\r
11790         if(*p == '-' ) *p++; /* empty holdings */ else {\r
11791             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
11792             /* if we would allow FEN reading to set board size, we would   */\r
11793             /* have to add holdings and shift the board read so far here   */\r
11794             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
11795                 *p++;\r
11796                 if((int) piece >= (int) BlackPawn ) {\r
11797                     i = (int)piece - (int)BlackPawn;\r
11798                     if( i >= BOARD_HEIGHT ) return FALSE;\r
11799                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
11800                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
11801                 } else {\r
11802                     i = (int)piece - (int)WhitePawn;\r
11803                     if( i >= BOARD_HEIGHT ) return FALSE;\r
11804                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
11805                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
11806                 }\r
11807             }\r
11808         }\r
11809     }\r
11810 \r
11811 \r
11812 \r
11813     if(sscanf(p, "%d", &i) == 1) {\r
11814         FENrulePlies = i; /* 50-move ply counter */\r
11815         /* (The move number is still ignored)    */\r
11816     }\r
11817  }\r
11818     return TRUE;\r
11819 }\r
11820       \r
11821 void\r
11822 EditPositionPasteFEN(char *fen)\r
11823 {\r
11824   if (fen != NULL) {\r
11825     Board initial_position;\r
11826 \r
11827     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
11828       DisplayError("Bad FEN position in clipboard", 0);\r
11829       return ;\r
11830     } else {\r
11831       int savedBlackPlaysFirst = blackPlaysFirst;\r
11832       EditPositionEvent();\r
11833       blackPlaysFirst = savedBlackPlaysFirst;\r
11834       CopyBoard(boards[0], initial_position);\r
11835           /* [HGM] copy FEN attributes as well */\r
11836           {   int i;\r
11837               initialRulePlies = FENrulePlies;\r
11838               epStatus[0] = FENepStatus;\r
11839               for( i=0; i<nrCastlingRights; i++ )\r
11840                   castlingRights[0][i] = FENcastlingRights[i];\r
11841           }\r
11842       EditPositionDone();\r
11843       DisplayBothClocks();\r
11844       DrawPosition(FALSE, boards[currentMove]);\r
11845     }\r
11846   }\r
11847 }\r